#include "ltrt13api.h"
#include "ltrmodule.h"
#include "lbitfield.h"
#include <stdlib.h>
#include <string.h>
#include "crc.h"
#include "ltrmodule_at93c86a.h"
#include "ltrmodule_flash_geninfo.h"


#if (LTRT13_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_EX_SIZE) || (LTRT13_NAME_SIZE != LTR_FLASH_GENINFO_NAME_EX_SIZE)
    #error module info len mismatch
#endif

#define LTRT13_SYNC_FREQ_IN  10000000.


#define CMD_EXT_SYNC_CTL                (LTR010CMD_INSTR | 0x01)
#define CMD_MARK_EN                     (LTR010CMD_INSTR | 0x02)
#define RESP_EXT_SYNC_CTL               (LTR010CMD_INSTR | 0x01)

#define CMD_BIT_MARK_EN                 (0x1UL << 0)
#define CMD_BIT_EXT_SYNC_START          (0x1UL << 0)
#define CMD_BITMSK_EXT_SYNC_FREQ_DIV    (0xFFUL << 8)


#define LTRT13_FLASH_INFO_FORMAT         1
#define LTRT13_FLASH_INFO_ADDR           0
#define LTRT13_FLASH_INFO_SIZE_MIN       sizeof(t_ltrt13_flash_info)
#define LTRT13_FLASH_INFO_SIZE_MAX       1024

typedef struct {
    ULONGLONG cbrtime;
    double rvalue;
} t_ltrt13_cbr_info;

typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module_ex module;
    t_ltrt13_cbr_info cbr;
} t_ltrt13_flash_info;




static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    { LTRT13_ERR_MARK_GEN_ENABLED, "Операция недопустима при разрешенной генерации меток"},
    { LTRT13_ERR_EXT_SYNC_FREQ_DIV, "Неверное значение делителя частоты для сигнала SYNC"}
};

static double f_get_sync_freq(DWORD div) {
    return LTRT13_SYNC_FREQ_IN / (div * 8 + 1);
}

static INT f_check_nonmark_cmd(TLTRT13 *hnd) {
    INT err = LTRT13_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.MarkGenEn)
        err = LTRT13_ERR_MARK_GEN_ENABLED;
    return err;
}

static INT f_set_ext_ctl(TLTRT13 *hnd, BOOLEAN start_en, BOOLEAN sync_en, DWORD sync_div) {
    INT err;
    DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_EXT_SYNC_CTL,
                        LBITFIELD_SET(CMD_BIT_EXT_SYNC_START, start_en ? 1 : 0) |
                        LBITFIELD_SET(CMD_BITMSK_EXT_SYNC_FREQ_DIV, sync_en ? sync_div : 0));
    DWORD resp;
    err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &resp);
    if (err == LTR_OK) {
        hnd->State.ExtStartLevel = start_en;
        hnd->State.ExtSyncGenEn = sync_en;
        if (sync_en && (hnd->State.ExtSyncFreqDiv != sync_div)) {
            hnd->State.ExtSyncFreqDiv = sync_div;
            hnd->State.ExtSyncFreq = f_get_sync_freq(sync_div);
        }
    }
    return err;
}

static void f_info_init(TLTRT13 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTRT13");    
    hnd->ModuleInfo.RValue = LTRT13_RVAL_NOM;

    memset(&hnd->State, 0, sizeof(TLTRT13_STATE));
}

LTRT13API_DllExport(INT) LTRT13_Init(TLTRT13 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err==LTR_OK){
        memset(hnd,0, sizeof(TLTRT13));
        hnd->size = sizeof(TLTRT13);
        f_info_init(hnd);
        hnd->Cfg.ExtSyncFreqDiv = LTRT13_EXT_SYNC_FREQ_DIV_MIN;
        err=LTR_Init(&hnd->Channel);
    }
    return err;
}




