#include "x502api_private.h"
#include "x502_eeprom.h"
#include "fast_crc.h"
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include "../devs/e502/e502_fpga_regs.h"

#define X502_CHECK_ADDR(hnd, addr, size) ((addr+size)>hnd->info.flash_size ? \
    X502_ERR_FLASH_INVALID_ADDR : (size==0) ? X502_ERR_FLASH_INVALID_SIZE : X502_ERR_OK)


X502_EXPORT(int32_t) X502_FlashRead(t_x502_hnd hnd, uint32_t addr, uint8_t* data,
                                    uint32_t size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = X502_CHECK_ADDR(hnd, addr, size);
    }
    if (err==X502_ERR_OK) {

        for ( ; (size!=0) && (err==X502_ERR_OK); ) {
            uint32_t rd_size = size;
            if (rd_size > hnd->iface_hnd->flash_rd_size)
                rd_size = hnd->iface_hnd->flash_rd_size;

            err = hnd->iface_hnd->flash_rd(hnd, addr, data, rd_size);
            if (err==X502_ERR_OK) {
                data+=rd_size;
                addr+=rd_size;
                size-=rd_size;
            }
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_FlashWrite(t_x502_hnd hnd, uint32_t addr,
                                     const uint8_t* data, uint32_t size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = X502_CHECK_ADDR(hnd, addr, size);
    }
    if (err==X502_ERR_OK) {
        for ( ; (size!=0) && (err==X502_ERR_OK); ) {
            uint32_t wr_size = size;
            if (wr_size > hnd->iface_hnd->flash_wr_size)
                wr_size = hnd->iface_hnd->flash_wr_size;

            err = hnd->iface_hnd->flash_wr(hnd, addr, data, wr_size);
            if (err==X502_ERR_OK) {
                data+=wr_size;
                addr+=wr_size;
                size-=wr_size;
            }
        }
    }
    return err;
}

X502_EXPORT(int32_t) X502_FlashErase(t_x502_hnd hnd, uint32_t addr, uint32_t size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = X502_CHECK_ADDR(hnd, addr, size);
    }
    if (err==X502_ERR_OK) {
        err = hnd->iface_hnd->flash_erase(hnd, addr, size);
    }
    return err;
}

X502_EXPORT(int32_t) X502_FlashWriteEnable(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = hnd->iface_hnd->flash_set_prot(hnd, X502_EEPROM_PROT_WR_USER, NULL, 0);
    }
    return err;
}

X502_EXPORT(int32_t) X502_FlashWriteDisable(t_x502_hnd hnd) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = hnd->iface_hnd->flash_set_prot(hnd, X502_EEPROM_PROT_ALL, NULL, 0);
    }
    return err;
}


X502_EXPORT(int32_t) X502_FlashSetProtection(t_x502_hnd hnd, uint32_t prot, uint8_t *prot_data, uint32_t prot_data_size) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if (err==X502_ERR_OK) {
        err = hnd->iface_hnd->flash_set_prot(hnd, prot, prot_data, prot_data_size);
    }
    return err;
}

bool x502_is_E16(t_x502_hnd hnd) {
    if (strcmp(hnd->info.name, E16_DEVICE_NAME) == 0) {
        return true;
    } else {
        return false;
    }
}

/** Функция проверяет правильность информации о устройстве, записанной в EEPROM.
    При наличии верной инофрмации, из EEPROM считывается название устройства и
    серийный номер, а так же, при наличии, калибровочные коэффициенты */
