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

#include "ltr4x.h"

#include "crc.h"
#include "ltrmodule.h"


/*================================================================================================*/
#define LTR4X_CONFIG_RECORD_LENGTH (42)
#define LTR4X_CRC_LENGTH (2)
#define LTR4X_STOP_TIMEOUT (5000)


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
INT LTR4X_CheckAck(DWORD ack, DWORD cmd) {
    INT err = LTR_OK;

    /* Проверка на четность пришедшего пакета */
    err = ltr_module_check_parity(ack);
    if (err==LTR_OK) {
        DWORD ack_code = LTR_MODULE_CMD_GET_CMDCODE(ack);
        if (ack_code == PARITY_ERROR) {
            err = LTR_ERROR_INVALID_CMD_PARITY;
#ifdef DATA_ERROR
        } else if (ack_code == DATA_ERROR) {
            err = LTR4X_ERR_DATA_TRANSMISSON_ERROR;
#endif
#ifdef RS485_ERROR_CONFIRM_TIMEOUT
        } else if (ack_code == RS485_ERROR_CONFIRM_TIMEOUT) {
            err = LTR4X_ERR_RS485_CONFIRM_TIMEOUT;
#endif
#ifdef RS485_ERROR_SEND_TIMEOUT
        } else if (ack_code == RS485_ERROR_SEND_TIMEOUT) {
            err = LTR4X_ERR_RS485_SEND_TIMEOUT;
#endif
        } else if (ack_code != LTR_MODULE_CMD_GET_CMDCODE(cmd)) {
            // Проверяем отв. команду, предварительно "убив" бит четности
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
#ifdef LTR_ERROR_PRINT
            fprintf(stderr, "invalid response: ack = 0x%x, cmd = 0x%x\n", ack, cmd);
#endif
        }
    }

    return err;
}

/*------------------------------------------------------------------------------------------------*/
#ifdef CONFIG_READ_RATE
INT LTR4X_EvalScales(double *Freq, WORD *Prescaler, BYTE *PrescalerCode, BYTE *Scaler) {
    /* Функция расчета делителя и пределителя таймера при потоковом вводе */
    if ((*Freq >= 100.0) && (*Freq < 230.0)) {
        *Prescaler = 1024;
        *PrescalerCode = 4;
    }

    if ((*Freq >= 230.0) && (*Freq < 920.0)) {
        *Prescaler = 256;
        *PrescalerCode = 3;
    }

    if ((*Freq >= 920.0) && (*Freq < 7325.0)) {
        *Prescaler = 64;
        *PrescalerCode = 2;
    }

    if ((*Freq >= 7325.0) && (*Freq < 58595.0)) {
        *Prescaler = 8;
        *PrescalerCode = 1;
    }

    if((*Freq >= 58595.0) && (*Freq <= 100000.0)) {
        *Prescaler = 1;
        *PrescalerCode = 0;
    }

    *Scaler = (BYTE)(((F_OSC / ((*Prescaler) * (*Freq))) - 1) + 0.5);
    *Freq = F_OSC / ((*Prescaler) * (*Scaler + 1));

    return LTR_OK;
}
#endif

/*------------------------------------------------------------------------------------------------*/
INT LTR4X_Open(TLTR *h_ltr, INT net_addr, WORD net_port, const CHAR *crate_sn, INT slot_num,
    TLTR4X_MODULE_INFO *info, WORD *ver) {
    return LTR4X_OpenEx(h_ltr, net_addr, net_port, crate_sn, slot_num, 0, info, ver);
}

INT LTR4X_GetConfig(TLTR *h_ltr, TLTR4X_MODULE_INFO *info, WORD *ver) {
    INT err;
    err = LTR4X_ReadConfigRecord(h_ltr, info->FirmwareVersion, info->FirmwareDate,
                                info->Name, info->Serial, ver);


    /* за первым нулем в строке во flash-памяти может лежать мусор. с ним могут
     * быть проблемы в .Net при интерпретации поля как char[].
     * обнуляем этот лишний мусор для избежания проблем */
    if (err == LTR_OK) {
        size_t len;

        info->FirmwareVersion[sizeof(info->FirmwareVersion) - 1] = '\0';
        len = strlen(info->FirmwareVersion);
        memset(&info->FirmwareVersion[len], 0, sizeof(info->FirmwareVersion) - len);

        info->FirmwareDate[sizeof(info->FirmwareDate) - 1] = '\0';
        len = strlen(info->FirmwareDate);
        memset(&info->FirmwareDate[len], 0, sizeof(info->FirmwareDate) - len);

        info->Name[sizeof(info->Name) - 1] = '\0';
        len = strlen(info->Name);
        memset(&info->Name[len], 0, sizeof(info->Name) - len);

        info->Serial[sizeof(info->Serial) - 1] = '\0';
        len = strlen(info->Serial);
        memset(&info->Serial[len], 0, sizeof(info->Serial) - len);
    }
    return err;
}



