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

#include "ltrmodule.h"

#include "ltimer.h"


/*================================================================================================*/
static INT f_eval_parity(DWORD cmd);


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
static INT f_eval_parity(DWORD cmd) {
    /* Расчет бита четности команды. Исключаются из расчета биты 15..5.
     * Алгоритм был улучшен по рекомендации одного из наших пользователей
     */
    cmd &= 0xFFFF001FUL;

    cmd ^= cmd >> 16;
    cmd ^= cmd >> 8;
    cmd ^= cmd >> 4;
    cmd ^= cmd >> 2;
    cmd ^= cmd >> 1;

    return cmd & 1;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_check_parity(DWORD cmd) {
    return (((cmd & 0x20) >> 5) == (DWORD)f_eval_parity(cmd)) ? LTR_OK :
        LTR_ERROR_INVALID_RESP_PARITY;
}

/*------------------------------------------------------------------------------------------------*/
DWORD ltr_module_fill_cmd_parity(WORD cmd, WORD param) {
    DWORD ret_cmd;
    ret_cmd = LTR010CMD | ((DWORD)param << 16) | ((DWORD)cmd);
    ret_cmd |= f_eval_parity(ret_cmd) << 5; /* подсчет четности команды (бит P) */
    return ret_cmd;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_open(TLTR *hnd, DWORD ltrd_addr, WORD ltrd_port, const CHAR *csn, INT slot_num,
    WORD mid, DWORD *flags, DWORD *ack, INT *warn) {
    INT err = (hnd == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR :
        ((slot_num == LTR_CC_CHNUM_CONTROL) || (slot_num > LTR_MODULES_PER_CRATE_MAX)) ?
                                                LTR_ERROR_INVALID_MODULE_SLOT : LTR_OK;
    DWORD inflags = (flags == NULL) ? 0 : *flags;
    INT warning = LTR_OK;

    /* если канал был открыт - закрываем его */
    if (err == LTR_OK) {
        if (LTR_IsOpened(hnd) == LTR_OK)
            err = LTR_Close(hnd);
    }

    if (err == LTR_OK) {
        /* Устанавливаем параметры канала */
        if (ltrd_addr)
            hnd->saddr = ltrd_addr;
        if (ltrd_port)
            hnd->sport = ltrd_port;

        if (csn != NULL) {
            strncpy(hnd->csn, csn, LTR_CRATE_SERIAL_SIZE - 1);
            hnd->csn[LTR_CRATE_SERIAL_SIZE - 1] = '\0';
        } else {
            memset(hnd->csn, 0, LTR_CRATE_SERIAL_SIZE);
        }

        hnd->cc = slot_num;
        /* Открываем созданный интерфейсный канал связи с модулем */
        err = LTR_Open(hnd);
        if (err == LTR_WARNING_MODULE_IN_USE) {
            warning = err;
            err = LTR_OK;
        }
    }

    if (!(inflags & LTR_MOPEN_INFLAGS_DONT_RESET)) {
#ifdef LTR_FORCE_RESET_WHEN_IN_USE
        if (err == LTR_OK) {
#else
        if ((err == LTR_OK) && !warning) {
#endif
            /* посылаем STOP-RESET */
            DWORD command[] = {LTR010CMD_STOP, LTR010CMD_STOP, LTR010CMD_RESET};
            err = ltr_module_send_cmd(hnd, command, sizeof(command) / sizeof(command[0]));
            if (err == LTR_OK) {
                /* ждем подтверждения на STOP с командой */
                DWORD rbuf[128];
                t_ltimer tmr;
                int ack_recv = 0;
                ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(LTR_MODULE_RESET_TIMEOUT));

                while ((err == LTR_OK) && !ack_recv) {
                    /* Загружаем данные, имеющиеся в FIFO */
                    INT transf_res = LTR_Recv(hnd, rbuf, NULL, sizeof(rbuf)/sizeof(rbuf[0]), 10);
                    if (transf_res < 0) {
                        err = transf_res;
                    } else if (transf_res > 0) {
                        /* проверка правильности подтверждения */
                        if ((rbuf[transf_res-1] & 0xF0C0) == LTR010CMD_RESET) {
                            /* если был указан ID модуля, то проверяем, что в ответе на RESET пришел
                             * правильный ID
                             */
                            if (mid && (((rbuf[transf_res-1] >> 16) & 0xFFFFUL) != mid)) {
                                err = LTR_ERROR_INVALID_MODULE_ID;
                            } else {
                                ack_recv = 1;
                            }
                            /* сохраняем ответ на RESET, если нужен */
                            if (ack)
                                *ack = rbuf[transf_res-1];
                        }
                    }
                    if (ltimer_expired(&tmr) && !ack_recv)
                        err = LTR_ERROR_NO_RESET_RESPONSE;
                }
            }

            if ((err == LTR_OK) && !(inflags & LTR_MOPEN_INFLAGS_DEF_SEC_STOP))
                err = ltr_module_send_cmd(hnd, command, 1);
        } else {
            if (flags != NULL)
                *flags |= LTR_MOPEN_OUTFLAGS_DONT_INIT;
        }
    } else if (flags != NULL) {
        *flags |= LTR_MOPEN_OUTFLAGS_DONT_INIT;
    }

    if ((err != LTR_OK) && (LTR_IsOpened(hnd) == LTR_OK)) {
        LTR_Close(hnd);
    }

    if (warn != NULL)
        *warn = warning;
    return err;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_recv_cmd_resp(TLTR *ltr, DWORD *data, DWORD size) {
    return ltr_module_recv_cmd_resp_tout(ltr, data, size, LTR_MODULE_CMD_RECV_TIMEOUT);
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_recv_cmd_resp_tout(TLTR *ltr, DWORD *data, DWORD size, DWORD tout) {
    INT res = LTR_Recv(ltr, data, NULL, size, tout);
    return (res < 0) ? res : ((DWORD)res == size) ? LTR_OK : LTR_ERROR_NO_CMD_RESPONSE;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_send_cmd(TLTR *ltr, const DWORD *data, DWORD size) {
    INT res = LTR_Send(ltr, data, size, LTR_MODULE_CMD_SEND_TIMEOUT);
    return (res < 0) ? res : ((DWORD)res == size) ? LTR_OK : LTR_ERROR_SEND_INSUFFICIENT_DATA;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_send_with_echo_resps_tout(TLTR* ltr, const DWORD *cmds, DWORD size, DWORD *resp, DWORD tout) {
    INT err;
    DWORD i;

    err = ltr_module_send_cmd(ltr, cmds, size);
    if (err == LTR_OK)
        err = ltr_module_recv_cmd_resp_tout(ltr, resp, size, tout);
    for (i = 0; ((i < size) && (err == LTR_OK)); i++) {
        if (LTR_MODULE_CMD_GET_CMDCODE(cmds[i]) != LTR_MODULE_CMD_GET_CMDCODE(resp[i]))
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
    }
    return err;
}

INT ltr_module_send_single_with_echo(TLTR *ltr, DWORD cmd) {
    DWORD ack;
    return ltr_module_send_with_echo_resps(ltr, &cmd, 1, &ack);
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_send_with_single_resp(TLTR *ltr, const DWORD *cmds, DWORD size, DWORD *resp) {
    DWORD recv_resp;
    INT err;

    err = ltr_module_send_cmd(ltr, cmds, size);
    if (err == LTR_OK)
        err = ltr_module_recv_cmd_resp(ltr, &recv_resp, 1);
    if (err == LTR_OK) {
        if (LTR_MODULE_CMD_GET_CMDCODE(*resp) != LTR_MODULE_CMD_GET_CMDCODE(recv_resp)) {
            err = LTR_ERROR_INVALID_CMD_RESPONSE;
        } else {
            *resp = recv_resp;
        }
    }
    return err;
}

/*------------------------------------------------------------------------------------------------*/
INT ltr_module_stop_tout(TLTR *ltr, const DWORD *stop_cmd, DWORD stop_cmd_cnt,
                         DWORD ack_cmd, DWORD flags, DWORD msk, DWORD tout, DWORD *ack) {
    INT err = (ltr == NULL) ? LTR_ERROR_INVALID_MODULE_DESCR : LTR_OK;
    if ((err == LTR_OK) && (stop_cmd_cnt != 0))
        err = ltr_module_send_cmd(ltr, stop_cmd, stop_cmd_cnt);

    if (err == LTR_OK) {
        err = ltr_module_recv_fnd_ack(ltr, ack_cmd, flags, msk, tout, ack);
    }
    return err;
}

INT ltr_module_recv_fnd_ack(TLTR *ltr, DWORD ack_cmd, DWORD flags, DWORD msk, DWORD tout, DWORD *ack) {
    INT err = LTR_OK;
    DWORD rbuf[128];
    t_ltimer tmr;
    int ack_recv = 0;
    if (!(flags & LTR_MSTOP_FLAGS_SPEC_MASK))
        msk = LTR_MODULE_CMD_CODE_MSK;

    ltimer_set(&tmr, LTIMER_MS_TO_CLOCK_TICKS(tout));

    while (!ack_recv && (err == LTR_OK)) {
        /* Загружаем данные, имеющиеся в FIFO */
        INT read_cntr = LTR_Recv(ltr, rbuf, NULL, sizeof(rbuf)/sizeof(rbuf[0]), 100);
        if (read_cntr < 0) {
            err = read_cntr;
        } else if (read_cntr > 0) {
            INT i;
            for (i = 0; (i < read_cntr) && (err == LTR_OK) && !ack_recv; ++i) {
                if ((rbuf[i] & msk) == (ack_cmd & msk)) {
                    ack_recv = 1;
                    if ((i != (read_cntr - 1)) && (rbuf[read_cntr-1] & msk) != (ack_cmd & msk)) {
                        err = LTR_ERROR_UNEXP_DATA_AFTER_ACK;
                    }
                }
            }
        }

        if (!ack_recv && ltimer_expired(&tmr))
            err = LTR_ERROR_NO_CMD_RESPONSE;

        if (ack_recv && (ack != NULL))
            *ack = rbuf[read_cntr-1];

        if (ack_recv && (flags & LTR_MSTOP_FLAGS_CHECK_PARITY)) {
            err = ltr_module_check_parity(rbuf[read_cntr-1]);
        }
    }
    return err;

}


