#include "l502api.h"
#include "e502api.h"
#include "burn_intf.h"

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


typedef enum {
    DEVTYPE_L502,
    DEVTYPE_E502
} t_devtype;

X502_EXPORT(int32_t) X502_FlashSetProtection(t_x502_hnd hnd, uint32_t prot, uint8_t *prot_data, uint32_t prot_data_size);

static int f_intf_open(t_lxfw_burn_opt *opts);
static int f_intf_close(void);
static int f_check_dev(t_lxfw_info_hnd fw);
static int f_check_version(t_lxfw_burn_opt *opts, const char* version);
static int f_flash_set_prot(uint8_t prot);
static int f_flash_erase_64k(uint32_t addr);
static int f_flash_write_start(uint32_t addr, const uint8_t *data, uint32_t size);
static int f_flash_write_next(const uint8_t *data, uint32_t size);
static int f_flash_write_stop(void);
static int f_flash_read(uint32_t addr, uint8_t *data, uint32_t size);
static int f_reload_info(void);
static int f_exec_for_all(t_lxfw_burn_opt* opts, t_exec_cmd_cb cb);
static const char* f_get_err_str(int err_code);

static t_x502_hnd f_hnd = NULL;
static t_devtype f_devtype;
static uint32_t f_addr = 0;

t_burn_intf g_pcie_intf = {
    "pcie",
    f_intf_open,
    f_intf_close,
    f_check_dev,
    f_check_version,
    f_flash_set_prot,
    f_flash_erase_64k,
    f_flash_write_start,
    f_flash_write_next,
    f_flash_write_stop,
    f_flash_read,
    f_reload_info,
    f_exec_for_all,
    f_get_err_str
};



static int f_intf_open(t_lxfw_burn_opt* opts) {
    int err = 0;
    f_hnd = X502_Create();
    if (f_hnd==NULL) {
        err = LXFW_ERR_MEMORY_ALLOC;
    } else {        
        if (!strcmp(opts->devname, "L502")) {
            err = L502_Open(f_hnd, opts->serial);
            f_devtype = DEVTYPE_L502;
        } else if (!strcmp(opts->devname, "E502")) {
            int open_err = E502_OpenUsb(f_hnd, opts->serial);
            if (open_err != X502_ERR_FPGA_NOT_LOADED)
                err = open_err;
            f_devtype = DEVTYPE_E502;
        } else {
            err = LXFW_ERR_UNSUPPORTED_DEVICE;
        }

        if (!err) {
            t_x502_info info;
            err = X502_GetDevInfo(f_hnd, &info);

            if (!err) {
                printf("\n   Название = %s\n   Сер. Номер = %s\n   Версия прошивки = %d.%d\n",
                       info.name, info.serial, (info.fpga_ver>>8)&0xFF, info.fpga_ver&0xFF);
                fflush(stdout);
            }
        }
    }
    return  err;
}

static int f_intf_close(void) {
    if (f_hnd!=NULL) {
        X502_Close(f_hnd);
        X502_Free(f_hnd);
        f_hnd=NULL;
    }
    return 0;
}

static int f_reload_info(void) {
    int err = 0;
    if (f_devtype == DEVTYPE_E502) {
        err = E502_ReloadFPGA(f_hnd);
    }
    return err;
}

static int f_check_dev(t_lxfw_info_hnd fw) {
    t_x502_info info;
    char rev[10];
    int32_t err = X502_GetDevInfo(f_hnd, &info);
    if (!err) {
        sprintf(rev, "%d", info.board_rev);
        if (f_devtype == DEVTYPE_L502) {
            err = firm_check_device(fw, "L502", "Cyclone IV GX", rev);
        } else if (f_devtype == DEVTYPE_E502) {
            err = firm_check_device(fw, "E502", "Cyclone IV", rev);
            if (!err) {
                const char *temp_range = lxfw_get_property(fw, "temp_range");
                const char *req_range = info.devflags & X502_DEVFLAGS_INDUSTRIAL ? "industrial" : "commercial";
                if ((temp_range != NULL) && strcmp(temp_range, req_range)) {
                    err = LXFW_ERR_DEVICE_CHECK;
                }
            }
        }
    }
    return err;
}

