#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb.h>
#include "pcscdefines.h"
#include "usbserial_linux.h"
#include "common.h"

#ifndef FALSE
#define FALSE           0
#endif

#ifndef TRUE
#define TRUE            1
#endif

// USB device
typedef struct _USB_DEVICE {
    usb_dev_handle *hDevice;
    DWORD readerId;
    char dirname[PATH_MAX + 1];
    char filename[PATH_MAX + 1];
} USB_DEVICE, *PUSB_DEVICE;

// Array of pointer to USB device
static PUSB_DEVICE g_pUsbDevices[MAX_READERS];

// Initialize usbserial
void __attribute__ ((constructor)) InitUSB(void)
{
    // Initialize array of pointer to USB device
    memset(g_pUsbDevices, 0, sizeof(g_pUsbDevices));
    
    // Initialize USB library
    usb_init();
}

// Finalize usbserial
void __attribute__ ((destructor)) FiniUSB(void)
{
    int i;
    
    for (i = 0; i < MAX_READERS; i++)
    {
        if (g_pUsbDevices[i] != NULL)
        {
            // Release interface
            usb_release_interface(g_pUsbDevices[i]->hDevice, 0);

            // Close device
            usb_close(g_pUsbDevices[i]->hDevice);
            
            // Free device
            free(g_pUsbDevices[i]);
            g_pUsbDevices[i] = NULL;
        }
    }
}

// Get reader ID
DWORD GetReaderId(DWORD readerIndex)
{
    DWORD readerId = 0;
    
    if (IsValidReaderIndex(readerIndex) &&
        (g_pUsbDevices[readerIndex] != NULL))
    {
        readerId = g_pUsbDevices[readerIndex]->readerId;
    }

    return readerId;
}

// Open USB
RESPONSECODE OpenUSB( DWORD readerIndex, DWORD channel )
{
    RESPONSECODE ret = STATUS_UNSUCCESSFUL;
    int found;
    int opened;
    
    struct usb_bus *bus;
    struct usb_bus *busses;
    struct usb_device *dev;
    usb_dev_handle *hDevice;
    DWORD readerId;
    int err;
    
    // GET_ACR_STAT
    BYTE cmd[] = { 0x01, 0x01, 0x00, 0x00 };
    BYTE rsp[128];
    int bytes;
    int i;

#ifdef PCSC_DEBUG
    int j;
#endif

#ifdef PCSC_DEBUG
    printf("OpenUSB: readerIndex: %d\n", readerIndex);
#endif

    // Check parameters
    if (readerIndex >= MAX_READERS)
        return STATUS_UNSUCCESSFUL;

    // If device is already opened
    if (g_pUsbDevices[readerIndex] != NULL)
        return STATUS_UNSUCCESSFUL;

    usb_find_busses();
    usb_find_devices();

    // Find ACR38U device
    found = FALSE;
    busses = usb_get_busses();
    for (bus = busses; bus; bus = bus->next)
    {
        for (dev = bus->devices; dev; dev = dev->next)
        {
            // Create reader ID
            readerId = (dev->descriptor.idVendor << 16) | dev->descriptor.idProduct;
            
            // Compare reader ID
            if ((readerId == ACS_ACR38U) ||
                (readerId == ACS_ACR38U_SAM) ||
                (readerId == ACS_CRYPTOMATE))
            {
#ifdef PCSC_DEBUG
                printf("OpenUSB: %s/%s\n", bus->dirname, dev->filename);
#endif
                opened = FALSE;
                for (i = 0; i < MAX_READERS; i++)
                {
                    if (g_pUsbDevices[i] != NULL)
                    {
                        if ((strcmp(g_pUsbDevices[i]->dirname, bus->dirname) == 0) &&
                            (strcmp(g_pUsbDevices[i]->filename, dev->filename) == 0))
                        {
                            opened = TRUE;
                            break;
                        }
                    }
                }

#ifdef PCSC_DEBUG
                printf("OpenUSB: opened: %d\n", opened);
#endif
                // If device is not opened
                if (!opened)
                {
                    // Open device
                    hDevice = usb_open(dev);
#ifdef PCSC_DEBUG
                    printf("OpenUSB: usb_open: 0x%X\n", hDevice);
#endif
                    if (hDevice != NULL)
                    {
                        err = usb_claim_interface(hDevice, 0);
#ifdef PCSC_DEBUG
                        printf("OpenUSB: usb_claim_interface: %d\n", err);
#endif
                        // Claim interface
                        if (err >= 0)
                        {
#ifdef PCSC_DEBUG
                            printf("OpenUSB: Testing readerIndex %d...\n", readerIndex);
#endif
                            // Try 10 times to test command/response from reader
                            for (i = 0; i < 10; i++)
                            {
                                bytes = usb_bulk_write(hDevice, 0x02, (char *) cmd, sizeof(cmd), 2 * 1000);
#ifdef PCSC_DEBUG
                                printf("OpenUSB: bytes: %d, cmd: ", bytes);
                                for (j = 0; j < bytes; j++)
                                    printf("%02X ", cmd[j]);
                                printf("\n");
#endif
                                bytes = usb_bulk_read(hDevice, 0x82, (char *) rsp, sizeof(rsp), 2 * 1000);
#ifdef PCSC_DEBUG
                                printf("OpenUSB: bytes: %d, rsp: ", bytes);
                                for (j = 0; j < bytes; j++)
                                    printf("%02X ", rsp[j]);
                                printf("\n");
#endif
                                if (bytes > 0)
                                {
                                    found = TRUE;
                                    break;
                                }
                            }
#ifdef PCSC_DEBUG
                            printf("OpenUSB: found: %d\n", found);
#endif
                            if (found)
                            {
                                // Allocate device
                                g_pUsbDevices[readerIndex] = (PUSB_DEVICE) malloc(sizeof(USB_DEVICE));
                                if (g_pUsbDevices[readerIndex] != NULL)
                                {
#ifdef PCSC_DEBUG
                                    printf("OpenUSB: connected: %s/%s\n", bus->dirname, dev->filename);
#endif
                                    // Store device information
                                    g_pUsbDevices[readerIndex]->hDevice = hDevice;
                                    g_pUsbDevices[readerIndex]->readerId = readerId;
                                    strcpy(g_pUsbDevices[readerIndex]->dirname, bus->dirname);
                                    strcpy(g_pUsbDevices[readerIndex]->filename, dev->filename);

                                    ret = STATUS_SUCCESS;
                                }
                                else
                                {
                                    // Release interface if device cannot be allocated
                                    usb_release_interface(hDevice, 0);
                                    found = FALSE;
                                }
                            }
                            else
                            {
                                // Release interface if test is failed
                                usb_release_interface(hDevice, 0);
                            }
                        }
                        
                        if (!found)
                        {
                            // Close device if test is failed
                            usb_close(hDevice);
                        }
                    }

                    if (found)
                        break;
                }
            }
        }
        
        if (found)
            break;
    }

#ifdef PCSC_DEBUG
    printf("OpenUSB: return 0x%X\n", ret);
#endif
    return ret;
}

