#include "ltrk511api.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 (LTRK511_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_EX_SIZE) || (LTRK511_NAME_SIZE != LTR_FLASH_GENINFO_NAME_EX_SIZE)
    #error module info len mismatch
#endif


#define CMD_SET_OUT_SW_0_7              (LTR010CMD_INSTR | 0x01)
#define CMD_SET_OUT_SW_8_15             (LTR010CMD_INSTR | 0x02)
#define CMD_SET_OUT_SW_16_23            (LTR010CMD_INSTR | 0x03)
#define CMD_SET_OUT_SW_24_31            (LTR010CMD_INSTR | 0x04)
#define CMD_SET_DAC                     (LTR010CMD_INSTR | 0x05)
#define CMD_SET_RELAYS                  (LTR010CMD_INSTR | 0x06)
#define CMD_SET_OUT_SW(i)               (LTR010CMD_INSTR | (i+1))

#define OUT_SW_CNT_PER_CMD              8
#define OUT_SW_CMD_CNT                  (LTRK511_OUT_SW_CNT/OUT_SW_CNT_PER_CMD)


#define LTRK511_FLASH_INFO_FORMAT         1
#define LTRK511_FLASH_INFO_ADDR           0
#define LTRK511_FLASH_INFO_SIZE_MIN       sizeof(t_ltrk511_flash_info)
#define LTRK511_FLASH_INFO_SIZE_MAX       1024




typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module_ex module;
    TLTRK511_CBR_COEF dac_cbr_coef;
} t_ltrk511_flash_info;

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTRK511_ERR_INVALID_SW_STATE,  "Задано неверное состояние выходного ключа"},
    {LTRK511_ERR_INVALID_SW_NUM,    "Указан неверный номер выходного ключа"},
    {LTRK511_ERR_INVALID_RELAY_NUM, "Указан неверный номер реле"}
};

LTRK511API_DllExport(INT) LTRK511_ReadInfo(TLTRK511 *hnd);

static DWORD f_fill_cmd_sw_cmd(DWORD cmd_num, const BYTE *states, INT *perr) {
    WORD data = 0;
    unsigned i;
    INT err = LTR_OK;

    for (i = 0; (i < OUT_SW_CNT_PER_CMD); i++) {
        BYTE state = states[cmd_num*OUT_SW_CNT_PER_CMD + i];
        BYTE code = 3;

        if (state == LTRK511_OUT_SW_STATE_OPEN) {
            code = 3;
        } else if (state == LTRK511_OUT_SW_STATE_EXT_SIG) {
            code = 2;
        } else if (state == LTRK511_OUT_SW_STATE_INT_DIV) {
            code = 1;
        } else {
            err = LTRK511_ERR_INVALID_SW_STATE;
        }

        if (err == LTR_OK) {
            data |= code << (2*i);
        }
    }

    if (perr != NULL) {
        *perr = err;
    }
    return LTR_MODULE_MAKE_CMD(CMD_SET_OUT_SW(cmd_num), data);
}

static DWORD f_fill_cmd_set_dac(const TLTRK511_CBR_COEF *cbr,  TLTRK511_DAC_STATE *state) {
    LTRK511_CalcDacCode(cbr, state->Data, state->Flags, &state->ResCode);
    return LTR_MODULE_MAKE_CMD(CMD_SET_DAC, state->ResCode);
}


static DWORD f_fill_cmd_relays_state(DWORD states) {
    WORD data = 0;
    if (states & 1) {
        data |= 1;
    }
    if (states & 2) {
        data |= (1 << 8);
    }

    return LTR_MODULE_MAKE_CMD(CMD_SET_RELAYS, data);
}

static void f_info_init(TLTRK511 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTRK511");

    hnd->ModuleInfo.DacCbrCoefs.Offset = 0;
    hnd->ModuleInfo.DacCbrCoefs.Scale  = 1;

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

LTRK511API_DllExport(INT) LTRK511_Init(TLTRK511 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        memset(hnd,0, sizeof(TLTRK511));
        hnd->size = sizeof(TLTRK511);
        f_info_init(hnd);

        err = LTR_Init(&hnd->Channel);
    }
    return err;
}


