#include <string.h>
#include "ltr41api.h"
#include "ltr4x.h"
#include "ltrmodule.h"

#ifdef _WIN32
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     ) {
    return TRUE;
}
 
int __stdcall DllEntryPoint (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
      // Included for compatibility with Borland
      return 1;
}
#endif




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

        /* По умолчанию частота выдачи данных при потоковом сборе 15 кГц */
        hnd->StreamReadRate = 15000;

        // Режимы синхрометок по умолчанию
        hnd->Marks.SecondMark_Mode = 0;   // Режим секундой метки - внутренняя
        hnd->Marks.StartMark_Mode = 0;    // Режим метки "СТАРТ"-внутренняя

        err = LTR_Init(&hnd->Channel);   // Инициализируем интерефейсный канал

        strcpy(hnd->ModuleInfo.Name, MODULE_DEFAULT_NAME);
    }
    return err;
}


/*******************************************************************************
   Функция создания и открытия канала связи с LTR41. Также RESET модуля
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_Open(TLTR41 *hnd, INT net_addr, WORD net_port,
                                    const CHAR *crate_sn, INT slot_num) {
    INT err = hnd == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (err == LTR_OK) {
        err = LTR4X_Open(&hnd->Channel, net_addr, net_port, crate_sn, slot_num,
                         &hnd->ModuleInfo, NULL);
    }
    return err;
}
 

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

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

LTR41API_DllExport (INT) LTR41_GetConfig(TLTR41 *hnd) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_GetConfig(&hnd->Channel, &hnd->ModuleInfo, NULL);
    }
    return err;
}


/*******************************************************************************
                 Функция конфигурирования модуля
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_Config(TLTR41 *hnd) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        if((hnd->Marks.StartMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR41_ERR_WRONG_START_MARK_CONF;
        } else if ((hnd->Marks.SecondMark_Mode < 0) || (hnd->Marks.StartMark_Mode > 2)) {
            err = LTR41_ERR_WRONG_SECOND_MARK_CONF;
        } else if ((hnd->StreamReadRate < LTR41_STREAM_READ_RATE_MIN) ||
                   (hnd->StreamReadRate > LTR41_STREAM_READ_RATE_MAX)) {
            err = LTR41_ERR_WRONG_STREAM_READ_FREQ_SETTINGS;
        }
    }

    if (err == LTR_OK) {
        DWORD command[2], command_ack[2];
        WORD Prescaler;
        BYTE PrescalerCode;
        BYTE Scaler;

        LTR4X_EvalScales(&hnd->StreamReadRate, &Prescaler, &PrescalerCode, &Scaler);
        command[0] = ltr_module_fill_cmd_parity(CONFIG, (hnd->Marks.StartMark_Mode<<4) | hnd->Marks.SecondMark_Mode);
        command[1] = ltr_module_fill_cmd_parity(CONFIG_READ_RATE, (Scaler<<8) | PrescalerCode);

        err = LTR4X_SendCmdWithAck(&hnd->Channel, command, command_ack,
                                   sizeof(command)/sizeof(command[0]));
    }
    return err;
}

LTR41API_DllExport (INT) LTR41_CalcStreamReadFreq(double freq, double *resultFreq) {
    WORD Prescaler;
    BYTE PrescalerCode;
    BYTE Scaler;
    INT err;

    if (freq < LTR41_STREAM_READ_RATE_MIN) {
        freq = LTR41_STREAM_READ_RATE_MIN;
    }
    if (freq > LTR41_STREAM_READ_RATE_MAX) {
        freq = LTR41_STREAM_READ_RATE_MAX;
    }

    err = LTR4X_EvalScales(&freq, &Prescaler, &PrescalerCode, &Scaler);
    if ((err == LTR_OK) && (resultFreq != NULL)) {
        *resultFreq = freq;
    }
    return err;
}


/*******************************************************************************
   Функция чтения 16-битного слова из входного порта модуля
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_ReadPort(TLTR41 *hnd, WORD *InputData) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(READ_WORD, 0);
        /* Здесь не делаем проверку на четность, т.к. это дополнительное время.
           Правильно ли выполнилась команда- станет ясно по ответу (wtf???) */
        err = ltr_module_send_cmd(&hnd->Channel, &command, 1);
        if (err == LTR_OK) {
            err = ltr_module_recv_cmd_resp(&hnd->Channel, &ack, 1);
        }

        if ((err == LTR_OK) && (ack & 0x8000)) {
            err = LTR41_ERR_CANT_READ_DATA;
        }

        if ((err == LTR_OK) && (InputData != NULL)) {
            *InputData = (ack & 0xFFFF0000) >> 16;
        }
    }
    return err;
}

