#include "ltrk416api.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 "ltimer.h"


#if (LTRK416_SERIAL_SIZE != LTR_FLASH_GENINFO_SERIAL_EX_SIZE) || (LTRK416_NAME_SIZE != LTR_FLASH_GENINFO_NAME_EX_SIZE)
    #error module info len mismatch
#endif


#define CMD_SET_RS_CFG(ch)              (LTR010CMD_INSTR | (0x01 + (ch)))
#define CMD_SET_MIL_CFG(ch)             (LTR010CMD_INSTR | (0x05 + (ch)))
#define CMD_SET_OUT_BUF_WR_EN           (LTR010CMD_INSTR | 0x07)
#define CMD_GET_DAC                     (LTR010CMD_INSTR | 0x09)
#define CMD_SET_DAC                     (LTR010CMD_INSTR | 0x0A)



#define EXCH_CH_ID_RS(rs_ch)   (rs_ch)
#define EXCH_CH_ID_MIL(mil_ch) (LTRK416_RS_CH_CNT + (mil_ch))


#define LTRK416_FLASH_INFO_FORMAT         1
#define LTRK416_FLASH_INFO_ADDR           0
#define LTRK416_FLASH_INFO_SIZE_MIN       sizeof(t_ltrk416_flash_info)
#define LTRK416_FLASH_INFO_SIZE_MAX       512

#define DAC_TX_INC_SIZE    500
#define DAC_MAX_STEP       5
#define DAC_MIN_STEP_TIME  5

LTRK416API_DllExport(INT) LTRK416_ReadInfo(TLTRK416 *hnd);
static INT f_get_dac_code(TLTRK416 *hnd, WORD *code);

typedef struct {
    t_ltr_flash_geninfo_hdr hdr;
    t_ltr_flash_geninfo_module_ex module;
    TLTRK416_CBR_COEF dac_cbr_coef;
} t_ltrk416_flash_info;

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    {LTRK416_ERR_INVALID_CH_NUM,        "Указан недействительный номер канала"},
    {LTRK416_ERR_INSUF_OUT_CH_BUF_SIZE, "Превышен размер буфера канала на вывод"}
};




static void f_info_init(TLTRK416 *hnd) {
    memset(&hnd->ModuleInfo, 0, sizeof(hnd->ModuleInfo));
    strcpy(hnd->ModuleInfo.Name, "LTRK416");
    memset(&hnd->State, 0, sizeof(hnd->State));
    hnd->ModuleInfo.DacCbrCoefs.Offset = 0;
    hnd->ModuleInfo.DacCbrCoefs.Scale  = 1;
}

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

    return word & 1;
}


LTRK416API_DllExport(INT) LTRK416_Init(TLTRK416 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK){
        memset(hnd,0, sizeof(TLTRK416));
        hnd->size = sizeof(TLTRK416);
        f_info_init(hnd);
        err=LTR_Init(&hnd->Channel);

        hnd->Cfg.RS.Ch[0].RSBaudrate = LTRK416_RS_BAUDRATE_115200;
        hnd->Cfg.RS.Ch[1].RSBaudrate = LTRK416_RS_BAUDRATE_9600;
        hnd->Cfg.RS.Ch[2].RSBaudrate = LTRK416_RS_BAUDRATE_19200;
        hnd->Cfg.RS.Ch[3].RSBaudrate = LTRK416_RS_BAUDRATE_9600;
    }
    return err;
}


LTRK416API_DllExport(INT) LTRK416_Open(TLTRK416 *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_LTRK416, &pld_ver, NULL);
    }

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

    if (err == LTR_OK) {
        err = f_get_dac_code(hnd, &hnd->State.Dac.ResCode);
    }

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


    return err == LTR_OK ? rdcfg_err : err;
}


LTRK416API_DllExport(INT) LTRK416_Close(TLTRK416 *hnd) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR01_Close(&hnd->Channel);
    }
    return err;
}

LTRK416API_DllExport(INT) LTRK416_IsOpened(TLTRK416 *hnd) {
    return hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}