LTRK511API_DllExport(INT) LTRK511_Open(TLTRK511 *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 modification;
    BYTE ver_pld;

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

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

        if (modification == 2) {
            hnd->ModuleInfo.OutSwType = LTRK511_OUT_SW_TYPE_ADG453;
        } else {
            hnd->ModuleInfo.OutSwType = LTRK511_OUT_SW_TYPE_ADG444;
        }

        if ((modification == 1) || (modification == 2)) {
            hnd->ModuleInfo.RelayCnt = 1;
        } else {
            hnd->ModuleInfo.Flags |= LTRK511_INFO_FLAG_SUPPORT_DAC;
            hnd->ModuleInfo.RelayCnt = 2;
        }

        rdcfg_err = LTRK511_ReadInfo(hnd);
    }

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


    return err == LTR_OK ? rdcfg_err : err;
}


LTRK511API_DllExport(INT) LTRK511_Close(TLTRK511 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRK511API_DllExport(INT) LTRK511_IsOpened(TLTRK511 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}


LTRK511API_DllExport(INT) LTRK511_ReadInfo(TLTRK511 *hnd) {
    INT err = LTRK511_IsOpened(hnd);
    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        err = ltr_at93c86a_read(&hnd->Channel, LTRK511_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 != LTRK511_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && ((hdr.size < LTRK511_FLASH_INFO_SIZE_MIN) || (hdr.size > LTRK511_FLASH_INFO_SIZE_MAX)))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltrk511_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,
                                LTRK511_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, LTRK511_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTRK511_SERIAL_SIZE);
                memcpy(&hnd->ModuleInfo.DacCbrCoefs, &pinfo->dac_cbr_coef, sizeof(TLTRK511_CBR_COEF));
            }
            free(pinfo);
        }
    }
    return err;
}


LTRK511API_DllExport(INT) LTRK511_WriteInfo(TLTRK511 *hnd) {
    INT err = LTRK511_IsOpened(hnd);
    if (err == LTR_OK) {
        struct {
            t_ltrk511_flash_info info;
            WORD crc;
        } wrinfo, rdinfo;
        const size_t wr_size = sizeof(t_ltrk511_flash_info) + LTR_FLASH_GENINFO_CRC_SIZE;

        wrinfo.info.hdr.sign = LTR_FLASH_GENINFO_SIGN;
        wrinfo.info.hdr.format = LTRK511_FLASH_INFO_FORMAT;
        wrinfo.info.hdr.flags = 0;
        wrinfo.info.hdr.size = sizeof(t_ltrk511_flash_info);

        memcpy(wrinfo.info.module.name,   hnd->ModuleInfo.Name,         LTRK511_NAME_SIZE);
        memcpy(wrinfo.info.module.serial, hnd->ModuleInfo.Serial,       LTRK511_SERIAL_SIZE);
        memcpy(&wrinfo.info.dac_cbr_coef, &hnd->ModuleInfo.DacCbrCoefs, sizeof(TLTRK511_CBR_COEF));

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

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

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


LTRK511API_DllExport(LPCSTR) LTRK511_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);
}


LTRK511API_DllExport(INT) LTRK511_SetModuleOutState(TLTRK511 *hnd, const TLTRK511_OUT_STATE *state, DWORD flags) {
    INT err = LTRK511_IsOpened(hnd);
    if (err == LTR_OK) {
        unsigned i, pos;
        DWORD cmds[6], acks[6];
        TLTRK511_OUT_STATE new_state;
        memcpy(&new_state, state, sizeof (TLTRK511_OUT_STATE));


        pos = 0;
        for (i = 0; (i < OUT_SW_CMD_CNT) && (err == LTR_OK); i++) {
            cmds[pos++] = f_fill_cmd_sw_cmd(i, new_state.Sw, &err);
        }

        if (err == LTR_OK) {
            if (hnd->ModuleInfo.Flags & LTRK511_INFO_FLAG_SUPPORT_DAC) {
                cmds[pos++] = f_fill_cmd_set_dac(&hnd->ModuleInfo.DacCbrCoefs, &new_state.Dac);
            }
            cmds[pos++] = f_fill_cmd_relays_state(new_state.RelayStates);
        }

        if (err == LTR_OK) {
            err = ltr_module_send_with_echo_resps(&hnd->Channel, cmds, pos, acks);
        }

        if (err == LTR_OK) {
            hnd->State.Out = new_state;
        }
    }
    return err;
}