// Write USB
RESPONSECODE WriteUSB(DWORD readerIndex, DWORD length, unsigned char *buffer)
{
    RESPONSECODE ret = STATUS_SUCCESS;
    int bytes;
    
    // Check parameters
    if ((buffer == NULL) || (length == 0))
        return STATUS_UNSUCCESSFUL;

    // Check readerIndex
    if (readerIndex >= MAX_READERS)
        return STATUS_UNSUCCESSFUL;
        
    // Check open
    if (g_pUsbDevices[readerIndex] == NULL)
        return STATUS_UNSUCCESSFUL;
        
    bytes = usb_bulk_write(g_pUsbDevices[readerIndex]->hDevice, 0x02, (char *) buffer, length, 50 * 1000);
    if (bytes != length)
    {
#ifdef PCSC_DEBUG
        printf("WriteUSB: %d, %d\n", bytes, length);
#endif
        ret = STATUS_UNSUCCESSFUL;
    }

    return ret;
}

// Read USB
RESPONSECODE ReadUSB(DWORD readerIndex, DWORD *pLength, unsigned char *buffer)
{
    RESPONSECODE ret = STATUS_SUCCESS;
    int bytes;
    
    // Check parameters
    if ((buffer == NULL) || (pLength == NULL))
        return STATUS_UNSUCCESSFUL;
        
    // Check length
    if (*pLength == 0)
        return STATUS_UNSUCCESSFUL;

    // Check readerIndex
    if (readerIndex >= MAX_READERS)
        return STATUS_UNSUCCESSFUL;

    // Check open
    if (g_pUsbDevices[readerIndex] == NULL)
        return STATUS_UNSUCCESSFUL;
        
    bytes = usb_bulk_read(g_pUsbDevices[readerIndex]->hDevice, 0x82, (char *) buffer, *pLength, 50 * 1000);
    if (bytes < 0)
    {
#ifdef PCSC_DEBUG
        printf("ReadUSB: %d\n", bytes);
#endif
        ret = STATUS_UNSUCCESSFUL;
        *pLength = 0;
    }
    else
        *pLength = bytes;

    return ret;
}

// Close USB
RESPONSECODE CloseUSB(DWORD readerIndex)
{
#ifdef PCSC_DEBUG
    printf("CloseUSB: readerIndex: %d\n", readerIndex);
#endif
    // Check parameters
    if (readerIndex >= MAX_READERS)
        return STATUS_UNSUCCESSFUL;
        
    if (g_pUsbDevices[readerIndex] != NULL)
    {
        // Release interface
        usb_release_interface(g_pUsbDevices[readerIndex]->hDevice, 0);

        // Close device
        usb_close(g_pUsbDevices[readerIndex]->hDevice);

        // Free device
        free(g_pUsbDevices[readerIndex]);
        g_pUsbDevices[readerIndex] = NULL;
    }
    
    return STATUS_SUCCESS;
}
