#include "ltrk71api.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"
#include "lbitfield.h"


#if (LTRK71_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_EX_SIZE) || (LTRK71_NAME_SIZE != LTR_FLASH_GENINFO_NAME_EX_SIZE)
    #error module info len mismatch
#endif


#define CMD_MON_BUF_WR_WORD              (LTR010CMD_INSTR | 0x01)
#define CMD_MON_CTL                      (LTR010CMD_INSTR | 0x02)
#define CMD_STREAM_CTL                   (LTR010CMD_INSTR | 0x03)
#define CMD_STREAM_BUF_WR_EN             (LTR010CMD_INSTR | 0x04)

#define CMD_FLD_STREAM_CTL_INF           (0x7UL << 0U)
#define CMD_FLD_STREAM_CTL_CHNUM         (0x7UL << 3U)
#define CMD_FLD_STREAM_CTL_TXEN          (0x1UL << 6U)
#define CMD_FLD_STREAM_CTL_RXEN          (0x1UL << 7U)
#define CMD_FLD_STREAM_CTL_SPECSIG       (0x1UL << 8U)
#define CMD_FLD_STREAM_CTL_SS_PHASE_0    (0x1UL << 9U)
#define CMD_FLD_STREAM_CTL_SS_PHASE_180  (0x1UL << 10U)
#define CMD_FLD_STREAM_CTL_SS_MEANDER    (0x1UL << 11U)
#define CMD_FLD_STREAM_CTL_SS_PERIOD     (0xFUL << 12U)



#define LTRK71_FLASH_INFO_FORMAT         1
#define LTRK71_FLASH_INFO_ADDR           0
#define LTRK71_FLASH_INFO_SIZE_MIN       sizeof(t_LTRK71_flash_info)
#define LTRK71_FLASH_INFO_SIZE_MAX       1024

LTRK71API_DllExport(INT) LTRK71_ReadInfo(TLTRK71 *hnd);


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

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTRK71_ERR_INSUF_OUT_CH_BUF_SIZE, "Превышен размер буфера тестового потока на вывод"}
};


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

static BOOLEAN f_calc_parity(WORD word) {
    word ^= word >> 8;
    word ^= word >> 4;
    word ^= word >> 2;
    word ^= word >> 1;

    return word & 1;
}



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


LTRK71API_DllExport(INT) LTRK71_Open(TLTRK71 *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_LTRK71, &pld_ver, NULL);
    }

    if (err == LTR_OK) {
        f_info_init(hnd);
        hnd->ModuleInfo.VerPLD = pld_ver;
        hnd->State.ExchangeActive = FALSE;
        rdcfg_err = LTRK71_ReadInfo(hnd);
    }

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


    return err == LTR_OK ? rdcfg_err : err;
}


LTRK71API_DllExport(INT) LTRK71_Close(TLTRK71 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRK71API_DllExport(INT) LTRK71_IsOpened(TLTRK71 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}



LTRK71API_DllExport(INT) LTRK71_ReadInfo(TLTRK71 *hnd) {
    INT err = LTRK71_IsOpened(hnd);
    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        err = ltr_at93c86a_read(&hnd->Channel, LTRK71_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 != LTRK71_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && ((hdr.size < LTRK71_FLASH_INFO_SIZE_MIN) || (hdr.size > LTRK71_FLASH_INFO_SIZE_MAX)))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_LTRK71_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,
                                LTRK71_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, LTRK71_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTRK71_SERIAL_SIZE);
            }
            free(pinfo);
        }
    }
    return err;
}


LTRK71API_DllExport(INT) LTRK71_WriteInfo(TLTRK71 *hnd) {
    INT err = LTRK71_IsOpened(hnd);
    if (err == LTR_OK) {
        struct {
            t_LTRK71_flash_info info;
            WORD crc;
        } wrinfo, rdinfo;
        const size_t wr_size = sizeof(t_LTRK71_flash_info) + LTR_FLASH_GENINFO_CRC_SIZE;

        wrinfo.info.hdr.sign = LTR_FLASH_GENINFO_SIGN;
        wrinfo.info.hdr.format = LTRK71_FLASH_INFO_FORMAT;
        wrinfo.info.hdr.flags = 0;
        wrinfo.info.hdr.size = sizeof(t_LTRK71_flash_info);

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

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

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

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



LTRK71API_DllExport(INT) LTRK71_MonBufWrite(TLTRK71 *hnd, BYTE addr, BYTE word) {
    INT err = LTRK71_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.ExchangeActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD_BYTES(CMD_MON_BUF_WR_WORD, addr, word));
    }
    return err;
}

LTRK71API_DllExport(INT) LTRK71_MonSetMode(TLTRK71 *hnd, DWORD mode, DWORD CbrChNum) {
    INT err = LTRK71_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.ExchangeActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        const WORD data = (mode == LTRK71_MON_MODE_FROM_BUF ? 1 : 0) | ((CbrChNum & 0x7F) << 1);
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_MON_CTL, data));
    }
    return err;

}

