#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "ltr42api.h"
#include "ltr4x.h"
#include "ltrmodule.h"
#ifdef LTRAPI_USE_KD_STORESLOTS
#include "ltrslot.h"
#endif


/*================================================================================================*/
#pragma pack(4)
struct LTR42Config {
    CHAR name[16];
    CHAR serial[24];
    CHAR firmware_ver[8];
    CHAR firmware_date[16];
    BOOLEAN ack_en;
    struct {
        INT start;
        INT seconds;
    } marks_mode;
    WORD outputs_state;
};
#pragma pack()


/*================================================================================================*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr42_to_ltr42cfg(const void *h_card, void *cfg);
static void conv_ltr42cfg_to_hltr42(const void *cfg, void *h_card);
#endif
static INT LTR42_OpenEx(TLTR42 *h_ltr42, INT net_addr, WORD net_port, const CHAR *crate_sn,
    INT slot, DWORD in_flags, DWORD *out_flags);


/*================================================================================================*/

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    { LTR42_ERR_DATA_TRANSMISSON_ERROR, "Ошибка при передаче данных модулю"},
    { LTR42_ERR_WRONG_SECOND_MARK_CONF, "Неверная конфигурация секундных меток"},
    { LTR42_ERR_WRONG_START_MARK_CONF,  "Неверная конфигурация метки СТАРТ"}
};

/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifdef LTRAPI_USE_KD_STORESLOTS
static void conv_hltr42_to_ltr42cfg(const void *h_card, void *cfg) {
    const TLTR42 *h_ltr42 = h_card;
    struct LTR42Config *pcfg = cfg;

    strncpy(pcfg->name, h_ltr42->ModuleInfo.Name, sizeof pcfg->name / sizeof pcfg->name[0]);
    strncpy(pcfg->serial, h_ltr42->ModuleInfo.Serial, sizeof pcfg->serial / sizeof pcfg->serial[0]);
    strncpy(pcfg->firmware_ver, h_ltr42->ModuleInfo.FirmwareVersion,
        sizeof pcfg->firmware_ver / sizeof pcfg->firmware_ver[0]);
    strncpy(pcfg->firmware_date, h_ltr42->ModuleInfo.FirmwareDate,
        sizeof pcfg->firmware_date / sizeof pcfg->firmware_date[0]);

    pcfg->ack_en = h_ltr42->AckEna;
    pcfg->marks_mode.start = h_ltr42->Marks.StartMark_Mode;
    pcfg->marks_mode.seconds = h_ltr42->Marks.SecondMark_Mode;
}

/*------------------------------------------------------------------------------------------------*/
static void conv_ltr42cfg_to_hltr42(const void *cfg, void *h_card) {
    TLTR42 *h_ltr42 = h_card;
    const struct LTR42Config *pcfg = cfg;

    strncpy(h_ltr42->ModuleInfo.Name, pcfg->name,
        sizeof h_ltr42->ModuleInfo.Name / sizeof h_ltr42->ModuleInfo.Name[0]);
    strncpy(h_ltr42->ModuleInfo.Serial, pcfg->serial,
        sizeof h_ltr42->ModuleInfo.Serial / sizeof h_ltr42->ModuleInfo.Serial[0]);
    strncpy(h_ltr42->ModuleInfo.FirmwareVersion, pcfg->firmware_ver,
        sizeof h_ltr42->ModuleInfo.FirmwareVersion / sizeof h_ltr42->ModuleInfo.FirmwareVersion[0]);
    strncpy(h_ltr42->ModuleInfo.FirmwareDate, pcfg->firmware_date,
        sizeof h_ltr42->ModuleInfo.FirmwareDate / sizeof h_ltr42->ModuleInfo.FirmwareDate[0]);

    h_ltr42->AckEna = pcfg->ack_en;
    h_ltr42->Marks.StartMark_Mode = pcfg->marks_mode.start;
    h_ltr42->Marks.SecondMark_Mode = pcfg->marks_mode.seconds;
}
#endif

/*------------------------------------------------------------------------------------------------*/
#ifdef _WIN32
int __stdcall DllEntryPoint(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    /* Included for compatibility with Borland */
    return 1;
}
#endif