/*******************************************************************************
   Функция старта потокового чтения данных из модуля
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_StartStreamRead(TLTR41 *hnd) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        DWORD command, ack;
        command = ltr_module_fill_cmd_parity(START_STREAM_READ, 0);
        err = LTR4X_SendCmdWithAck(&hnd->Channel, &command, &ack, 1);
    }
    return err;
}

/*******************************************************************************
   Функция получения массива входных входных данных при непрерывном вводе
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_Recv(TLTR41 *hnd, DWORD *data, DWORD *tmark,
                                    DWORD size, DWORD timeout) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR_Recv(&hnd->Channel, data, tmark, size, timeout);
        if ((err >= 0) && (hnd->Channel.flags & LTR_FLAG_RBUF_OVF)) {
            err = LTR_ERROR_RECV_OVERFLOW;
        }
    }
    return err;
}


/*******************************************************************************
   Функция проверки входных данных при непрерывном вводе и формирования 16-битных слов
*******************************************************************************/
LTR41API_DllExport (INT) LTR41_ProcessData(TLTR41 *hnd, const DWORD *src,
                                           WORD *dest, DWORD *size) {
    DWORD i = 0, j = 0;
    INT err = (src == NULL) || (size == NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        DWORD proc_size = *size;
        DWORD done_size = 0;
        for (i = 0; (i < proc_size) && (err == LTR_OK); ++i) {
            if(i != 0) {
                if (((src[i] & 0xFF) - (src[i - 1] & 0xFF)) != 1) {
                    if (((src[i - 1] & 0xFF) != 255) && ((src[i] & 0xFF) != 0)) {
                        err = LTR41_ERR_WRONG_IO_DATA;
                    }
                }
            }

            if ((err == LTR_OK) && (dest != NULL)) {
                *dest++ = (WORD)(src[i] >> 16);
                ++done_size;
            }
        }
        *size = done_size;
    }
    return err;
}

/****************************************************************************************
   Функция остановки потокового чтения данных из модуля
****************************************************************************************/   
LTR41API_DllExport (INT) LTR41_StopStreamRead(TLTR41 *hnd) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_StopStreamRead(&hnd->Channel);
    }
    return err;
}



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

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


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



LTR41API_DllExport (INT) LTR41_SetStartMarkPulseTime(TLTR41 *hnd, DWORD time_mks) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_SetStartMarkPulseTime(&hnd->Channel, time_mks, hnd->ModuleInfo.FirmwareVersion);
    }
    return err;
}

static const TLTR_ERROR_STRING_DEF f_err_tbl[] = {
    { LTR41_ERR_WRONG_SECOND_MARK_CONF, "Неверная конфигурация секундных меток"},
    { LTR41_ERR_WRONG_START_MARK_CONF,  "Неверная конфигурация метки СТАРТ"},
    { LTR41_ERR_CANT_READ_DATA,         "Ошибка при чтении значений входов"},
    { LTR41_ERR_WRONG_IO_DATA,          "Неверные данные с портов ввода-вывода"},
    { LTR41_ERR_WRONG_STREAM_READ_FREQ_SETTINGS, "Неверные установки часоты данных при потоковом вводе"}
};

/*****************************************************************************************************
                Функция определения строки ошибки
******************************************************************************************************/   
LTR41API_DllExport (LPCSTR) LTR41_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);
}



/*******************************************************************************
        Функция записи байта в указанную ячейку ППЗУ
 ******************************************************************************/

LTR41API_DllExport (INT) LTR41_WriteEEPROM(TLTR41 *hnd, INT Address, BYTE val) {
    INT err = LTR41_IsOpened(hnd);
    if (err == LTR_OK) {
        err = LTR4X_WriteEEPROM(&hnd->Channel, Address, val);
    }
    return err;
}


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