
#include "ltr032api.h"
#include "ltrapi.h"
#include "ltrd_protocol_defs.h"

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

#define ADDR_RELAY              0x82010040UL
#define ADDR_TEMP_LOWER         0x82010030UL
#define ADDR_TEMP_UPPER         0x82010031UL
#define ADDR_RESET              0x82010050UL
#define ADDR_START_BOOT         0x82010051UL
#define ADDR_SUPP_VOLT          0x82010020UL
#define ADDR_TIME               0x82010000UL
#define ADDR_NAMUR_LOWER        0x82010010UL
#define ADDR_NAMUR_HYSTER       0x82010011UL

#define ERR_STR_LEN             128
#define IO_TIMEOUT              5000

struct ltr032 {
    TLTR    chan;
    char    err_str[ERR_STR_LEN];
};

static const TLTR_ERROR_STRING_DEF f_err_tbl[] =
{
    {LTR032_ERR_INVAL_CRATE_TYPE,       "Неверный тип контроллера крейта"},
    {LTR032_ERR_CMD_REJECTED,           "Контроллер крейта не принял команду"},
    {LTR032_ERR_INVALID_M1S_OUT_MODE,   "Неверно задан режим работы выхода M1S OUT"},
    {LTR032_ERR_INVALID_NAMUR_LEVELS,   "Неверно заданы пороговые уровни NAMUR"},
    {LTR032_ERR_INVALID_THERM_SENS_IND, "Неверно задан индекс термодатчика"}
};

LTR032API_DllExport(DWORD) ltr032_get_version(void)
{
    return LTR032_CURR_VERS_CODE;
}

LTR032API_DllExport(struct ltr032 *) ltr032_init(void)
{
    struct ltr032 *ltr032;

    ltr032 = (struct ltr032 *) malloc(sizeof(*ltr032));
    if (ltr032 == NULL) {
        return NULL;
    }
    memset(ltr032, 0, sizeof(*ltr032));
    if (LTR_Init(&ltr032->chan) != LTR_OK) {
        free(ltr032);
        return NULL;
    }

    return ltr032;
}

LTR032API_DllExport(void) ltr032_exit(struct ltr032 *ltr032)
{
    if (ltr032 != NULL) {
        if (ltr032_is_open(ltr032)) {
            ltr032_close(ltr032);
        }
        free(ltr032);
    }
}

static void set_error_string(struct ltr032 *ltr032, INT err)
{
    if (ltr032!=NULL)
    {
        const char *str=NULL;

        size_t i;
        for (i = 0; (i < sizeof(f_err_tbl) / sizeof(f_err_tbl[0]))
             && (str!=NULL); i++)
        {
            if (f_err_tbl[i].code == err)
                str = f_err_tbl[i].message;
        }

        if (str==NULL)
            str = LTR_GetErrorString(err);

        strncpy(ltr032->err_str, str, ERR_STR_LEN - 1);
        ltr032->err_str[ERR_STR_LEN - 1] = '\0';
    }
}

static INT generic_ctl_wrapper(struct ltr032 *ltr032, void *req, int req_len, void *reply, int reply_len)
{
    INT res;

    res = LTR__GenericCtlFunc(&ltr032->chan, req, req_len, reply, reply_len, -1, IO_TIMEOUT);
    if (res == -1) {
        res = LTR032_ERR_CMD_REJECTED;
        set_error_string(ltr032, res);
    } else if (res != LTR_OK) {
        set_error_string(ltr032, res);
    }

    return res;
}

static INT check_crate_is_open(struct ltr032 *ltr032)
{
    return ltr032 == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_IsOpened(&ltr032->chan);
}

