// SPDX-License-Identifier: Apache-2.0
/*
 * Copyright (c) 2022 Gwenhael Goavec-Merou <gwenhael.goavec-merou@trabucayre.com>
 */

#include <libusb.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <string.h>

#include "cable.hpp"
#include "display.hpp"
#include "libusb_ll.hpp"

using namespace std;

libusb_ll::libusb_ll(int vid, int pid):_verbose(true)
{
	(void)vid;
	(void)pid;
	if (libusb_init(&_usb_ctx) < 0)
		throw std::runtime_error("libusb_init_failed");
}

libusb_ll::~libusb_ll()
{
	libusb_exit(_usb_ctx);
}

bool libusb_ll::scan()
{
	int i = 0;
	libusb_device **dev_list;
	libusb_device *usb_dev;
	libusb_device_handle *handle;

	/* iteration */
	ssize_t list_size = libusb_get_device_list(_usb_ctx, &dev_list);
	if (_verbose)
		printInfo("found " + std::to_string(list_size) + " USB device");

	char *mess = (char *)malloc(1024);
	snprintf(mess, 1024, "%3s %3s %-13s %-15s %-12s %-20s %s",
			"Bus", "device", "vid:pid", "probe type", "manufacturer",
			"serial", "product");
	printSuccess(mess);

	while ((usb_dev = dev_list[i++]) != NULL) {
		bool found = false;
		struct libusb_device_descriptor desc;
		if (libusb_get_device_descriptor(usb_dev, &desc) != 0) {
			printError("Unable to get device descriptor");
			return false;
		}

		char probe_type[256];

		/* Linux host controller */
		if (desc.idVendor == 0x1d6b)
			continue;

		/* ftdi devices */
		// FIXME: missing iProduct in cable_list
		if (desc.idVendor == 0x403) {
			if (desc.idProduct == 0x6010)
				snprintf(probe_type, 256, "FTDI2232");
			else if (desc.idProduct == 0x6011)
				snprintf(probe_type, 256, "ft4232");
			else if (desc.idProduct == 0x6001)
				snprintf(probe_type, 256, "ft232RL");
			else if (desc.idProduct == 0x6014)
				snprintf(probe_type, 256, "ft232H");
			else if (desc.idProduct == 0x6015)
				snprintf(probe_type, 256, "ft231X");
			else if (desc.idProduct == 0x6043)
				snprintf(probe_type, 256, "FT4232HP");
			else
				snprintf(probe_type, 256, "unknown FTDI");
			found = true;
		} else {
			// FIXME: DFU device can't be detected here
			for (auto b = cable_list.begin(); b != cable_list.end(); b++) {
				cable_t *c = &(*b).second;
				if (c->vid == desc.idVendor && c->pid == desc.idProduct) {
					snprintf(probe_type, 256, "%s", (*b).first.c_str());
					found = true;
				}
			}
		}

		if (!found)
			continue;

		int ret = libusb_open(usb_dev, &handle);
		if (ret != 0) {
			snprintf(mess, 1024,
				"Error: can't open device with vid:vid = 0x%04x:0x%04x. "
				"Error code %d %s",
				desc.idVendor, desc.idProduct,
				ret, libusb_strerror(static_cast<libusb_error>(ret)));
			printError(mess);
			continue;
		}

		uint8_t iproduct[200];
		uint8_t iserial[200];
		uint8_t imanufacturer[200];
		ret = libusb_get_string_descriptor_ascii(handle,
			desc.iProduct, iproduct, 200);
		if (ret < 0)
			snprintf((char*)iproduct, 200, "none");
		ret = libusb_get_string_descriptor_ascii(handle,
			desc.iManufacturer, imanufacturer, 200);
		if (ret < 0)
			snprintf((char*)imanufacturer, 200, "none");
		ret = libusb_get_string_descriptor_ascii(handle,
			desc.iSerialNumber, iserial, 200);
		if (ret < 0)
			snprintf((char*)iserial, 200, "none");
		uint8_t bus_addr = libusb_get_bus_number(usb_dev);
		uint8_t dev_addr = libusb_get_device_address(usb_dev);

		snprintf(mess, 1024, "%03d %03d    0x%04x:0x%04x %-15s %-12s %-20s %s",
				bus_addr, dev_addr,
				desc.idVendor, desc.idProduct,
				probe_type, imanufacturer, iserial, iproduct);

		printInfo(mess);

		libusb_close(handle);
	}

	libusb_free_device_list(dev_list, 1);
	free(mess);

	return true;
}