LTRK71API_DllExport(INT) LTRK71_LoadTxStreamBuf(TLTRK71 *hnd, const WORD *data, WORD size) {
    INT err = LTRK71_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.ExchangeActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }

    if ((err == LTR_OK) && (size > LTRK71_TX_STREAM_BUF_SIZE)) {
        err = LTRK71_ERR_INSUF_OUT_CH_BUF_SIZE;
    }
    if (err == LTR_OK) {
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_STREAM_BUF_WR_EN, 1));
    }

    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 {
            DWORD i;
            for (i = 0; i < size; ++i) {
                WORD d = data[i] & 0x03FF;
                data_list[i] = ((i >> 6) & 0xFF) | ((i & 0x3F) << 26) | (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_STREAM_BUF_WR_EN, 0));
        if (err == LTR_OK) {
            err = dis_err;
        }
    }

    return err;
}

LTRK71API_DllExport(INT) LTRK71_ExchangeStart(TLTRK71 *hnd) {
    INT err = LTRK71_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.ExchangeActive) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        const TLTRK71_STREAM_CONFIG *stream_cfg = &hnd->Cfg.Stream;
        const BOOLEAN specsig_en = stream_cfg->TxSrc == LTRK71_TXSRC_SPECSIG;
        WORD data = LBITFIELD_SET(CMD_FLD_STREAM_CTL_INF, stream_cfg->Informativity)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_CHNUM, stream_cfg->RxChNum)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_RXEN, stream_cfg->RxEn)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_TXEN, stream_cfg->TxEn)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SPECSIG, specsig_en)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PHASE_0, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_PHASE_0))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PHASE_180, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_PHASE_180))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_MEANDER, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_MEANDER))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PERIOD, stream_cfg->SpecSigPeriod)
                ;
        err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_STREAM_CTL, data));
    }
    if (err == LTR_OK) {
        hnd->State.ExchangeActive = TRUE;
    }
    return err;
}

LTRK71API_DllExport(INT) LTRK71_ExchangeStop(TLTRK71 *hnd) {
    INT err = LTRK71_IsOpened(hnd);
    if (err == LTR_OK) {
        const TLTRK71_STREAM_CONFIG *stream_cfg = &hnd->Cfg.Stream;
        const BOOLEAN specsig_en = stream_cfg->TxSrc == LTRK71_TXSRC_SPECSIG;
        WORD data = LBITFIELD_SET(CMD_FLD_STREAM_CTL_INF, stream_cfg->Informativity)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_CHNUM, stream_cfg->RxChNum)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_RXEN, 0)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_TXEN, 0)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SPECSIG, specsig_en)
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PHASE_0, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_PHASE_0))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PHASE_180, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_PHASE_180))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_MEANDER, specsig_en && (stream_cfg->SpecSigType == LTRK71_SPECSIG_TYPE_MEANDER))
                | LBITFIELD_SET(CMD_FLD_STREAM_CTL_SS_PERIOD, stream_cfg->SpecSigPeriod)
                ;
        DWORD cmd[3];
        DWORD put_pos = 0;
        cmd[put_pos++] = LTR010CMD_STOP;
        cmd[put_pos++] = LTR010CMD_STOP;
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD(CMD_STREAM_CTL, data);
        err = ltr_module_stop_tout(&hnd->Channel, cmd, put_pos, cmd[put_pos-1],
                 LTR_MSTOP_FLAGS_SPEC_MASK, LTR_MODULE_CMD_DATA_MSK | LTR_MODULE_CMD_CODE_MSK, 10000, NULL);
        hnd->State.ExchangeActive = FALSE;
    }
    return err;
}

LTRK71API_DllExport(INT) LTRK71_Recv(TLTRK71 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    INT res = LTRK71_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;
}

LTRK71API_DllExport(INT) LTRK71_ProcessData(TLTRK71 *hnd, const DWORD *src_data, TLTRK71_RX_DATA *dst_data, INT *size) {
    INT res = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR :
                     ((src_data == NULL) || (dst_data == NULL) || (size == NULL)) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (res == LTR_OK) {
        INT i;
        INT in_size = *size;
        INT proc_size = 0;
        for (i = 0; (i < in_size) && (res == LTR_OK); ++i) {
            const WORD dwrd = LTR_MODULE_CMD_GET_DATA(src_data[i]);
            const BYTE info = src_data[i] & 0xFF;
            TLTRK71_RX_DATA *res_data = &dst_data[proc_size];
            res_data->Data =  (dwrd >> 1) & 0x3FF;
            res_data->ParityVal = dwrd & 1;
            res_data->ParityOk = f_calc_parity(res_data->Data) == !res_data->ParityVal;
            res_data->SvcInfo = (dwrd >> 11) & 1;
            res_data->PhaseWordNum = (dwrd >> 12) & 7;
            res_data->PhaseMarker = info & 1;
            res_data->Reserved = info;
            proc_size++;
        }
        *size = proc_size;
    }
    return res;
}


LTRK71API_DllExport(LPCSTR) LTRK71_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);
}