LTRK416API_DllExport(INT) LTRK416_ReadInfo(TLTRK416 *hnd) {
    INT err = LTRK416_IsOpened(hnd);
    if (err == LTR_OK) {
        t_ltr_flash_geninfo_hdr hdr;
        err = ltr_at93c86a_read(&hnd->Channel, LTRK416_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 != LTRK416_FLASH_INFO_FORMAT))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if ((err == LTR_OK) && ((hdr.size < LTRK416_FLASH_INFO_SIZE_MIN) || (hdr.size > LTRK416_FLASH_INFO_SIZE_MAX)))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;
        if (err == LTR_OK) {
            t_ltrk416_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,
                                LTRK416_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, LTRK416_NAME_SIZE);
                memcpy(hnd->ModuleInfo.Serial, pinfo->module.serial, LTRK416_SERIAL_SIZE);
                memcpy(&hnd->ModuleInfo.DacCbrCoefs, &pinfo->dac_cbr_coef, sizeof(TLTRK416_CBR_COEF));
            }
            free(pinfo);
        }
    }
    return err;
}


LTRK416API_DllExport(INT) LTRK416_WriteInfo(TLTRK416 *hnd) {
    INT err = LTRK416_IsOpened(hnd);
    if (err == LTR_OK) {
        struct {
            t_ltrk416_flash_info info;
            WORD crc;
        } wrinfo, rdinfo;
        const size_t wr_size = sizeof(t_ltrk416_flash_info) + LTR_FLASH_GENINFO_CRC_SIZE;

        wrinfo.info.hdr.sign = LTR_FLASH_GENINFO_SIGN;
        wrinfo.info.hdr.format = LTRK416_FLASH_INFO_FORMAT;
        wrinfo.info.hdr.flags = 0;
        wrinfo.info.hdr.size = sizeof(t_ltrk416_flash_info);

        memcpy(wrinfo.info.module.name,   hnd->ModuleInfo.Name,   LTRK416_NAME_SIZE);
        memcpy(wrinfo.info.module.serial, hnd->ModuleInfo.Serial, LTRK416_SERIAL_SIZE);
        memcpy(&wrinfo.info.dac_cbr_coef, &hnd->ModuleInfo.DacCbrCoefs, sizeof(TLTRK416_CBR_COEF));

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

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

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


static INT f_get_dac_code(TLTRK416 *hnd, WORD *code) {
    INT err;
    DWORD cmd = LTR_MODULE_MAKE_CMD(CMD_GET_DAC, 0);
    err = ltr_module_send_cmd(&hnd->Channel, &cmd, 1);
    if (err == LTR_OK) {
        DWORD ack;
        err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack, 1);
        if (err == LTR_OK) {
            *code = LTR_MODULE_CMD_GET_DATA(ack);
        }
    }
    return err;
}



LTRK416API_DllExport(INT) LTRK416_SetDAC(TLTRK416 *hnd, double data, WORD flags) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Exchange.Active) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        WORD res_code, cur_code = hnd->State.Dac.ResCode;
        LTRK416_CalcDacCode(&hnd->ModuleInfo.DacCbrCoefs, data, flags, &res_code);
        while ((res_code != cur_code) && (err == LTR_OK)) {
            t_lclock_ticks time;
            INT add_code = res_code - cur_code;
            if (add_code > DAC_MAX_STEP) {
                add_code = DAC_MAX_STEP;
            }
            if (add_code < -DAC_MAX_STEP) {
                add_code = -DAC_MAX_STEP;
            }
            cur_code += add_code;
            time = lclock_get_ticks();
            err = ltr_module_send_single_with_echo(&hnd->Channel, LTR_MODULE_MAKE_CMD(CMD_SET_DAC, cur_code));
            if (err == LTR_OK) {
                hnd->State.Dac.ResCode = cur_code;
                time = lclock_get_ticks() - time;
                if (time < DAC_MIN_STEP_TIME) {
                    int dt = DAC_MIN_STEP_TIME - time;
                    LTRAPI_SLEEP_MS(dt);
                }
            }
        }

        if (err == LTR_OK) {
            hnd->State.Dac.Data = data;
            hnd->State.Dac.Flags = flags;
        }
    }
    return err;
}

