#include "ltrk415api.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"

#define LTRK415_OUT_CH_MSK ((1 << LTRK415_OUT_CH_CNT) - 1)

#if (LTRK415_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_EX_SIZE) || (LTRK415_NAME_SIZE != LTR_FLASH_GENINFO_NAME_EX_SIZE)
    #error module info len mismatch
#endif


#define CMD_SET_OUT_CH1_SW_K             (LTR010CMD_INSTR | 0x01)
#define CMD_SET_OUT_CH1_SW_N             (LTR010CMD_INSTR | 0x02)
#define CMD_SET_OUT_CH2_SW_K             (LTR010CMD_INSTR | 0x03)
#define CMD_SET_OUT_CH2_SW_N             (LTR010CMD_INSTR | 0x04)
#define CMD_SET_OUT_BUF_WR_EN            (LTR010CMD_INSTR | 0x05)
#define CMD_SET_OUT_CFG                  (LTR010CMD_INSTR | 0x06)
#define CMD_SET_IN_RX_EN                 (LTR010CMD_INSTR | 0x07)


#define CMDBIT_SET_OUT_CFG_BUF(ch)       (1 << (0 + (ch)))
#define CMDBIT_SET_OUT_CFG_WREQ(ch)      (1 << (2 + (ch)))
#define CMDBIT_SET_OUT_CFG_INV(ch)       (1 << (4 + (ch)))


#define CMD_SET_OUT_CHx_SW_K(ch)         (ch == 1 ? CMD_SET_OUT_CH2_SW_K : CMD_SET_OUT_CH1_SW_K)
#define CMD_SET_OUT_CHx_SW_N(ch)         (ch == 1 ? CMD_SET_OUT_CH2_SW_N : CMD_SET_OUT_CH1_SW_N)

#define LTRK415_FLASH_INFO_FORMAT         1
#define LTRK415_FLASH_INFO_ADDR           0
#define LTRK415_FLASH_INFO_SIZE_MIN       sizeof(t_ltrk415_flash_info)
#define LTRK415_FLASH_INFO_SIZE_MAX       1024

LTRK415API_DllExport(INT) LTRK415_ReadInfo(TLTRK415 *hnd);

typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module_ex module;
} t_ltrk415_flash_info;

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTRK415_ERR_INVALID_SW_STATE,      "Задано неверное состояние выходного ключа"},
    {LTRK415_ERR_INVALID_CH_NUM,        "Указан неверный номер канала"},
    {LTRK415_ERR_INVALID_CH_MSK,        "Неверна задана группа каналов"},
    {LTRK415_ERR_INVALID_OUT_CH_SW_NUM, "Указан неверный номер выходного ключа канала"},
    {LTRK415_ERR_INVALID_OUT_CH_SRC,    "Неверный код источника данных для канала"},
    {LTRK415_ERR_INSUF_OUT_BUF_SIZE,    "Превышен максимальный размер буфера вывода"}
};

static void f_info_init(TLTRK415 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTRK415");
    memset(&hnd->State, 0, sizeof(hnd->State));
}



LTRK415API_DllExport(INT) LTRK415_Init(TLTRK415 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK){
        memset(hnd,0, sizeof(TLTRK415));
        hnd->size = sizeof(TLTRK415);
        f_info_init(hnd);
        err=LTR_Init(&hnd->Channel);
    }
    return err;
}


LTRK415API_DllExport(INT) LTRK415_Open(TLTRK415 *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 pld_ver;
    if (err == LTR_OK) {
        err = LTR01_Open(&hnd->Channel, net_addr, net_port, csn, slot,
                         LTR01_SUBID_LTRK415, &pld_ver, NULL);
    }

    if (err == LTR_OK) {
        f_info_init(hnd);
        hnd->ModuleInfo.VerPLD = pld_ver;
        rdcfg_err = LTRK415_ReadInfo(hnd);
    }

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


    return err == LTR_OK ? rdcfg_err : err;
}


LTRK415API_DllExport(INT) LTRK415_Close(TLTRK415 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRK415API_DllExport(INT) LTRK415_IsOpened(TLTRK415 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}



LTRK415API_DllExport(INT) LTRK415_ReadInfo(TLTRK415 *hnd) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        err = ltr_at93c86a_read(&hnd->Channel, LTRK415_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 != LTRK415_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && ((hdr.size < LTRK415_FLASH_INFO_SIZE_MIN) || (hdr.size > LTRK415_FLASH_INFO_SIZE_MAX)))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltrk415_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,
                                LTRK415_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, LTRK415_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTRK415_SERIAL_SIZE);
            }
            free(pinfo);
        }
    }
    return err;
}


