#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "usbtenki_provider.h"
#include "usbtenki.h"
#include "chip.h"
#include "unit.h"
#include "virtual.h"


typedef struct SourceEntry {
    Source source;
    struct USBTenki_info *device_info;
    struct USBTenki_channel *usb_channels;
} SourceEntry;

List entries;
List disconnected_entries;


VirtualOptions usbtenki_virtual_options = VIRTUAL_OPTIONS_DEFAULT;

char usbtenki_provider_verbose = 0;

/**
 * List of channel IDs to re-use on every call to usbtenki_readChannelList()
 * (for compatibility with legacy API... will remove this later...)
 */
static const int CHANNEL_IDS[DEVICE_MAX_CHANNELS] = {
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
};

static struct USBTenki_channel usb_channels_tmp[DEVICE_MAX_CHANNELS];
static struct USBTenki_list_ctx *list_context;


int usbtenki_provider_init() {

    if (usbtenki_init() != 0) {
        fprintf(stderr, "Failed to init. Aborting.\n");
        return -1;
    };

    list_init(&entries);
    list_init(&disconnected_entries);

    // Populate devices

    list_context = usbtenki_initListCtx();

    if (!list_context) {
        fprintf(stderr, "Error: Failed to allocate device listing context.\n");
        return -1;
    }

    struct USBTenki_info *info;

    for (int i = 0; i < list_context->n_devices; i++) {

        info = &list_context->devices[i];

        USBTenki_dev_handle handle = usbtenki_openDevice(info);
        if (!handle) {
            fprintf(stderr, "ERROR: Cannot open device %s with serial number %s\n", info->str_prodname, info->str_serial);
            continue;
        }

        int channel_count = usbtenki_listChannels(handle, usb_channels_tmp, DEVICE_MAX_CHANNELS);

        usbtenki_closeDevice(handle);

        if (channel_count < 0) {
            continue;
        }

        SourceEntry *entry = malloc(sizeof(SourceEntry));
        entry->device_info = info;

        int usb_channels_len = channel_count * sizeof(struct USBTenki_channel);
        entry->usb_channels = malloc(usb_channels_len);
        memcpy(entry->usb_channels, usb_channels_tmp, usb_channels_len);

        Device *device = device_new();
        strncpy(device->serial_number, info->str_serial, DEVICE_SERIAL_NUMBER_LEN);
        strncpy(device->product_name, info->str_prodname, DEVICE_PRODUCT_NAME_LEN);
        device->version.major = info->major;
        device->version.minor = info->minor;
        device->port = info->port;
        device->flags = (DEVICE_FLAG_LOCAL | DEVICE_FLAG_ALIVE);

        for (int i = 0; i < channel_count; i++) {

            struct USBTenki_channel *usb_channel = entry->usb_channels + i;

            if (usb_channel->chip_id == USBTENKI_CHIP_NONE) {
                continue;
            }

            Channel *channel = malloc(sizeof(Channel));
            channel->chip_id = usb_channel->chip_id;
            channel->quantity.unit = chip_native_unit(channel->chip_id);
            channel->quantity.type = QUANTITY_TYPE_ERROR;
            channel->quantity.value_error = CHIP_ERROR_NO_DATA;

            list_add(&device->channels, channel);

        }

        source_init(&entry->source, device, &usbtenki_virtual_options);

        list_add(&entries, entry);

    }

    return 0;

}

void usbtenki_provider_shutdown() {

    LIST_FOR(&entries) {
        SourceEntry *entry = LIST_CUR(SourceEntry);
        // usbtenki_closeDevice(entry->handle);
        device_delete(entry->source.device);
        source_clear(&entry->source);
        free(entry->usb_channels);
        free(entry);
    }

    list_clear(&entries);
    list_clear(&disconnected_entries);

    usbtenki_freeListCtx(list_context);
    usbtenki_shutdown();

}


static void entry_disconnected(SourceEntry *entry) {

    Source *source = &entry->source;
    Device *device = source->device;
    device_invalidate(device);
    device_flag_clear(device, DEVICE_FLAG_ALIVE);

    source_refresh(source);

    list_add_unique(&disconnected_entries, entry);

}

struct USBTenki_list_ctx *usbtenki_provider_get_context() 
{
    return list_context;
}