LTRK511API_DllExport(INT) LTRK511_SetOutSwState(TLTRK511 *hnd, DWORD sw_num, BYTE sw_state) {
    INT err = LTRK511_IsOpened(hnd);
    if ((err == LTR_OK) && (sw_num >= LTRK511_OUT_SW_CNT)) {
        err = LTRK511_ERR_INVALID_SW_NUM;
    } else {
        DWORD cmd, ack;
        BYTE sw_states[LTRK511_OUT_SW_CNT];

        memcpy(sw_states, &hnd->State.Out.Sw[0], sizeof(sw_states));
        sw_states[sw_num] = sw_state;
        cmd = f_fill_cmd_sw_cmd(sw_num/OUT_SW_CNT_PER_CMD, sw_states, &err);
        if (err == LTR_OK) {
            err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        }
        if (err == LTR_OK) {
            hnd->State.Out.Sw[sw_num] = sw_state;
        }

    }
    return err;
}



LTRK511API_DllExport(INT) LTRK511_SetDAC(TLTRK511 *hnd, double data, WORD flags) {
    INT err = LTRK511_IsOpened(hnd);
    if ((err == LTR_OK) && !(hnd->ModuleInfo.Flags & LTRK511_INFO_FLAG_SUPPORT_DAC)) {
        err = LTR_ERROR_UNSUP_BY_MODIFICATION;
    }
    if (err == LTR_OK) {
        DWORD cmd, ack;
        TLTRK511_DAC_STATE state;
        memset(&state, 0, sizeof(state));
        state.Data = data;
        state.Flags = flags;


        cmd = f_fill_cmd_set_dac(&hnd->ModuleInfo.DacCbrCoefs, &state);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (err == LTR_OK) {
            hnd->State.Out.Dac = state;
        }
    }
    return err;
}

LTRK511API_DllExport(INT) LTRK511_SetRelayStates(TLTRK511 *hnd, DWORD states) {
    INT err = LTRK511_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD cmd, ack;
        cmd = f_fill_cmd_relays_state(states);
        err = ltr_module_send_with_echo_resps(&hnd->Channel, &cmd, 1, &ack);
        if (err == LTR_OK) {
            hnd->State.Out.RelayStates = states;
        }
    }
    return err;
}

LTRK511API_DllExport(INT) LTRK511_SetRelayState(TLTRK511 *hnd, DWORD relay_num, BOOLEAN state) {
    INT err = LTRK511_IsOpened(hnd);
    if ((err == LTR_OK) && (relay_num >= hnd->ModuleInfo.RelayCnt)) {
        err = LTRK511_ERR_INVALID_RELAY_NUM;
    }
    if (err == LTR_OK) {
        DWORD states = hnd->State.Out.RelayStates;
        if (state) {
            states |= 1 << relay_num;
        } else {
            states &= ~(1 << relay_num);
        }
        err = LTRK511_SetRelayStates(hnd, states);
    }
    return err;
}


LTRK511API_DllExport(INT) LTRK511_CalcDacCode(const TLTRK511_CBR_COEF *dac_cbr, double data, DWORD flags, WORD *res_code) {
    INT err = LTR_OK;
    INT code;
    if (flags & LTRK511_DAC_FLAG_VALUE) {
        data = data * LTRK511_DAC_CODE_MAX / LTRK511_DAC_VALUE_MAX;
    }
    if ((flags & LTRK511_DAC_FLAG_CALIBR) && (dac_cbr != NULL)) {
        data = data * dac_cbr->Scale + dac_cbr->Offset;
    }

     code = (INT)(data + 0.5);
     if (code < 0) {
         code = 0;
     }
     if (code > LTRK511_DAC_CODE_MAX) {
         code = LTRK511_DAC_CODE_MAX;
     }

     if (res_code != NULL)
         *res_code = code;

     return err;
}