#define DAC_BUF_MAX_SIZE         (64*1024)

static INT f_put_word(TLTRK416 *hnd, DWORD *buf, DWORD buf_size, DWORD *pput_pos, DWORD *psend_cmds, DWORD cmd) {
    INT err = LTR_OK;
    DWORD put_pos = *pput_pos;
    buf[put_pos++] = cmd;
    if (put_pos == buf_size) {
        err = ltr_module_send_cmd(&hnd->Channel, buf, put_pos);
        if (err == LTR_OK) {
            DWORD send_cmds = *psend_cmds;
            send_cmds += put_pos;
            put_pos = 0;
            if (send_cmds > 4 * buf_size) {
                DWORD recv_cnt = buf_size;
                DWORD i;
                err = ltr_module_recv_cmd_resp(&hnd->Channel, buf, recv_cnt);
                for (i = 0; ((i < recv_cnt) && (err == LTR_OK)); i++) {
                    if ((LTR_MODULE_CMD_GET_CMDCODE(buf[i]) != CMD_SET_DAC) &&
                            (LTR_MODULE_CMD_GET_CMDCODE(buf[i]) != CMD_GET_DAC)) {
                        err = LTR_ERROR_INVALID_CMD_RESPONSE;
                    }
                }

                if (err == LTR_OK) {
                    send_cmds -= recv_cnt;
                }
            }
            *psend_cmds = send_cmds;
        }
    }
    *pput_pos = put_pos;
    return err;
}

LTRK416API_DllExport(INT) LTRK416_SetDACEx(TLTRK416 *hnd, double data, WORD flags, INT inc, DWORD cmd_delay) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Exchange.Active) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        WORD res_code, cur_code = hnd->State.Dac.ResCode;
        DWORD send_cmds = 0;
        DWORD put_pos = 0;
        DWORD *cmds = malloc(DAC_BUF_MAX_SIZE * sizeof(DWORD));
        LTRK416_CalcDacCode(&hnd->ModuleInfo.DacCbrCoefs, data, flags, &res_code);
        while ((res_code != cur_code) && (err == LTR_OK)) {
            DWORD i;
            t_lclock_ticks time;
            INT add_code = res_code - cur_code;
            if (add_code > inc) {
                add_code = inc;
            }
            if (add_code < -inc) {
                add_code = -inc;
            }
            cur_code += add_code;
            f_put_word(hnd, cmds, DAC_BUF_MAX_SIZE, &put_pos, &send_cmds, LTR_MODULE_MAKE_CMD(CMD_SET_DAC, cur_code));
            for (i = 0; (i < cmd_delay) && (err == LTR_OK); ++i) {
                f_put_word(hnd, cmds, DAC_BUF_MAX_SIZE, &put_pos, &send_cmds, LTR_MODULE_MAKE_CMD(CMD_GET_DAC, 0));
            }
        }
        if ((err == LTR_OK) && (put_pos > 0)) {
            err = ltr_module_send_cmd(&hnd->Channel, cmds, put_pos);
            if (err == LTR_OK) {
                send_cmds += put_pos;
            }
        }

         while ((err == LTR_OK) && (send_cmds > 0)) {
             DWORD i;
             DWORD recv_cnt = send_cmds;
             if (recv_cnt > DAC_BUF_MAX_SIZE)
                 recv_cnt = DAC_BUF_MAX_SIZE;
             err = ltr_module_recv_cmd_resp(&hnd->Channel, cmds, recv_cnt);

            for (i = 0; ((i < recv_cnt) && (err == LTR_OK)); i++) {
                if ((LTR_MODULE_CMD_GET_CMDCODE(cmds[i]) != CMD_SET_DAC) &&
                        (LTR_MODULE_CMD_GET_CMDCODE(cmds[i]) != CMD_GET_DAC)) {
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                }
            }

            if (err == LTR_OK) {
                send_cmds -= recv_cnt;
            }
        }
        if (err == LTR_OK) {
            hnd->State.Dac.Data = data;
            hnd->State.Dac.Flags = flags;
            hnd->State.Dac.ResCode = cur_code;
        }
    }
    return err;

}