/*------------------------------------------------------------------------------------------------*/
#ifdef _WIN32
BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    return TRUE;
}
#endif

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_Close(TLTR42 *hnd) {
    /* Функция окончания работы с модулем */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        INT stop_err;
        stop_err = LTR_Close(&hnd->Channel);
        if (err == LTR_OK)
            err = stop_err;
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_Config(TLTR42 *hnd) {
    /* Функция конфигурирования модуля */
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if((hnd->Marks.StartMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR42_ERR_WRONG_START_MARK_CONF;
        } else if ((hnd->Marks.SecondMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR42_ERR_WRONG_SECOND_MARK_CONF;
        }
    }

    if (err == LTR_OK) {
        DWORD command;
        DWORD command_ack;
        if (hnd->AckEna != 0)
            hnd->AckEna = 1;

        command = ltr_module_fill_cmd_parity(CONFIG, (((WORD)hnd->AckEna) << 8) |
            (hnd->Marks.StartMark_Mode << 4) | hnd->Marks.SecondMark_Mode);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &command_ack, 1);
    }


    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_ConfigAndStart(TLTR42 *hnd) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    /* Функция конфигурирования модуля */
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        if((hnd->Marks.StartMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR42_ERR_WRONG_START_MARK_CONF;
        } else if ((hnd->Marks.SecondMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR42_ERR_WRONG_SECOND_MARK_CONF;
        }
    }

    if (err == LTR_OK) {
        DWORD command;
        DWORD command_ack;
        if (hnd->AckEna != 0)
            hnd->AckEna = 1;

        command = ltr_module_fill_cmd_parity(CONFIG, (((WORD)hnd->AckEna) << 8) |
            (hnd->Marks.StartMark_Mode << 4) | hnd->Marks.SecondMark_Mode);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &command_ack, 1);
    }

    if (err == LTR_OK) {
        err = ltrslot_start_wconfig(hnd, &hnd->Channel, sizeof(struct LTR42Config), LTR_MID_LTR42,
            conv_hltr42_to_ltr42cfg);
    }
    return err;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(LPCSTR) LTR42_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);
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_Init(TLTR42 *hnd) {
    /* Функция заполнения структуры описания модуля значениями по умолчанию и
     * инициализации интерфейсного канала
     */
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        memset(hnd, 0, sizeof(*hnd));
        hnd->size = sizeof(TLTR42);

        hnd->AckEna = 1;                    /* По умолчанию подтверждения включены */
        hnd->Marks.SecondMark_Mode = LTR42_MARK_MODE_INTERNAL;
        hnd->Marks.StartMark_Mode = LTR42_MARK_MODE_INTERNAL;
        err = LTR_Init(&hnd->Channel);      /* Инициализируем интерефейсный канал */
        strcpy(hnd->ModuleInfo.Name, MODULE_DEFAULT_NAME);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_IsOpened(TLTR42 *hnd) {
    /* Функция проверки: открыт ли модуль */
    return (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&hnd->Channel);
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_MakeStartMark(TLTR42 *hnd) {
    /* Функция формирования метки "СТАРТ" */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD ack;
        DWORD command;
        command = ltr_module_fill_cmd_parity(MAKE_START_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_Open(TLTR42 *hnd, INT net_addr, WORD net_port, const CHAR *crate_sn,
    INT slot_num) {
    /* Функция создания и открытия канала связи с LTR42. Также RESET модуля */
    return LTR42_OpenEx(hnd, net_addr, net_port, crate_sn, slot_num, 0, NULL);
}

/*------------------------------------------------------------------------------------------------*/
static INT LTR42_OpenEx(TLTR42 *h_ltr42, INT net_addr, WORD net_port, const CHAR *crate_sn,
    INT slot, DWORD in_flags, DWORD *out_flags) {
    /* Функция создания и открытия канала связи с LTR42. Также RESET модуля */

    DWORD open_flg = 0;
    DWORD out_flg = 0;
    INT ret_val = (h_ltr42 == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;

    if (ret_val == LTR_OK) {
        if (in_flags & LTR_OPENINFLG_REOPEN) {
#ifdef LTRAPI_USE_KD_STORESLOTS
            ret_val = ltrslot_restore_config(h_ltr42, net_addr, net_port, crate_sn, slot,
                sizeof(struct LTR42Config), conv_ltr42cfg_to_hltr42, &out_flg);
            if (ret_val == LTR_OK) {
                if (out_flg & LTR_OPENOUTFLG_REOPEN)
                    open_flg |= LTR_MOPEN_INFLAGS_DONT_RESET;
            }
#else
            ret_val = LTR_ERROR_NOT_IMPLEMENTED;
#endif
        }
    }

    if (ret_val == LTR_OK) {
        ret_val = LTR4X_OpenEx(&h_ltr42->Channel, net_addr, net_port, crate_sn, slot, open_flg,
            &h_ltr42->ModuleInfo, NULL);
    }

    if (out_flags != NULL)
        *out_flags = out_flg;

    return ret_val;
}

LTR42API_DllExport(INT) LTR42_GetConfig(TLTR42 *hnd) {
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_GetConfig(&hnd->Channel, &hnd->ModuleInfo, NULL);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_ReadEEPROM(TLTR42 *hnd, INT Address, BYTE *val) {
    /* Функция чтения байта из указанной ячейки ППЗУ */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK)
        err = LTR4X_ReadEEPROM(&hnd->Channel, Address, val);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_StartSecondMark(TLTR42 *hnd) {
    /* Функция включения генерации секундной метки */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD ack;
        DWORD command;
        command = ltr_module_fill_cmd_parity(START_SECOND_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_StopSecondMark(TLTR42 *hnd) {
    /* Функция выключения генерации внутренней секундной метки */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD ack;
        DWORD command;
        command = ltr_module_fill_cmd_parity(STOP_SECOND_MARK, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_StoreConfig(TLTR42 *hnd, TLTR_CARD_START_MODE start_mode) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    INT ret_code = LTR42_IsOpened(hnd);

    if (ret_code == LTR_OK) {
        ret_code = ltrslot_store_config(hnd, &hnd->Channel, sizeof(struct LTR42Config), LTR_MID_LTR42,
            start_mode, conv_hltr42_to_ltr42cfg);
    }

    return ret_code;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_WriteArray(TLTR42 *hnd, const WORD *OutputArray, INT ArraySize) {
    /* Функция отправки массива 16-битных слов в выходной порт модуля */
    INT i;
    INT err = ((OutputArray == NULL) || (ArraySize <= 0)) ? LTR_ERROR_PARAMETERS : LTR42_IsOpened(hnd);
    for (i = 0; ((i < ArraySize) && (err == LTR_OK)); i++) {
        DWORD data[2];
        data[0] = (*OutputArray << 16) | 1; /* Отправляем пакет со словом для выхода */
        data[1] = (*OutputArray << 16);     /* Дублируем пакет */
        OutputArray++;
        /* @todo здесь был некий счетчик частоты передачи данных, который никак не использовался
         * (мб кто то в отладчике смотрел) и закоментированный Sleep(). Вообще по идее тут надо
         * как-то умнее действовать, чем просто тупо слать сперва все, а потом принимать все ответы,
         * т.к. иначе LTR42 может не успеть
         */
        err = ltr_module_send_cmd(&hnd->Channel, data, sizeof(data)/sizeof(data[0]));
    }

    if ((err == LTR_OK) && hnd->AckEna) {
        for (i = 0; ((i < ArraySize) && (err == LTR_OK)); i++) {
            DWORD command_ack;
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &command_ack, 1);
            if (err == LTR_OK)
                err = LTR4X_CheckAck(command_ack, DATA_OUTPUT_CONFIRM);
        }
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_WriteEEPROM(TLTR42 *hnd, INT Address, BYTE val) {
    /* Функция записи байта в указанную ячейку ППЗУ */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK)
        err = LTR4X_WriteEEPROM(&hnd->Channel, Address, val);
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_WritePort(TLTR42 *hnd, WORD OutputData) {
    /* Функция отправки 16-битного слова в выходной порт модуля */
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD data[2];
        data[0] = (OutputData << 16) | 1;   /* Отправляем пакет со словом для выхода */
        data[1] = (OutputData << 16);       /* Дублируем пакет */

        err = ltr_module_send_cmd(&hnd->Channel, data, sizeof(data)/sizeof(data[0]));
        /* Если включены подтверждения записи слов - принимаем и проверяем подтверждение */
        if ((err == LTR_OK) && hnd->AckEna) {
            DWORD command_ack;
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &command_ack, 1);
            if (err == LTR_OK)
                err = LTR4X_CheckAck(command_ack, DATA_OUTPUT_CONFIRM);
            if (err == LTR_OK) {
                DWORD ack_data = LTR_MODULE_CMD_GET_DATA(command_ack);
                if (ack_data != 0) {
                    fprintf(stderr, "Invaid data in ack:  ack = 0x%x\n", command_ack);
                    err = LTR_ERROR_INVALID_CMD_RESPONSE;
                }
            }
        }
    }
    return err;
}

LTR42API_DllExport (INT) LTR42_SetStartMarkPulseTime(TLTR42 *hnd, DWORD time_mks) {
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_SetStartMarkPulseTime(&hnd->Channel, time_mks, hnd->ModuleInfo.FirmwareVersion);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
LTR42API_DllExport(INT) LTR42_WritePortSaved(TLTR42 *hnd, WORD OutputData) {
#ifdef LTRAPI_USE_KD_STORESLOTS
    /* Функция отправки 16-битного слова в выходной порт модуля с сохранением его состояния в
     * крейте
     */
    int slots_supported;
    t_ltrslot_conn h_slot_conn = LTRSLOT_CONN_INVAL;
    INT err = LTR42_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD data[2];
        data[0] = (OutputData << 16) | 1;   /* Отправляем пакет со словом для выхода */
        data[1] = (OutputData << 16);       /* Дублируем пакет */

        err = ltr_module_send_cmd(&hnd->Channel, data, sizeof(data)/sizeof(data[0]));
        /* Если включены подтверждения записи слов - принимаем и проверяем подтверждение */
        if ((err == LTR_OK) && hnd->AckEna) {
            DWORD command_ack;
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &command_ack, 1);
            if (err == LTR_OK)
                err = LTR4X_CheckAck(command_ack, DATA_OUTPUT_CONFIRM);
        }
    }

    if (err == LTR_OK)
        err = ltrslot_replicate_connection(&hnd->Channel, &h_slot_conn);

    if (err == LTR_OK)
        err = ltrslot_check_support(h_slot_conn, &slots_supported);

    if (err == LTR_OK) {
        if (!slots_supported)
            err = LTR_ERROR_CARDSCONFIG_UNSUPPORTED;
    }

    if (err == LTR_OK) {
        const WORD outs = OutputData;
        err = ltrslot_update_config(h_slot_conn, offsetof(struct LTR42Config, outputs_state),
            sizeof(WORD), &outs);
    }


    ltrslot_destroy_connection(h_slot_conn);

    return err;
#else
    return LTR_ERROR_NOT_IMPLEMENTED;
#endif
}