LTR032API_DllExport(INT) ltr032_open(struct ltr032 *ltr032, DWORD ip_addr, WORD port,
                          const char *serial)
{
    TLTR_CRATE_INFO info;
    INT res = ltr032 == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if (res==LTR_OK)
        res = ltr032_close(ltr032);
    if (res == LTR_OK)
    {
        ltr032->chan.cc = LTR_CC_CHNUM_CONTROL;
        ltr032->chan.saddr = ip_addr;
        ltr032->chan.sport = port;

        if (serial != NULL)
        {
            strncpy(ltr032->chan.csn, serial, LTR_CRATE_SERIAL_SIZE);
            ltr032->chan.csn[LTR_CRATE_SERIAL_SIZE-1] = '\0';
        }
        else
        {
            ltr032->chan.csn[0] = '\0';
        }

        res = LTR_Open(&ltr032->chan);
        if (res==LTR_OK)
        {
            res = LTR_GetCrateInfo(&ltr032->chan, &info);
            if ((res==LTR_OK) && (info.CrateType != LTR_CRATE_TYPE_LTR032))
            {
                res = LTR032_ERR_INVAL_CRATE_TYPE;
                ltr032_close(ltr032);
            }
        }
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_is_open(struct ltr032 *ltr032)
{
    if (ltr032 == NULL) {
        return 0;
    }
    return LTR_IsOpened(&ltr032->chan) == LTR_OK ? 1 : 0;
}

LTR032API_DllExport(INT) ltr032_close(struct ltr032 *ltr032)
{
    INT res = ltr032 == NULL ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_Close(&ltr032->chan);
    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_m1sout_mode(struct ltr032 *ltr032, BYTE mode)
{
    INT res = check_crate_is_open(ltr032);

    if ((res==LTR_OK) && (mode!=LTR032_M1SOUT_MODE_OPEN) &&
            (mode != LTR032_M1SOUT_MODE_CLOSE) &&
            (mode != LTR032_M1SOUT_MODE_CLOSE_PULSE) &&
            (mode != LTR032_M1SOUT_MODE_OPEN_PULSE))
    {
        res = LTR032_ERR_INVALID_M1S_OUT_MODE;
    }

    if (res==LTR_OK)
    {
        struct {
            DWORD   hdr[4];
            BYTE    data[1];
        } req = {
            {CONTROL_COMMAND_START, CONTROL_COMMAND_PUT_ARRAY, ADDR_RELAY, 1},
            {0}
        };
        req.data[0] = mode;

        res = generic_ctl_wrapper(ltr032, &req, 17, NULL, 0);
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

/** Возвращает режим работы выхода M1S_OUT. */
LTR032API_DllExport(INT) ltr032_get_m1sout_mode(struct ltr032 *ltr032, BYTE *mode)
{
    INT res = mode==NULL ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_ARRAY, ADDR_RELAY, 1};
        res = generic_ctl_wrapper(ltr032, req, 16, mode, 1);
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_startin_namur(struct ltr032 *ltr032, float lower, float upper)
{
    INT res = check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        float diff = upper - lower;
        if (lower < 0 || lower > 2.13 || diff < (0.813 / 256) || diff > 0.813)
            res = LTR032_ERR_INVALID_NAMUR_LEVELS;
    }

    if (res==LTR_OK)
    {
        struct {
            DWORD   hdr[4];
            BYTE    data[1];
        } req = {
            {CONTROL_COMMAND_START, CONTROL_COMMAND_PUT_ARRAY, 0, 1},
            {0}
        };

        req.hdr[2] = ADDR_NAMUR_LOWER;
        req.data[0] = (BYTE) (256 * (1 - lower / 2.13));
        res = generic_ctl_wrapper(ltr032, &req, 17, NULL, 0);

        if (res==LTR_OK)
        {
            req.hdr[2] = ADDR_NAMUR_HYSTER;
            req.data[0] = (BYTE) (256 * (upper - lower) / 0.813);
            res = generic_ctl_wrapper(ltr032, &req, 17, NULL, 0);
        }
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_startin_namur(struct ltr032 *ltr032, float *lower, float *upper)
{
    INT res = (lower == NULL) && (upper == NULL) ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        BYTE code;
        float low;
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_ARRAY, 0, 1};

        req[2] = ADDR_NAMUR_LOWER;
        res = generic_ctl_wrapper(ltr032, req, 16, &code, 1);
        if (res==LTR_OK)
        {
            low = 2.13F * (256 - code) / 256;
            if (lower != NULL)
            {
                *lower = low;
            }
            req[2] = ADDR_NAMUR_HYSTER;
            res = generic_ctl_wrapper(ltr032, req, 16, &code, 1);
        }
        if (res==LTR_OK)
        {
            if (upper != NULL)
                *upper = low + 0.813F * code / 256;
        }
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_temperature(struct ltr032 *ltr032, BYTE sens, float *temp)
{
    INT res = (temp == NULL) ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if ((res==LTR_OK) && (sens > 1))
    {
        res = LTR032_ERR_INVALID_THERM_SENS_IND;
    }
    if (res==LTR_OK)
    {
        static DWORD addr[2] = {ADDR_TEMP_LOWER, ADDR_TEMP_UPPER};
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_ARRAY, 0, 2};
        WORD code;

        req[2] = addr[sens];
        res = generic_ctl_wrapper(ltr032, req, 16, &code, 2);
        if (res==LTR_OK)
            *temp = code / 32.0F;
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_supply_voltage(struct ltr032 *ltr032, float *volt)
{
    INT res = (volt == NULL) ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_ARRAY, ADDR_SUPP_VOLT, 2};
        WORD code;

        res = generic_ctl_wrapper(ltr032, req, 16, &code, 2);
        if (res==LTR_OK)
        {
            *volt = code * 19.868F / 4096;
        }
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_time(struct ltr032 *ltr032, ULONGLONG ptime)
{
    INT res = check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        struct {
            DWORD   hdr[4];
            ULONGLONG   data[1];
        } req = {
            {CONTROL_COMMAND_START, CONTROL_COMMAND_PUT_ARRAY, ADDR_TIME, 8},
            {0}
        };

        memcpy(req.data, &ptime, sizeof(ptime));
        res = generic_ctl_wrapper(ltr032, &req, 24, NULL, 0);
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_time(struct ltr032 *ltr032, ULONGLONG *ptime)
{
    INT res = (ptime == NULL) ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_GET_ARRAY, ADDR_TIME, 8};

        res = generic_ctl_wrapper(ltr032, req, 24, ptime, 8);
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(const char *) ltr032_get_error_string(struct ltr032 *ltr032)
{
    return ltr032 == NULL ? LTR_GetErrorString(LTR_ERROR_INVALID_MODULE_DESCR) : ltr032->err_str;
}

LTR032API_DllExport(INT) ltr032_get_legacy_interface(struct ltr032 *ltr032, void *ltr)
{
    INT res = (ltr == NULL) ? LTR_ERROR_PARAMETERS : check_crate_is_open(ltr032);
    if (res==LTR_OK)
        memcpy(ltr, &ltr032->chan, sizeof(ltr032->chan));

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_reset(struct ltr032 *ltr032)
{
    INT res = check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_PUT_ARRAY, ADDR_RESET, 0};

        res = generic_ctl_wrapper(ltr032, req, 16, NULL, 0);
    }

    if (res!=LTR_OK)
        set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_start_boot(struct ltr032 *ltr032)
{
    INT res = check_crate_is_open(ltr032);
    if (res==LTR_OK)
    {
        DWORD req[4] = {CONTROL_COMMAND_START, CONTROL_COMMAND_PUT_ARRAY, ADDR_START_BOOT, 0};
        res = generic_ctl_wrapper(ltr032, req, 16, NULL, 0);
    }
    return res;
}

LTR032API_DllExport(INT) ltr032_set_ip_addr(struct ltr032 *ltr032, DWORD ip_addr)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_ip_addr(struct ltr032 *ltr032, DWORD *ip_addr)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_netmask(struct ltr032 *ltr032, DWORD mask)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_netmask(struct ltr032 *ltr032, DWORD *mask)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_gateway(struct ltr032 *ltr032, DWORD gw)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_gateway(struct ltr032 *ltr032, DWORD *gw)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_mac_addr(struct ltr032 *ltr032, BYTE *mac)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_mac_addr(struct ltr032 *ltr032, BYTE *mac)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_set_use_custom_mac_addr(struct ltr032 *ltr032, BYTE use)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}

LTR032API_DllExport(INT) ltr032_get_use_custom_mac_addr(struct ltr032 *ltr032, BYTE *use)
{
    INT res = LTR_ERROR_NOT_IMPLEMENTED;
    set_error_string(ltr032, res);
    return res;
}