LTRK415API_DllExport(INT) LTRK415_WriteInfo(TLTRK415 *hnd) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        struct {
            t_ltrk415_flash_info info;
            WORD crc;
        } wrinfo, rdinfo;
        const size_t wr_size = sizeof(t_ltrk415_flash_info) + LTR_FLASH_GENINFO_CRC_SIZE;

        wrinfo.info.hdr.sign = LTR_FLASH_GENINFO_SIGN;
        wrinfo.info.hdr.format = LTRK415_FLASH_INFO_FORMAT;
        wrinfo.info.hdr.flags = 0;
        wrinfo.info.hdr.size = sizeof(t_ltrk415_flash_info);

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

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

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

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



LTRK415API_DllExport(INT) LTRK415_SetOutSwState(TLTRK415 *hnd, BYTE ch_num, BYTE sw_num, BYTE sw_state) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (ch_num >= LTRK415_OUT_CH_CNT)) {
        err = LTRK415_ERR_INVALID_CH_NUM;
    }
    if ((err == LTR_OK) && (sw_num >= LTRK415_OUT_CH_SW_CNT)) {
        err = LTRK415_ERR_INVALID_OUT_CH_SW_NUM;
    }
    if (err == LTR_OK) {
        BYTE sw_states[LTRK415_OUT_CH_SW_CNT];
        memcpy(sw_states, &hnd->State.Ch[ch_num].Sw[0], sizeof(sw_states));
        sw_states[sw_num] = sw_state;
        err = LTRK415_SetOutChSwStates(hnd, ch_num, sw_states);
    }
    return err;
}

LTRK415API_DllExport(INT) LTRK415_SetOutChSwStates(TLTRK415 *hnd, BYTE ch_num, const BYTE *sw_states) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (ch_num >= LTRK415_OUT_CH_CNT)) {
        err = LTRK415_ERR_INVALID_CH_NUM;
    }

    if (err == LTR_OK) {

        BYTE sw_num;
        WORD k_vals = 0, n_vals = 0;
        for (sw_num = 0; (sw_num < LTRK415_OUT_CH_SW_CNT) && (err == LTR_OK); ++sw_num) {
            const BYTE state = sw_states[sw_num];
            if (state == LTRK415_OUT_SW_STATE_ON) {
                n_vals |= 1 << sw_num;
            } else if (state == LTRK415_OUT_SW_STATE_ON_INV) {
                k_vals |= 1 << sw_num;
            } else if (state != LTRK415_OUT_SW_STATE_OFF) {
                err = LTRK415_ERR_INVALID_SW_STATE;
            }
        }

        if (err == LTR_OK) {
            DWORD cmds[2], acks[2];
            cmds[0] = LTR_MODULE_MAKE_CMD(CMD_SET_OUT_CHx_SW_K(ch_num), k_vals);
            cmds[1] = LTR_MODULE_MAKE_CMD(CMD_SET_OUT_CHx_SW_N(ch_num), n_vals);
            err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, 2, acks);
        }

        if (err == LTR_OK) {
            memcpy(&hnd->State.Ch[sw_num].Sw, sw_states, sizeof(hnd->State.Ch[sw_num].Sw));
        }
    }
    return err;
}

LTRK415API_DllExport(INT) LTRK415_Configure(TLTRK415 *hnd) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        WORD cmd_data = 0;
        BYTE ch_num;

        for (ch_num = 0; (ch_num < LTRK415_OUT_CH_CNT) && (err == LTR_OK); ++ch_num) {
            const TLTRK415_CH_CONFIG *chCfg = &hnd->Cfg.Out.Ch[ch_num];
            if (chCfg->Source == LTRK415_CH_SRC_CBUF) {
                cmd_data |= CMDBIT_SET_OUT_CFG_BUF(ch_num);
            } else if (chCfg->Source != LTRK415_CH_SRC_DIRECT) {
                err = LTRK415_ERR_INVALID_OUT_CH_SRC;
            }

            if (err == LTR_OK) {
                if (chCfg->ActiveWithoutReq) {
                    cmd_data |= CMDBIT_SET_OUT_CFG_WREQ(ch_num);
                }

                if (chCfg->InvertedData) {
                    cmd_data |= CMDBIT_SET_OUT_CFG_INV(ch_num);
                }
            }
        }

        if (err == LTR_OK) {
            err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_SET_OUT_CFG, cmd_data));
        }
    }
    return err;
}