LTRK416API_DllExport(INT) LTRK416_CalcDacCode(const TLTRK416_CBR_COEF *dac_cbr, double data, DWORD flags, WORD *res_code) {
    INT err = LTR_OK;
    INT code;
    if (flags & LTRK416_DAC_FLAG_VALUE) {
        data = data * LTRK416_DAC_CODE_MAX / LTRK416_DAC_VALUE_MAX;
    }
    if ((flags & LTRK416_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 > LTRK416_DAC_CODE_MAX) {
         code = LTRK416_DAC_CODE_MAX;
     }

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

     return err;
}

LTRK416API_DllExport(INT) LTRK416_LoadRsBuf(TLTRK416 *hnd, BYTE ch_num, const DWORD *data, WORD size) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Exchange.Active) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (ch_num >= LTRK416_RS_CH_CNT)) {
        err = LTRK416_ERR_INVALID_CH_NUM;
    }
    if ((err == LTR_OK) && (size > LTRK416_RS_CH_BUF_SIZE)) {
        err = LTRK416_ERR_INSUF_OUT_CH_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 << EXCH_CH_ID_RS(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) & 3) << 14) | (data[i] & 0xFF);
                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;
}

LTRK416API_DllExport(INT) LTRK416_LoadMilBuf(TLTRK416 *hnd, BYTE ch_num, const DWORD *data, WORD size) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Exchange.Active) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if ((err == LTR_OK) && (ch_num >= LTRK416_MIL_CH_CNT)) {
        err = LTRK416_ERR_INVALID_CH_NUM;
    }
    if ((err == LTR_OK) && (size > LTRK416_MIL_CH_BUF_SIZE)) {
        err = LTRK416_ERR_INSUF_OUT_CH_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 << EXCH_CH_ID_MIL(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 = data[i] & 0xFFFF;
                data_list[i] = (i & 0x7F) | (((data[i] & LTRK416_MIL_DATA_FLAG_CMD) ? 1 : 0) << 7) | (d << 16);
            }
            err = ltr_module_send_with_echo_resps(&hnd->Channel, data_list, size, ack_list);
            if (err == LTR_ERROR_INVALID_CMD_RESPONSE) {
                err = LTR_OK;
            }
        }
        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;
}


INT f_exchange_cfg(TLTRK416 *hnd, BOOLEAN enabled) {
    INT err = 0;
    DWORD cmd[LTRK416_RS_CH_CNT + LTRK416_MIL_CH_CNT + 1];
    DWORD ack[LTRK416_RS_CH_CNT + LTRK416_MIL_CH_CNT + 1];
    DWORD i;

    DWORD put_pos = 0;

    if (!enabled) {
        cmd[put_pos++] = LTR010CMD_STOP;
    }

    for (i = 0; i < LTRK416_RS_CH_CNT; ++i) {
        const TLTRK416_CH_CONFIG *chCfg = &hnd->Cfg.RS.Ch[i];
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD_BYTES(CMD_SET_RS_CFG(i),
                                                   enabled ? ((chCfg->TxEn ? 1 : 0)
                                                             | (chCfg->RxEn ? 2 : 0))
                                                             | ((chCfg->RSBaudrate & 0xF) << 4)
                                                           : 0,
                                                   chCfg->PauseLen);
    }

    for (i = 0; i < LTRK416_MIL_CH_CNT; ++i) {
        const TLTRK416_CH_CONFIG *chCfg = &hnd->Cfg.MIL.Ch[i];
        cmd[put_pos++] = LTR_MODULE_MAKE_CMD_BYTES(CMD_SET_MIL_CFG(i),
                                                   enabled ? ((chCfg->TxEn ? 1 : 0) | (chCfg->RxEn ? 2 : 0)) : 0,
                                                   chCfg->PauseLen);
    }

    if (enabled) {
        err = ltr_module_send_with_echo_resps(&hnd->Channel, cmd, put_pos, ack);
    } else {
        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);
    }

    if (err == LTR_OK) {
        hnd->State.Exchange.Active = enabled;
    }

    return err;
}