int x502_check_eeprom(t_x502_hnd hnd, uint32_t flags) {
    int err;
    uint32_t sign, size;

    if (x502_is_E16(hnd)) {
        // в E16 добавился новый регистр и мы можем узнать размер flash-памяти
        uint32_t flash_size;
        err =  hnd->iface_hnd->fpga_reg_read(hnd, E502_REGS_ARM_FLASHSIZE, &flash_size);
        if (err != X502_ERR_OK) {
            hnd->info.flash_size = 0;
            return err;
        }
        hnd->info.flash_size = flash_size;
    } else {
        hnd->info.flash_size = X502_EEPROM_SIZE;
    }

    hnd->info.devflags &= ~(X502_DEVFLAGS_FLASH_DATA_VALID |
                            ((flags & X502_RELOAD_FLAGS_NO_ADC) ? 0 : X502_DEVFLAGS_FLASH_ADC_CALIBR_VALID) |
                            ((flags & X502_RELOAD_FLAGS_NO_DAC) ? 0 : X502_DEVFLAGS_FLASH_DAC_CALIBR_VALID));

    /* проверяем признак правильного описателя в EEPROM и его размер */
    err = X502_FlashRead(hnd, X502_EEPROM_ADDR_DESCR, (uint8_t*)&sign, (uint32_t)sizeof(sign));
    if (err == X502_ERR_OK)
        err = X502_FlashRead(hnd, X502_EEPROM_ADDR_DESCR+sizeof(sign), (uint8_t*)&size, (uint32_t)sizeof(size));

    if (err == X502_ERR_OK) {
        if ((sign == X502_EEPROM_SIGN) && (size >= X502_DESCR_MIN_SIZE) && (size <= X502_DESCR_MAX_SIZE)) {
            t_x502_descr* pdescr = (t_x502_descr*)malloc(size);
            /* читаем весь описатель */
            if (pdescr!=NULL) {
                err = X502_FlashRead(hnd, X502_EEPROM_ADDR_DESCR, (uint8_t*)pdescr, size);
            } else {
                err = X502_ERR_MEMORY_ALLOC;
            }

            /* сверяем crc */
            if (err == X502_ERR_OK) {
                uint32_t crc, crc2;
                crc = CRC32_Block8(0, (uint8_t*)pdescr, (uint32_t)(size-sizeof(crc)));
                memcpy(&crc2, &((uint8_t*)pdescr)[size-4], 4);
                if (crc == crc2) {

                    hnd->info.devflags |= X502_DEVFLAGS_FLASH_DATA_VALID;
                    memcpy(hnd->info.serial, pdescr->hdr.serial, sizeof(pdescr->hdr.serial));
                    memcpy(hnd->info.name, pdescr->hdr.name, sizeof(pdescr->hdr.name));
                    memcpy(hnd->info.factory_mac, pdescr->hdr.factory_mac, sizeof(pdescr->hdr.factory_mac));

                    if (!(flags & X502_RELOAD_FLAGS_NO_ADC)) {
                        if ((pdescr->cbr_adc.hdr.cbr_sign == X502_EEPROM_CBR_SIGN) &&
                                (pdescr->cbr_adc.hdr.format == X502_EEPROM_CBR_FROMAT) &&
                                (pdescr->cbr_adc.hdr.src == X502_EEPROM_CBR_SRC_ADC) &&
                                (pdescr->cbr_adc.hdr.range_cnt == X502_ADC_RANGE_CNT || pdescr->cbr_adc.hdr.range_cnt == E16_ADC_RANGE_CNT) &&
                                (pdescr->cbr_adc.hdr.channel_cnt == 1)) {
                            unsigned int i;

                            hnd->info.devflags |= X502_DEVFLAGS_FLASH_ADC_CALIBR_VALID;
                            for (i=0; (i < pdescr->cbr_adc.hdr.range_cnt) && (err == X502_ERR_OK); i++) {
                                if (x502_is_E16(hnd)) {
                                    if (i > E16_ADC_RANGE_CNT) {
                                        break;
                                    }
                                } else {
                                    if (i > X502_ADC_RANGE_CNT) {
                                        break;
                                    }
                                }
                                err = X502_SetAdcCoef(hnd, i, pdescr->cbr_adc.coefs[i].k,
                                                        pdescr->cbr_adc.coefs[i].offs);
                            }
                        }
                    }

                    if (!(flags & X502_RELOAD_FLAGS_NO_DAC)) {
                        if ((pdescr->cbr_dac.hdr.cbr_sign == X502_EEPROM_CBR_SIGN) &&
                                (pdescr->cbr_dac.hdr.format == X502_EEPROM_CBR_FROMAT) &&
                                (pdescr->cbr_dac.hdr.src == X502_EEPROM_CBR_SRC_DAC) &&
                                (pdescr->cbr_dac.hdr.range_cnt == 1) &&
                                (pdescr->cbr_dac.hdr.channel_cnt == X502_DAC_CH_CNT)) {
                            unsigned int i;

                            hnd->info.devflags |= X502_DEVFLAGS_FLASH_DAC_CALIBR_VALID;
                            for (i=0; (i < pdescr->cbr_dac.hdr.channel_cnt) &&
                                 (i < X502_ADC_RANGE_CNT) && (err == X502_ERR_OK); i++) {
                                err = X502_SetDacCoef(hnd, i, pdescr->cbr_dac.coefs[i].k,
                                                        pdescr->cbr_dac.coefs[i].offs);
                            }
                        }
                    }
                }
            }
            free(pdescr);
        }
    }
    return err;
}


X502_EXPORT(int32_t) X502_ReloadDevInfo(t_x502_hnd hnd, uint32_t flags) {
    int32_t err = X502_CHECK_HND_OPENED(hnd);
    if ((err==X502_ERR_OK) && (hnd->iface_hnd->reload_dev_info!=NULL))
        err = hnd->iface_hnd->reload_dev_info(hnd);
    if (err==X502_ERR_OK)
        err = x502_check_eeprom(hnd, flags);
    return err;
}