uint8_t usbtenki_provider_get_source_count() {

    return entries.size;

}

Source *usbtenki_provider_get_source(uint8_t index) {

    SourceEntry *entry = LIST_GET(&entries, index, SourceEntry);
    return entry ? &entry->source : NULL;

}

static SourceEntry *find_entry(const char *serial_number) {

    LIST_FOR(&entries) {
        SourceEntry *entry = LIST_CUR(SourceEntry);
        if (DEVICE_SERIAL_NUMBERS_EQUAL(entry->source.device->serial_number, serial_number)) {
            return entry;
        }
    }

    return NULL;

}

Source *usbtenki_provider_find_device(const char *serial_number) {

    SourceEntry *entry = find_entry(serial_number);
    return (entry != NULL) ? &entry->source : NULL;

}


USBTenki_dev_handle usbtenki_provider_find_handle(const char *serial_number) {

    USBTenki_dev_handle handle = NULL;
    SourceEntry *entry = find_entry(serial_number);
    if (entry && usbtenki_provider_is_connected(&entry->source)) {
        handle = usbtenki_openDevice(entry->device_info);
        if (!handle) {
            entry_disconnected(entry);
        }
    }
    return handle;
}

USBTenki_dev_handle usbtenki_provider_get_handle(uint8_t index) {

    SourceEntry *entry = LIST_GET(&entries, index, SourceEntry);
    return entry ? usbtenki_provider_find_handle(entry->device_info->str_serial) : NULL;

}



static void entry_poll(SourceEntry *entry) {

    if (!device_flag_check(entry->source.device, DEVICE_FLAG_ALIVE)) {
        return;
    }

    Source *source = &entry->source;
    Device *device = source->device;
    int channel_count = device->channels.size;
    int res;

    USBTenki_dev_handle handle = usbtenki_openDevice(entry->device_info);

    if (!handle) {
        entry_disconnected(entry);
        return;
    }

    res = usbtenki_readChannelList(
        handle,
        CHANNEL_IDS,
        device->channels.size,
        entry->usb_channels,
        channel_count,
        1,
        usbtenki_provider_verbose ? USBTENKI_FLAG_VERBOSE : 0
    );

    usbtenki_closeDevice(handle);

    if (res < 0) {
        entry_disconnected(entry);
        return;
    }

    uint8_t max_calpoints_count = 0;

    for (int c = 0; c < channel_count; c++) {
        Channel *channel = LIST_GET(&device->channels, c, Channel);
        channel->quantity = entry->usb_channels[c].quantity;
        channel->calpoints_count = entry->usb_channels[c].calpoints_count;
        if (channel->calpoints_count > 0) {
            memcpy(channel->calpoints, entry->usb_channels[c].calpoints, CAL_POINTS_SIZE);
            if (channel->calpoints_count > max_calpoints_count) {
                max_calpoints_count = channel->calpoints_count;
            }
        }
        else {
            memset(channel->calpoints, 0xFF, sizeof(channel->calpoints)); // All NAN
        }
    }

    if (max_calpoints_count > 0) {
        device_flag_set(device, DEVICE_FLAG_CALPOINTS);
    }
    else {
        device_flag_clear(device, DEVICE_FLAG_CALPOINTS);
        memset(source->user_calibration_enabled, 0, sizeof(source->user_calibration_enabled));
    }

    source_refresh(source);

}

void usbtenki_provider_detect_reconnections() {

    LIST_FOR(&disconnected_entries) {

        SourceEntry *entry = LIST_CUR(SourceEntry);

        USBTenki_dev_handle handle = usbtenki_openBySerial(entry->device_info);
        if (!handle) {
            continue;
        }

        usbtenki_closeDevice(handle);

        device_flag_set(entry->source.device, DEVICE_FLAG_ALIVE);
        LIST_CUR_REMOVE(&disconnected_entries);
    }
}

void usbtenki_provider_poll() {

    if (disconnected_entries.size > 0) {
        usbtenki_provider_detect_reconnections();
    }

    LIST_FOR(&entries) {
        SourceEntry *entry = LIST_CUR(SourceEntry);
        entry_poll(entry);
    }


}

int usbtenki_provider_is_connected(Source *source) {
    return device_flag_check(source->device, DEVICE_FLAG_ALIVE);
}

