#include "UsbDevice.h"
#include "UsbDeviceErrors.h"
#include "libusb-1.0/libusb.h"
#include <memory>

UsbDevice::UsbDevice() {
    libusb_init(NULL);
}

UsbDevice::~UsbDevice() {
    close();
}

void UsbDevice::detect(LQError &err, const QList<ID> &idList) {
    close();

    libusb_device **usb_devlist;
    ssize_t usb_devlist_size;



    libusb_device *fnd_dev = NULL;
    //получаем список устройств
    usb_devlist_size = libusb_get_device_list(NULL, &usb_devlist);
    for (ssize_t dev_idx = 0; (dev_idx < usb_devlist_size)  && (fnd_dev == NULL); ++dev_idx) {
        libusb_device *dev = usb_devlist[dev_idx];
        //получаем дескриптор устройства для проверки vid, pid
        struct libusb_device_descriptor devdescr;
        struct libusb_config_descriptor *pcfg;


        if (libusb_get_device_descriptor (dev, &devdescr) == 0) {
            const auto &it {std::find_if(idList.cbegin(), idList.cend(), [&devdescr](const ID &id){
                    return (id.vid() == devdescr.idVendor) && (id.pid() == devdescr.idProduct);})};
            if (it != idList.cend()) {
                fnd_dev = dev;
            }
        }
    }

    if (!fnd_dev) {
        err = UsbDeviceErrors::devNotDetected();
    } else {
        struct libusb_device_handle *devhnd;
        int err_code = libusb_open(fnd_dev, &devhnd);
        if (err_code != 0) {
            err = UsbDeviceErrors::devOpen(err_code);
        } else {
            if (libusb_claim_interface(devhnd, 0) != 0) {
                err = UsbDeviceErrors::devIfaceClaim(err_code);
            }


            m_usb_hnd = devhnd;
        }
    }

    libusb_free_device_list(usb_devlist, 1);
}

void UsbDevice::close() {
    if (m_usb_hnd) {
        libusb_close(m_usb_hnd);
        m_usb_hnd = nullptr;
    }
}

void UsbDevice::ctlVendorReqTx(quint8 req, quint16 wValue, quint16 wIndex, const QByteArray &data, LQError &err) {
    if (!m_usb_hnd) {
        err = UsbDeviceErrors::devNotOpened();
    } else {
        int usbres = libusb_control_transfer(
                    m_usb_hnd,
                    LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_OUT,
                    req,  wValue, wIndex,
                    reinterpret_cast<unsigned char *>(const_cast<char *>(data.data())),
                    data.size(), m_ioreq_tout);
        if (usbres < 0) {
            err = UsbDeviceErrors::devCtlReqFailed(req, usbres);
        } else if (usbres != data.size()) {
            err = UsbDeviceErrors::devCtlReqInsufSize(req, data.size(), usbres);
        }
    }
}

void UsbDevice::ctlVendorReqRx(quint8 req, quint16 wValue, quint16 wIndex, quint16 reqLen, QByteArray &data, LQError &err) {
    if (!m_usb_hnd) {
        err = UsbDeviceErrors::devNotOpened();
    } else {
        std::unique_ptr<unsigned char []> rx_buf{new unsigned char[reqLen]};
        int usbres = libusb_control_transfer(
                    m_usb_hnd,
                    LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE | LIBUSB_ENDPOINT_IN,
                    req,  wValue, wIndex,
                    rx_buf.get(),
                    reqLen, m_ioreq_tout);
        if (usbres < 0) {
            err = UsbDeviceErrors::devCtlReqFailed(req, usbres);
        } else {
            data = QByteArray{reinterpret_cast<char *>(rx_buf.get()), usbres};
        }
    }
}