/*------------------------------------------------------------------------------------------------*/
INT LTR4X_OpenEx(TLTR *h_ltr, INT net_addr, WORD net_port, const CHAR *crate_sn, INT slot_num,
                 DWORD mflags, TLTR4X_MODULE_INFO *info, WORD *ver) {
    INT warning;
    INT err = (h_ltr == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    DWORD flags = mflags;

    if (err == LTR_OK) {
        err = ltr_module_open(h_ltr, net_addr, net_port, crate_sn, slot_num, MODULE_ID, &flags,
                              NULL, &warning);
        if (err == LTR_OK) {
            memset(info, 0, sizeof(TLTR4X_MODULE_INFO));
            strcpy(info->Name, MODULE_DEFAULT_NAME);
        }
    }

    if ((err == LTR_OK) && !(flags & LTR_MOPEN_OUTFLAGS_DONT_INIT)) {
        DWORD command = INIT;

        LTRAPI_SLEEP_MS(LTR4X_INIT_TIMEOUT);

        /* Посылаем первую команду INSTR */
        err = ltr_module_send_cmd(h_ltr, &command, 1);
        if (err == LTR_OK)
            err = ltr_module_recv_cmd_resp(h_ltr, &command, 1);

        if (err == LTR_OK) {
            err = LTR4X_GetConfig(h_ltr, info, ver);
        }
    }
    return (err == LTR_OK) ? warning : err;
}

/*------------------------------------------------------------------------------------------------*/
INT LTR4X_ReadConfigRecord(TLTR *hnd, CHAR *version, CHAR *date, CHAR *name, CHAR *serial,
    WORD *firm_ver) {
    /* Функция чтения конфигурационной записи модуля модуля */
    BYTE conf_rec[LTR4X_CONFIG_RECORD_LENGTH];
    BYTE ver[2];
    INT err = (hnd == NULL) ? LTR_ERROR_PARAMETRS : LTR_OK;

    if (err == LTR_OK) {
        DWORD command = ltr_module_fill_cmd_parity(READ_CONF_RECORD, 0);

        err = ltr_module_send_cmd(hnd, &command, 1);
        if (err == LTR_OK) {
            DWORD ack[LTR4X_CONFIG_RECORD_LENGTH+LTR4X_CRC_LENGTH];
            WORD CR_CRC;                    /* Контр. сумма конф. записи, посчитанная тут */
            WORD CR_CRC_RCV;                /* Контр. сумма конф. записи, считанная из Flash */
            DWORD i;

            err = ltr_module_recv_cmd_resp(hnd, ack, sizeof(ack)/sizeof(ack[1]));

            for (i = 0; ((i < sizeof(ack) / sizeof(ack[0])) && (err == LTR_OK)); ++i) {
                err = LTR4X_CheckAck(ack[i], command);
                if (i < LTR4X_CONFIG_RECORD_LENGTH)
                    conf_rec[i] = (BYTE)((ack[i] >> 16) & 0xFF);
            }

            /* сравниваем CRC */
            CR_CRC = eval_crc16(0, conf_rec, LTR4X_CONFIG_RECORD_LENGTH);
            CR_CRC_RCV = (WORD)(((ack[LTR4X_CONFIG_RECORD_LENGTH] >> 16) & 0x0FFF) |
                ((ack[LTR4X_CONFIG_RECORD_LENGTH+1] >> 8) & 0xFF00u));
            if (CR_CRC != CR_CRC_RCV)
                err = LTR_ERROR_FLASH_INFO_CRC;
        }
    }

    if (err == LTR_OK) {
        WORD i;

        if(conf_rec[0] != (MODULE_ID & 0xFF))
            err = LTR_ERROR_FLASH_INFO_UNSUP_FORMAT;

        for (i = 0; (i < 2); i++) {
            ver[i] = conf_rec[1+i];         /* Версия БИОСа */
        }
        sprintf(version, "%d.%d",ver[0], ver[1]);

        memcpy(date, &conf_rec[3], 14);     /* Дата БИОСа */
        memcpy(name, &conf_rec[17], 8);     /* Имя модуля */
        memcpy(serial, &conf_rec[25], 17);  /* Серийный номер */

        if (firm_ver != NULL)
            *firm_ver = (ver[0] << 8) | ver[1];
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
INT LTR4X_ReadEEPROM(TLTR *hnd, INT Address, BYTE *val) {
    /* Функция чтения байта из указанной ячейки ППЗУ */
    INT err = (hnd == NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;
    if (err == LTR_OK) {
        if ((Address < 0) || (Address >= LTR4X_EEPROM_SIZE))
            err = LTR_ERROR_FLASH_INVALID_ADDR;
    }

    if (err == LTR_OK) {
        /* передаем команду с адресом, в ответе возвращаются данные */
        DWORD ack;
        DWORD command = ltr_module_fill_cmd_parity(READ_EEPROM, Address);
        err = LTR4X_SendCmdWithAck(hnd, &command, &ack, 1);
        if ((err == LTR_OK) && (val != NULL))
            *val=(BYTE)((ack >> 16) & 0xFF);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
INT LTR4X_SendCmdWithAck(TLTR *hnd, const DWORD *cmd, DWORD *ack, DWORD size) {
    INT err = ltr_module_send_cmd(hnd, cmd, size);

    if (err == LTR_OK)
        err = ltr_module_recv_cmd_resp(hnd, ack, size);

    if (err == LTR_OK) {
        DWORD i;
        for (i = 0; ((i < size) && (err == LTR_OK)); i++) {
            err = LTR4X_CheckAck(ack[i], cmd[i]);
        }
    }
    return err;
}

INT LTR4X_CheckVersion(const char *version_str, WORD min_ver) {
    int ver[2];
    sscanf(version_str, "%d.%d",&ver[0], &ver[1]);
    return ((ver[0] << 8) | ver[1]) >= min_ver ? LTR_OK : LTR_ERROR_UNSUP_BY_FIRM_VER;
}


INT LTR4X_SetStartMarkPulseTime(TLTR *hnd, DWORD time_mks, const char *ver_str) {
    INT err = LTR4X_CheckVersion(ver_str, LTR4X_START_PULSE_CFG_VER);
    if (err == LTR_OK) {
        BYTE fnd_scaler = 0;
        WORD fnd_prescaler = 0;
        DWORD cmd[3], ack[3];

        if (time_mks != 0) {
            double err_min = time_mks;
            BYTE i;

            for (i = 1; i < 255; i++) {
                DWORD presc_dw = (DWORD) (time_mks/(1.e6*(i+1)/F_OSC)+0.999999);
                WORD presc = MIN(presc_dw, 0xFFFF) & 0xFFFF;
                double time = (1.e6*(i+1)/F_OSC) * presc;
                double dt = time - time_mks;
                if (((presc == 1) || (i > 64)) &&  (dt >= 0) && (dt < err_min)) {
                    err_min = dt;
                    fnd_scaler = i;
                    fnd_prescaler = presc;
                }
            }
        }

        cmd[0] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_START_MARK_CNTR_L,
                               fnd_prescaler&0xFF);
        cmd[1] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_START_MARK_CNTR_H,
                               (fnd_prescaler>>8)&0xFF);
        cmd[2] = LTR_MODULE_FILL_CMD_PARITY_BYTES(CMD_CONFIG_PAR_EX, PARAM_START_MARK_SCALER,
                                fnd_scaler);
        err = LTR4X_SendCmdWithAck(hnd, cmd, ack, 3);
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
#ifdef STOP_STREAM_READ
INT LTR4X_StopStreamRead(TLTR *hnd) {
    /* Функция остановки потокового чтения данных из модуля */
    DWORD cmd = ltr_module_fill_cmd_parity(STOP_STREAM_READ, 0);
    return ltr_module_stop(hnd, &cmd, 1, cmd, LTR_MSTOP_FLAGS_CHECK_PARITY, 0,  NULL);
}
#endif

/*------------------------------------------------------------------------------------------------*/
INT LTR4X_WriteEEPROM(TLTR *hnd, INT Address, BYTE val) {
    INT err = (hnd == NULL) ? LTR_ERROR_PARAMETERS : LTR_OK;

    if (err == LTR_OK) {
        if ((Address < 0) || (Address >= LTR4X_EEPROM_SIZE))
            err = LTR_ERROR_FLASH_INVALID_ADDR;
    }

    if (err == LTR_OK) {
        /* передаем первую команду с адресом ячейки, а вторую - с данными.
         * ответ приходит ровно один
         */
        DWORD ack;
        DWORD command = ltr_module_fill_cmd_parity(WRITE_EEPROM, Address);
        err = ltr_module_send_cmd(hnd, &command, 1);
        if (err == LTR_OK) {
            command = ltr_module_fill_cmd_parity(WRITE_EEPROM, val);
            err = LTR4X_SendCmdWithAck(hnd, &command, &ack, 1);
        }
    }
    return err;
}

