#include "usbadc.h"
#include <map>
#include <mutex>
#include <cstring>
#include "urpc.h"
static std::map<device_t, urpc_device_handle_t> impl_by_handle;
static std::mutex impl_by_handle_mutex;
static void push_data(uint8_t **where, const void *data, size_t size)
{
    memcpy(*where, data, size);
    *where += size;
}
static void push_float(uint8_t **where, float value)
{
    push_data(where, &value, sizeof(value));
}
static void push_double(uint8_t **where, double value)
{
    push_data(where, &value, sizeof(value));
}
static float pop_float(uint8_t **where)
{
    float result;
    memcpy(&result, *where, sizeof(result));
    *where += sizeof(result);
    return result;
}
static double pop_double(uint8_t **where)
{
    double result;
    memcpy(&result, *where, sizeof(result));
    *where += sizeof(result);
    return result;
}
#define GENERATE_PUSH(Type) \
static void push_##Type(uint8_t **where, Type value) { \
    push_data(where, &value, sizeof(value)); \
}
GENERATE_PUSH(uint64_t)
GENERATE_PUSH(uint32_t)
GENERATE_PUSH(uint16_t)
GENERATE_PUSH(uint8_t)
GENERATE_PUSH(int64_t)
GENERATE_PUSH(int32_t)
GENERATE_PUSH(int16_t)
GENERATE_PUSH(int8_t)
#define GENERATE_POP(Type) \
static Type pop_##Type(uint8_t **where) { \
    Type result; \
    memcpy(&result, *where, sizeof(result)); \
    *where += sizeof(result); \
    return (Type)result; \
}
GENERATE_POP(uint64_t)
GENERATE_POP(uint32_t)
GENERATE_POP(uint16_t)
GENERATE_POP(uint8_t)
GENERATE_POP(int64_t)
GENERATE_POP(int32_t)
GENERATE_POP(int16_t)
GENERATE_POP(int8_t)
device_t usbadc_open_device(const char *uri)
{
    device_t handle;
    urpc_device_handle_t device;
    device = urpc_device_create(uri);
    if(device == NULL) {
        return device_undefined;
    }
    {
        std::lock_guard<std::mutex> lock(impl_by_handle_mutex);
        do
        {
            handle = rand();
        }
        while(impl_by_handle.count(handle) != 0);
        impl_by_handle[handle] = device;
    }
    return handle;
}

result_t usbadc_libversion(char *lib_version)
{
    char *lib_v = "0.1.6";
    strcpy(lib_version,lib_v);
    return result_ok;
}

result_t usbadc_get_identity_information(device_t handle, usbadc_get_identity_information_t *output)
{
    uint8_t *p;
    unsigned int i;
    uint8_t out_buffer[72];
    memset(out_buffer, 0, 72);
    urpc_device_handle_t device;
    if(handle < 0)
    {
        return result_error;
    }
    {
        std::lock_guard<std::mutex> lock(impl_by_handle_mutex);
        try
        {
            device = impl_by_handle.at(handle);
        }
        catch(const std::out_of_range &)
        {
            return result_error;
        }
    }
    if(urpc_device_send_request(device, "ginf", NULL, 0, out_buffer, 72) != urpc_result_ok)
    {
        return result_error;
    }
    p = out_buffer;
    for(i=0; i<16; i++) output->Manufacturer[i] = pop_uint8_t(&p);
    for(i=0; i<16; i++) output->ProductName[i] = pop_uint8_t(&p);
    for(i=0; i<16; i++) output->ControllerName[i] = pop_uint8_t(&p);
    output->HardwareMajor = pop_uint8_t(&p);
    output->HardwareMinor = pop_uint8_t(&p);
    output->HardwareBugfix = pop_uint16_t(&p);
    output->BootloaderMajor = pop_uint8_t(&p);
    output->BootloaderMinor = pop_uint8_t(&p);
    output->BootloaderBugfix = pop_uint16_t(&p);
    output->FirmwareMajor = pop_uint8_t(&p);
    output->FirmwareMinor = pop_uint8_t(&p);
    output->FirmwareBugfix = pop_uint16_t(&p);
    output->SerialNumber = pop_uint32_t(&p);
    for(i=0; i<8; i++) output->Reserved[i] = pop_uint8_t(&p);
    return result_ok;
}

result_t usbadc_get_conversion_raw(device_t handle, usbadc_get_conversion_raw_t *output)
{
    uint8_t *p;
    unsigned int i;
    uint8_t out_buffer[32];
    memset(out_buffer, 0, 32);
    urpc_device_handle_t device;
    if(handle < 0)
    {
        return result_error;
    }
    {
        std::lock_guard<std::mutex> lock(impl_by_handle_mutex);
        try
        {
            device = impl_by_handle.at(handle);
        }
        catch(const std::out_of_range &)
        {
            return result_error;
        }
    }
    if(urpc_device_send_request(device, "gdta", NULL, 0, out_buffer, 32) != urpc_result_ok)
    {
        return result_error;
    }
    p = out_buffer;
    for(i=0; i<10; i++) output->data[i] = pop_uint16_t(&p);
    for(i=0; i<12; i++) output->Reserved[i] = pop_uint8_t(&p);
    return result_ok;
}

result_t usbadc_get_conversion(device_t handle, usbadc_get_conversion_t *output)
{
    uint8_t *p;
    unsigned int i;
    uint8_t out_buffer[32];
    memset(out_buffer, 0, 32);
    urpc_device_handle_t device;
    if(handle < 0)
    {
        return result_error;
    }
    {
        std::lock_guard<std::mutex> lock(impl_by_handle_mutex);
        try
        {
            device = impl_by_handle.at(handle);
        }
        catch(const std::out_of_range &)
        {
            return result_error;
        }
    }
    if(urpc_device_send_request(device, "gcon", NULL, 0, out_buffer, 32) != urpc_result_ok)
    {
        return result_error;
    }
    p = out_buffer;
    for(i=0; i<10; i++) output->data[i] = pop_uint16_t(&p);
    for(i=0; i<12; i++) output->Reserved[i] = pop_uint8_t(&p);
    return result_ok;
}

result_t usbadc_close_device(device_t *handle_ptr)
{
    if(handle_ptr == NULL)
    {
        return result_error;
    }
    device_t handle = *handle_ptr;
    if(handle < 0)
    {
        return result_error;
    }
    urpc_device_handle_t device;
    {
        std::lock_guard<std::mutex> lock(impl_by_handle_mutex);
        try
        {
            device = impl_by_handle.at(handle);
        }
        catch(const std::out_of_range &)
        {
            return result_error;
        }
        impl_by_handle.erase(handle);
    }
    if(urpc_device_destroy(&device) != urpc_result_ok)
    {
        return result_error;
    }
    return result_ok;
}