LTRK415API_DllExport(INT) LTRK415_LoadOutBuf(TLTRK415 *hnd, BYTE ch_num, const WORD *data, WORD size) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (ch_num >= LTRK415_OUT_CH_CNT)) {
        err = LTRK415_ERR_INVALID_CH_NUM;
    }
    if ((err == LTR_OK) && (size > LTRK415_OUT_CH_CBUF_SIZE)) {
        err = LTRK415_ERR_INSUF_OUT_BUF_SIZE;
    }
    if (err == LTR_OK) {
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_SET_OUT_BUF_WR_EN, 1 << ch_num));
    }

    if (err == LTR_OK) {
        DWORD *data_list = malloc(size * sizeof(DWORD));
        DWORD *ack_list = malloc(size * sizeof(DWORD));

        if ((data_list == NULL) || (ack_list == NULL)) {
            err = LTR_ERROR_MEMORY_ALLOC;
        } else {
            WORD i;
            for (i = 0; i < size; ++i) {
                WORD d = (((i >> 8) & 1) << 15) | data[i];
                data_list[i] = (i & 0xFF) | (d << 16);
            }
            err = ltr_module_send_with_echo_resps(&hnd->Channel, data_list, size, ack_list);
        }
        free(data_list);
        free(ack_list);

        INT dis_err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_SET_OUT_BUF_WR_EN, 0));
        if (err == LTR_OK) {
            err = dis_err;
        }
    }


    return err;
}

LTRK415API_DllExport(INT) LTRK415_StartAcq(TLTRK415 *hnd, BYTE ch_msk) {
    INT err = LTRK415_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.AcqActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (((ch_msk & LTRK415_OUT_CH_MSK) == 0) || ((ch_msk & ~LTRK415_OUT_CH_MSK) != 0))) {
        err = LTRK415_ERR_INVALID_CH_MSK;
    }

    if (err == LTR_OK) {
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_SET_IN_RX_EN, ch_msk));
    }

    if (err == LTR_OK) {
        hnd->State.AcqActive = TRUE;
    }

    return err;
}


LTRK415API_DllExport(INT) LTRK415_StopAcq(TLTRK415 *hnd) {
    INT err = LTRK415_IsOpened(hnd);
    if (err == LTR_OK) {
        const DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_SET_IN_RX_EN, 0);
        const DWORD ack = cmd;

        err = ltr_module_stop(&hnd->Channel, &cmd, 1, ack, 0, 0, NULL);

        hnd->State.AcqActive = FALSE;
    }

    return err;
}

LTRK415API_DllExport(INT) LTRK415_Recv(TLTRK415 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    int res = LTRK415_IsOpened(hnd);
    if (res == LTR_OK)
        res = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);

    if ((res >= 0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF))
        res = LTR_ERROR_RECV_OVERFLOW;

    return res;
}

LTRK415API_DllExport(INT) LTRK415_ProcessData(TLTRK415 *hnd, const DWORD *src_data, TLTRK415_ACQ_DATA *dst_data, DWORD size) {
    DWORD i;
    for (i = 0; i < size; ++i) {
        const WORD dwrd = LTR_MODULE_CMD_GET_DATA(src_data[i]);
        const BYTE info = src_data[i] & 0xFF;

        TLTRK415_ACQ_DATA *res_data = &dst_data[i];
        res_data->ChNum = (info >> 4) & 0xF;
        res_data->Type = (info >> 0) & 0xF;


        res_data->Cntr.Full = dwrd;
        const DWORD ns_mul = (res_data->Type == LTRK415_ACQ_TYPE_PHASE_PERIOD) ? 5000 : 50;
        if ((res_data->Type == LTRK415_ACQ_TYPE_REQ_PERIOD) ||
                (res_data->Type == LTRK415_ACQ_TYPE_PHASE_PERIOD) ||
                (res_data->Type == LTRK415_ACQ_TYPE_REQ2_DELAY)) {
            res_data->TimeNS = ns_mul * dwrd;
        } else {
            const BYTE p = (dwrd >> 8) & 0xFF;
            const BYTE d = (dwrd >> 0) & 0xFF;

            res_data->TimeNS    = ns_mul * p;
            res_data->TimeNS_D  = d == 0xFF ? 0xFFFFFFFF : ns_mul * d;
        }
    }
    return LTR_OK;
}


LTRK415API_DllExport(LPCSTR) LTRK415_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);
}





