/**
    @file Connection_uLimeSDREntry.cpp
    @author Lime Microsystems
    @brief Implementation of uLimeSDR board connection.
*/

#include "Connection_uLimeSDR.h"
using namespace lime;

#ifdef __unix__
void Connection_uLimeSDREntry::handle_libusb_events()
{
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 250000;
    while(mProcessUSBEvents.load() == true)
    {
        int r = libusb_handle_events_timeout_completed(ctx, &tv, NULL);
        if(r != 0) printf("error libusb_handle_events %s\n", libusb_strerror(libusb_error(r)));
    }
}
#endif // __UNIX__

int Connection_uLimeSDR::USBTransferContext::idCounter=0;

//! make a static-initialized entry in the registry
void __loadConnection_uLimeSDREntry(void) //TODO fixme replace with LoadLibrary/dlopen
{
static Connection_uLimeSDREntry uLimeSDREntry;
}

Connection_uLimeSDREntry::Connection_uLimeSDREntry(void):
    ConnectionRegistryEntry("uLimeSDR")
{
#ifndef __unix__
    //m_pDriver = new CDriverInterface();
#else
    int r = libusb_init(&ctx); //initialize the library for the session we just declared
    if(r < 0)
        printf("Init Error %i\n", r); //there was an error
    libusb_set_debug(ctx, 3); //set verbosity level to 3, as suggested in the documentation
    mProcessUSBEvents.store(true);
    mUSBProcessingThread = std::thread(&Connection_uLimeSDREntry::handle_libusb_events, this);
#endif
}

Connection_uLimeSDREntry::~Connection_uLimeSDREntry(void)
{
#ifndef __unix__
    //delete m_pDriver;
#else
    mProcessUSBEvents.store(false);
    mUSBProcessingThread.join();
    libusb_exit(ctx);
#endif
}

std::vector<ConnectionHandle> Connection_uLimeSDREntry::enumerate(const ConnectionHandle &hint)
{
    std::vector<ConnectionHandle> handles;

#ifndef __unix__
    DWORD devCount = 0;
    FT_STATUS ftStatus = FT_OK;
    ftStatus = FT_ListDevices(&devCount, NULL, FT_LIST_NUMBER_ONLY);
    if(FT_FAILED(ftStatus))
        return handles;
    if (devCount > 0)
    {
        for(int i = 0; i<devCount; ++i)
        {
            ConnectionHandle handle;
            handle.media = "USB";
            handle.name = "uLimeSDR";
            handle.index = i;
            handles.push_back(handle);
        }
    }
#else
    libusb_device **devs; //pointer to pointer of device, used to retrieve a list of devices
    int usbDeviceCount = libusb_get_device_list(ctx, &devs);

    if (usbDeviceCount < 0) {
        printf("failed to get libusb device list: %s\n", libusb_strerror(libusb_error(usbDeviceCount)));
        return handles;
    }

    libusb_device_descriptor desc;
    for(int i=0; i<usbDeviceCount; ++i)
    {
        int r = libusb_get_device_descriptor(devs[i], &desc);
        if(r<0)
            printf("failed to get device description\n");
        int pid = desc.idProduct;
        int vid = desc.idVendor;

        if( vid == 0x0403)
        {
            if(pid == 0x601F)
            {
                libusb_device_handle *tempDev_handle;
                tempDev_handle = libusb_open_device_with_vid_pid(ctx, vid, pid);
                if(libusb_kernel_driver_active(tempDev_handle, 0) == 1)   //find out if kernel driver is attached
                {
                    if(libusb_detach_kernel_driver(tempDev_handle, 0) == 0) //detach it
                        printf("Kernel Driver Detached!\n");
                }
                if(libusb_claim_interface(tempDev_handle, 0) < 0) //claim interface 0 (the first) of device
                {
                    printf("Cannot Claim Interface\n");
                }

                ConnectionHandle handle;
                //check operating speed
                int speed = libusb_get_device_speed(devs[i]);
                if(speed == LIBUSB_SPEED_HIGH)
                    handle.media = "USB 2.0";
                else if(speed == LIBUSB_SPEED_SUPER)
                    handle.media = "USB 3.0";
                else
                    handle.media = "USB";
                //read device name
                char data[255];
                memset(data, 0, 255);
                int st = libusb_get_string_descriptor_ascii(tempDev_handle, 2, (unsigned char*)data, 255);
                if(st < 0)
                    printf("Error getting usb descriptor\n");
                if(strlen(data) > 0)
                    handle.name = std::string(data, size_t(st));
                handle.addr = std::to_string(int(pid))+":"+std::to_string(int(vid));

                if (desc.iSerialNumber > 0)
                {
                    r = libusb_get_string_descriptor_ascii(tempDev_handle,desc.iSerialNumber,(unsigned char*)data, sizeof(data));
                    if(r<0)
                        printf("failed to get serial number\n");
                    else
                        handle.serial = std::string(data, size_t(r));
                }
                libusb_close(tempDev_handle);

                //add handle conditionally, filter by serial number
                if (hint.serial.empty() or hint.serial == handle.serial)
                {
                    handles.push_back(handle);
                }
            }
        }
    }

    libusb_free_device_list(devs, 1);
#endif
    return handles;
}

IConnection *Connection_uLimeSDREntry::make(const ConnectionHandle &handle)
{
#ifndef __unix__
    return new Connection_uLimeSDR(mFTHandle, handle.index);
#else
    const auto pidvid = handle.addr;
    const auto splitPos = pidvid.find(":");
    const auto pid = std::stoi(pidvid.substr(0, splitPos));
    const auto vid = std::stoi(pidvid.substr(splitPos+1));
    return new Connection_uLimeSDR(ctx, handle.index, vid, pid);
#endif
}