LTRT13API_DllExport(INT) LTRT13_Open(TLTRT13 *hnd, DWORD net_addr, WORD net_port,
                                     const CHAR *csn, WORD slot) {

    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    INT rdcfg_err = LTR_OK;
    BYTE ver_pld;

    if (err == LTR_OK) {
        err = LTR01_Open(&hnd->Channel, net_addr, net_port, csn, slot,
                         LTR01_SUBID_LTRT13, &ver_pld, NULL);
    }

    if (err == LTR_OK) {
        f_info_init(hnd);
        hnd->ModuleInfo.VerPLD = ver_pld;

        rdcfg_err = LTRT13_GetConfig(hnd);
    }

    /* по ошибкам read config не закрываем соединение */
    if (err != LTR_OK)
        LTRT13_Close(hnd);


    return err == LTR_OK ? rdcfg_err : err;
}


LTRT13API_DllExport(INT) LTRT13_Close(TLTRT13 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err=LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRT13API_DllExport(INT) LTRT13_IsOpened(TLTRT13 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}



LTRT13API_DllExport(INT) LTRT13_GetConfig(TLTRT13 *hnd) {
    INT err = LTRT13_IsOpened(hnd);
    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        err = ltr_at93c86a_read(&hnd->Channel, LTRT13_FLASH_INFO_ADDR, (BYTE*)&hdr, sizeof(hdr));
        if ((err == LTR_OK) && (hdr.sign != LTR_FLASH_GENINFO_SIGN))
            err = LTR_ERROR_FLASH_INFO_NOT_PRESENT;
        if ((err == LTR_OK) && (hdr.format != LTRT13_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && ((hdr.size < LTRT13_FLASH_INFO_SIZE_MIN) || (hdr.size > LTRT13_FLASH_INFO_SIZE_MAX)))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltrt13_flash_info *pinfo = malloc(hdr.size+ LTR_FLASH_GENINFO_CRC_SIZE);
            if (pinfo == NULL)
                err = LTR_ERROR_MEMORY_ALLOC;
            /* читаем оставшуюся информацию */
            if (err == LTR_OK) {
                memcpy(pinfo, &hdr, sizeof(hdr));
                err = ltr_at93c86a_read(&hnd->Channel,
                                LTRT13_FLASH_INFO_ADDR+sizeof(hdr), ((BYTE *)pinfo) + sizeof(hdr),
                    (WORD)(hdr.size + LTR_FLASH_GENINFO_CRC_SIZE-sizeof(hdr)));
            }

            /* рассчитываем CRC и сверяем со считанной из памяти */
            if (err == LTR_OK) {
                WORD crc, crc_ver;
                crc_ver = eval_crc16(0, (unsigned char *)pinfo, hdr.size);
                crc = (WORD)(((BYTE *)pinfo)[hdr.size] | (((WORD)((BYTE *)pinfo)[hdr.size+1]) << 8));
                if (crc != crc_ver)
                    err = LTR_ERROR_FLASH_INFO_CRC;
            }


            if (err == LTR_OK) {
                memcpy(hnd->ModuleInfo.Name, pinfo->module.name, LTRT13_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTRT13_SERIAL_SIZE);
                hnd->ModuleInfo.RValue = pinfo->cbr.rvalue;
                hnd->ModuleInfo.CbrTime = pinfo->cbr.cbrtime;
            }
            free(pinfo);
        }
    }
    return err;
}


LTRT13API_DllExport(INT) LTRT13_WriteInfo(TLTRT13 *hnd) {
    INT err = LTRT13_IsOpened(hnd);
    if (err == LTR_OK) {
        struct {
            t_ltrt13_flash_info info;
            WORD crc;
        } wrinfo, rdinfo;
        const size_t wr_size = sizeof(t_ltrt13_flash_info) + LTR_FLASH_GENINFO_CRC_SIZE;

        wrinfo.info.hdr.sign = LTR_FLASH_GENINFO_SIGN;
        wrinfo.info.hdr.format = LTRT13_FLASH_INFO_FORMAT;
        wrinfo.info.hdr.flags = 0;
        wrinfo.info.hdr.size = sizeof(t_ltrt13_flash_info);

        memcpy(wrinfo.info.module.name,   hnd->ModuleInfo.Name,   LTRT13_NAME_SIZE);
        memcpy(wrinfo.info.module.serial, hnd->ModuleInfo.Serial, LTRT13_SERIAL_SIZE);

        wrinfo.info.cbr.rvalue = hnd->ModuleInfo.RValue;
        wrinfo.info.cbr.cbrtime = hnd->ModuleInfo.CbrTime;

        wrinfo.crc = eval_crc16(0, (BYTE *)&wrinfo.info, sizeof(t_ltrt13_flash_info));

        err = ltr_at93c86a_write(&hnd->Channel, LTRT13_FLASH_INFO_ADDR, (const BYTE*)&wrinfo, (WORD)wr_size);
        if (err == LTR_OK) {
            err = ltr_at93c86a_read(&hnd->Channel, LTRT13_FLASH_INFO_ADDR, (BYTE*)&rdinfo, (WORD)wr_size);
        }

        if ((err == LTR_OK) && memcmp(&wrinfo, &rdinfo, wr_size)) {
            err = LTR_ERROR_FLASH_VERIFY;
        }
    }
    return err;
}