static int f_check_version(t_lxfw_burn_opt *opts, const char* version) {
    t_x502_info info;
    int ver_minor, ver_major, ver;
    int32_t err = X502_GetDevInfo(f_hnd, &info);
    if (!err) {
        sscanf(version, "%d.%d", &ver_major, &ver_minor);
        ver = (ver_major<<8)|ver_minor;
        if (ver < info.fpga_ver) {
            err = LXFW_ERR_FIRM_OLD_VERSION;
        }
    }
    return err;
}

static int f_flash_set_prot(uint8_t prot) {
    uint16_t prot_data = 0xA502;
    return X502_FlashSetProtection(f_hnd, prot, (uint8_t*)&prot_data, sizeof(prot_data));
}

static int f_flash_erase_64k(uint32_t addr) {
    return X502_FlashErase(f_hnd, addr, 64*1024);
}

static int f_flash_write_start(uint32_t addr, const uint8_t* data, uint32_t size) {
    f_addr = addr+size;
    return X502_FlashWrite(f_hnd, addr, data, size);
}

static int f_flash_write_next(const uint8_t* data, uint32_t size) {
    int err = X502_FlashWrite(f_hnd, f_addr, data, size);
    if (!err)
        f_addr+=size;
    return err;
}

static int f_flash_write_stop(void) {
    return 0;
}

static int f_flash_read(uint32_t addr, uint8_t *data, uint32_t size) {
    return X502_FlashRead(f_hnd, addr, data, size);
}

static const char* f_get_err_str(int err_code) {
    return X502_GetErrorString(err_code);
}

typedef int32_t (APIENTRY *t_x502_get_serials)(char serials[][X502_SERIAL_SIZE], uint32_t size,
                                               uint32_t flags, uint32_t *devcnt);

static int f_exec_for_all(t_lxfw_burn_opt* opts, t_exec_cmd_cb cb) {
    uint32_t dev_cnt;
    int32_t res;
    t_x502_get_serials get_serials = NULL;

    if (!strcmp(opts->devname, "L502")) {
        get_serials = L502_GetSerialList;
    } else if (!strcmp(opts->devname, "E502")) {
        get_serials = E502_UsbGetSerialList;
    }

    if (get_serials != NULL) {
        /* Получаем количество модулей в системе */
        res = get_serials(NULL, 0, 0, &dev_cnt);
    } else {
        res = LXFW_ERR_UNSUPPORTED_DEVICE;
    }

    if (res>=0) {
        if (dev_cnt != 0) {
           /* Выделяем плоский массив под dev_cnt серийных номеров размером
              dev_cnt*X502_SERIAL_SIZE */
           char (*serial_list)[X502_SERIAL_SIZE] =
                (char(*)[X502_SERIAL_SIZE]) malloc
                (dev_cnt*X502_SERIAL_SIZE);
           if (serial_list==NULL) {
                res = X502_ERR_MEMORY_ALLOC;
           } else {
                res = get_serials(serial_list, dev_cnt, 0, NULL);
                if (res>0) {
                    int32_t i, err = 0;
                    /* Получено res серийных номеров */
                    for (i=0; !err && (i < res); i++) {
                        opts->serial = serial_list[i];
                        err = cb(opts);
                    }
                }
                /* Освобождаем выделенный массив под серийные номера */
                free(serial_list);
           }
        }
    } else {
        res = LXFW_ERR_GET_DEVLIST;
        fprintf(stderr, "Не удалось получить список устройств");
    }

    if (res == 0) {
        res = LXFW_ERR_NO_DEVICE_FOUND;
        fprintf(stderr, "Не обнаружено ни одного устройства, для которого подходит прошивка");
    }


    return res;
}