LTRK416API_DllExport(INT) LTRK416_ExchangeStart(TLTRK416 *hnd) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && hnd->State.Exchange.Active) {
        err = LTR_ERROR_MODULE_STARTED;
    }
    if (err == LTR_OK) {
        err = f_exchange_cfg(hnd, TRUE);
    }

    return err;
}

LTRK416API_DllExport(INT) LTRK416_ExchangeStop(TLTRK416 *hnd) {
    INT err = LTRK416_IsOpened(hnd);
    if (err == LTR_OK) {
        err = f_exchange_cfg(hnd, FALSE);
    }

    return err;
}


LTRK416API_DllExport(INT) LTRK416_Recv(TLTRK416 *hnd, DWORD *data, DWORD *tmark, DWORD size, DWORD timeout) {
    INT res = LTRK416_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;
}

LTRK416API_DllExport(INT) LTRK416_ProcessData(TLTRK416 *hnd, const DWORD *src_data, TLTRK416_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;
            const BYTE ch_id = (info >> 4) & 0xF;
            TLTRK416_RX_DATA *res_data = &dst_data[proc_size];

            if ((ch_id >= 1) && (ch_id <= 4)) {
                res_data->ChType = LTRK416_CH_TYPE_RS;
                res_data->ChNum  = ch_id - 1;
                res_data->RS.Data = dwrd & 0xFF;
                res_data->RS.ParityVal = (dwrd >> 8) & 1;
                res_data->RS.ParityOk = f_calc_parity(res_data->RS.Data) == !res_data->RS.ParityVal;
                proc_size++;
            } else if ((ch_id >= 5) && (ch_id <= 6)) {
                res_data->ChType = LTRK416_CH_TYPE_MIL;
                res_data->ChNum = ch_id - 5;
                res_data->MIL.Data = dwrd;
                if (info & 2) {
                    res_data->MIL.WordType = LTRK416_MIL_WORD_TYPE_RX_ERR;
                    res_data->MIL.Error.Code   = dwrd & 0xFF;
                    res_data->MIL.Error.Parity = (dwrd >> 15) & 0x1;
                } else {
                    res_data->MIL.WordType = (info & 1) ? LTRK416_MIL_WORD_TYPE_CMD :
                                                          LTRK416_MIL_WORD_TYPE_DATA;
                    res_data->MIL.Data = dwrd;
                }
                proc_size++;
            }
        }
        *size = proc_size;
    }
    return res;
}

LTRK416API_DllExport(INT) LTRK416_FlashWrite(TLTRK416 *hnd, WORD addr, const BYTE *data, WORD size) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && ((addr < LTRK416_FLASH_USER_ADDR)
                            || ((addr + size) > (LTRK416_FLASH_USER_ADDR + LTRK416_FLASH_USER_SIZE)))) {
        err = LTR_ERROR_PARAMETERS;
    }
    if (err == LTR_OK) {
        err = ltr_at93c86a_write(&hnd->Channel, addr, data, size);
    }
    return err;
}

LTRK416API_DllExport(INT) LTRK416_FlashRead(TLTRK416 *hnd, WORD addr, BYTE *data, WORD size) {
    INT err = LTRK416_IsOpened(hnd);
    if ((err == LTR_OK) && ((addr < LTRK416_FLASH_USER_ADDR)
                            || ((addr + size) > (LTRK416_FLASH_USER_ADDR + LTRK416_FLASH_USER_SIZE)))) {
        err = LTR_ERROR_PARAMETERS;
    }
    if (err == LTR_OK) {
        err = ltr_at93c86a_read(&hnd->Channel, addr, data, size);
    }
    return err;
}



LTRK416API_DllExport(LPCSTR) LTRK416_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);
}