LTRT13API_DllExport(INT) LTRT13_FindExtSyncFreqParams(double freq, DWORD *div, double *resultFreq) {
    DWORD fnd_div;
    if (freq > LTRT13_EXT_SYNC_FREQ_MAX) {
        fnd_div = LTRT13_EXT_SYNC_FREQ_DIV_MIN;
    } else if (freq > 0) {
        INT calc_div  = (INT)((LTRT13_SYNC_FREQ_IN/freq - 1) / 8 + 0.5);
        if (calc_div > LTRT13_EXT_SYNC_FREQ_DIV_MAX)
            calc_div = LTRT13_EXT_SYNC_FREQ_DIV_MAX;
        if (calc_div < LTRT13_EXT_SYNC_FREQ_DIV_MIN)
            calc_div = LTRT13_EXT_SYNC_FREQ_DIV_MIN;
        fnd_div = (DWORD)calc_div;
    } else {
        fnd_div = LTRT13_EXT_SYNC_FREQ_DIV_MAX;
    }

    if (div)
        *div = fnd_div;
    if (resultFreq) {
        *resultFreq = f_get_sync_freq(fnd_div);
    }
    return LTR_OK;
}

LTRT13API_DllExport(INT) LTRT13_FillExtSyncFreqParams(TLTRT13 *hnd, double freq, double *resultFreq) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
    if (err == LTR_OK) {
        err = LTRT13_FindExtSyncFreqParams(freq, &hnd->Cfg.ExtSyncFreqDiv, resultFreq);
    }
    return err;
}

LTRT13API_DllExport(INT) LTRT13_SetExtStartLevel(TLTRT13 *hnd, BOOLEAN lvl) {
    INT err = f_check_nonmark_cmd(hnd);
    if (err == LTR_OK)  {
        f_set_ext_ctl(hnd, lvl, hnd->State.ExtSyncGenEn, hnd->State.ExtSyncFreqDiv);
    }
    return err;
}

LTRT13API_DllExport(INT) LTRT13_SetExtSyncGenEnabled(TLTRT13 *hnd, BOOLEAN en) {
    INT err = f_check_nonmark_cmd(hnd);
    if (err == LTR_OK) {
        if (en && ((hnd->Cfg.ExtSyncFreqDiv < LTRT13_EXT_SYNC_FREQ_DIV_MIN)
                || (hnd->Cfg.ExtSyncFreqDiv > LTRT13_EXT_SYNC_FREQ_DIV_MAX))) {
            err = LTRT13_ERR_EXT_SYNC_FREQ_DIV;
        }
    }

    if (err == LTR_OK) {
        f_set_ext_ctl(hnd, hnd->State.ExtStartLevel, en, hnd->Cfg.ExtSyncFreqDiv);
    }
    return err;
}

LTRT13API_DllExport(INT) LTRT13_SetMarksGenEnabled(TLTRT13 *hnd, BOOLEAN en) {
    INT err = LTRT13_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_MARK_EN, LBITFIELD_SET(CMD_BIT_MARK_EN, en ? 1 : 0));
        err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
        if (err == LTR_OK) {
            hnd->State.MarkGenEn = en;
        }
    }
    return err;
}


LTRT13API_DllExport(LPCSTR) LTRT13_GetErrorString(INT err) {
    size_t i;
    for (i = 0; i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0]); i++) {
        if (f_err_tbl[i].code == err)
            return f_err_tbl[i].message;
    }
    return LTR_GetErrorString(err);
}



