#include "DfuDevice.h"
#include "libusb-1.0/libusb.h"
#include "dfu_defs.h"
#include "DfuDeviceErrors.h"
#include "ProgressLog.h"
#include <QThread>
#include <memory>

DfuDevice::DfuDevice() {
    libusb_init(NULL);
    //libusb_set_debug(NULL, LIBUSB_LOG_LEVEL_DEBUG);
}

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

void DfuDevice::detect(LQError &err) {
    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) {
            if (libusb_get_config_descriptor(dev, 0, &pcfg) == 0) {
                for (ssize_t iface_idx = 0; (iface_idx < pcfg->bNumInterfaces) && (fnd_dev == NULL); ++iface_idx) {
                    const struct libusb_interface *iface = &pcfg->interface[iface_idx];
                    for (ssize_t altset_idx = 0; (altset_idx < iface->num_altsetting) && (fnd_dev == NULL); ++altset_idx) {
                        const struct libusb_interface_descriptor *iface_descr = &iface->altsetting[altset_idx];
                        if ((iface_descr->bInterfaceClass == DFU_USB_IFACE_CLASS)
                            && (iface_descr->bInterfaceSubClass == DFU_USB_IFACE_SUBCLASS)) {
                            fnd_dev = dev;
                            m_vid = devdescr.idVendor;
                            m_pid = devdescr.idProduct;
                            m_mode = iface_descr->bInterfaceProtocol == DFU_USB_IFACE_PROTOCOL_APPMODE ? Mode::Application :
                                     iface_descr->bInterfaceProtocol == DFU_USB_IFACE_PROTOCOL_DFUMODE ? Mode::DFU : Mode::Unknown;
                        }
                    }
                }
                libusb_free_config_descriptor(pcfg);
            }
        }
    }

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


            m_usb_hnd = devhnd;
        }
    }

    libusb_free_device_list(usb_devlist, 1);
}

void DfuDevice::upload(quint16 block_num, quint16 len, QByteArray &data, LQError &err) {
    ioreq_rx(DFU_USB_REQ_UPLOAD, block_num, len, data, err);
}

void DfuDevice::dnload(quint16 block_num, const QByteArray &data, LQError &err) {
    ioreq_tx(DFU_USB_REQ_DNLOAD, block_num, data, err);
}

void DfuDevice::getStatus(StatusInfo &info, LQError &err) {
    static const quint16 status_size {6};
    QByteArray rxdata;
    ioreq_rx(DFU_USB_REQ_GETSTATUS, 0, status_size, rxdata, err);
    if (err.isSuccess()) {
        if (rxdata.size() != status_size) {
            err = DfuDeviceErrors::devCtlReqInsufSize(DFU_USB_REQ_GETSTATUS, status_size, rxdata.size());
        } else {
            info = StatusInfo{static_cast<Status>(rxdata.at(0)),
                              static_cast<quint32>(rxdata.at(1)) |
                              (static_cast<quint32>(rxdata.at(2)) << 8) |
                              (static_cast<quint32>(rxdata.at(3)) << 16),
                            static_cast<State>(rxdata.at(4))
                    };
            if (info.pollTout() > 0) {
                QThread::msleep(info.pollTout());
            }
        }
    }
}

void DfuDevice::clearStatus(LQError &err) {
    ioreq_tx(DFU_USB_REQ_CLRSTATUS, 0, QByteArray{}, err);
}

void DfuDevice::getState(State &state, LQError &err) {
    static const quint16 state_size {1};
    QByteArray rxdata;
    ioreq_rx(DFU_USB_REQ_GETSTATE, 0, state_size, rxdata, err);
    if (err.isSuccess()) {
        if (rxdata.size() != state_size) {
            err = DfuDeviceErrors::devCtlReqInsufSize(DFU_USB_REQ_GETSTATE, state_size, rxdata.size());
        } else {
            state = static_cast<State>(rxdata.at(0));
        }
    }
}

void DfuDevice::abort(LQError &err) {
    ioreq_tx(DFU_USB_REQ_ABORT, 0, QByteArray{}, err);
}

void DfuDevice::close() {
    if (m_usb_hnd) {
        libusb_close(m_usb_hnd);
    }
}

void DfuDevice::ioreq_tx(quint8 req, quint16 wValue, const QByteArray &data, LQError &err) {
    if (!m_usb_hnd) {
        err = DfuDeviceErrors::devNotOpened();
    } else {
        int usbres = libusb_control_transfer(
                    m_usb_hnd,
                    LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT,
                    req,  wValue, m_iface_num,
                    reinterpret_cast<unsigned char *>(const_cast<char *>(data.data())),
                    data.size(), m_ioreq_tout);
        if (usbres < 0) {
            err = DfuDeviceErrors::devCtlReqFailed(req, usbres);
        } else if (usbres != data.size()) {
            err = DfuDeviceErrors::devCtlReqInsufSize(req, data.size(), usbres);
        }
    }
}

void DfuDevice::ioreq_rx(quint8 req, quint16 wValue, quint16 req_len, QByteArray &data, LQError &err) {
    if (!m_usb_hnd) {
        err = DfuDeviceErrors::devNotOpened();
    } else {
        std::unique_ptr<unsigned char []> rx_buf{new unsigned char[req_len]};
        int usbres = libusb_control_transfer(
                    m_usb_hnd,
                    LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN,
                    req,  wValue, m_iface_num,
                    rx_buf.get(),
                    req_len, m_ioreq_tout);
        if (usbres < 0) {
            err = DfuDeviceErrors::devCtlReqFailed(req, usbres);
        } else {
            data = QByteArray{reinterpret_cast<char *>(rx_buf.get()), usbres};
        }
    }
}
