#include "ltr/include/ltr24api.h"

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

#ifdef _WIN32
#include <locale.h>
#include <conio.h>
#else
#include <signal.h>
#include <unistd.h>
#endif

#include "getopt.h"


/*================================================================================================*/
/* количество отсчетов на канал, принмаемых за раз */
#define RECV_BLOCK_CH_SIZE  (4096 * 3)
/* таймаут на ожидание данных при приеме */
#define RECV_TOUT (4000)


/*================================================================================================*/
typedef struct {
    int slot;
    const char *serial;
    int reopen;
    int stop_card;
    int storeconfig_on;
    int autorun_en;
    TLTR_CARD_START_MODE start_mode;
    DWORD addr;

    unsigned char freq_rate;
} t_open_param;


/*================================================================================================*/
static int f_get_params(int argc, char **argv, t_open_param *par);

/*================================================================================================*/
/* признак необходимости завершить сбор данных */
static volatile int f_out = 0;


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
#ifndef _WIN32
/* Обработчик сигнала завершения для Linux */
static void f_abort_handler(int sig) {
    f_out = 1;
}
#endif


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
static int f_get_params(int argc, char **argv, t_open_param *par) {
    /* Разбор параметров командной строки. Если указано меньше, то используются
     * значения по умолчанию:
     * 1 параметр - номер слота (от 1 до 16)
     * 2 параметр - номер частоты дискретизации
     * 3 параметр - серийный номер крейта
     * 4 параметр - ip-адрес сервера
     */
    int optch;
    const char optstr[] = "p:ocgsmn:a:f:";

    par->slot = LTR_CC_CHNUM_MODULE1;
    par->serial = "";
    par->reopen = 0;
    par->stop_card = 1;
    par->storeconfig_on = 0;
    par->addr = LTRD_ADDR_DEFAULT;
    par->autorun_en = 0;
    par->start_mode = LTR_CARD_START_OFF;
    par->freq_rate = LTR24_FREQ_117K;


    while ((optch = getopt_long(argc, argv, optstr, NULL, NULL)) != -1) {
        switch (optch) {
        case 'p':
            par->slot = atoi(optarg);
            break;
        case 'o':
            par->autorun_en = 1;
            break;
        case 'c':
            par->reopen = 1;
            break;
        case 'g':
            par->stop_card = 0;
            break;
        case 's':
            par->storeconfig_on = 1;
            break;
        case 'm':
            par->start_mode = LTR_CARD_START_RUN;
            break;
        case 'n':
            par->serial = optarg;
            break;
        case 'a': {
            int a[4];
            int i;
            if (sscanf(optarg, "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3]) != 4) {
                fprintf(stderr, "Неверный формат адреса сервера!!\n");
                return -1;
            }
            for (i = 0; (i < 4); i++) {
                if ((a[i] < 0) || (255 < a[i])) {
                    fprintf(stderr, "Неверный формат адреса сервера!!\n");
                    return -1;
                }
            }
            par->addr = (a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3];
            break;
        }
        case 'f':
            par->freq_rate = atoi(optarg);
            break;
        default:
            fprintf(stderr, "Ошибка в опциях!!\n");
            return -1;
        }
    }


    return LTR_OK;
}

/*------------------------------------------------------------------------------------------------*/
int main(int argc, char *argv[]) {
    TLTR24 hltr24;
    t_open_param par;
    DWORD out_flags = 0;
    INT err = LTR_OK;
#ifndef _WIN32
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    /* В ОС Linux устанавливаем свой обработчик на сигнал закрытия,
     * чтобы завершить сбор корректно
     */
    sa.sa_handler = f_abort_handler;
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGABRT, &sa, NULL);
#endif

#ifdef _WIN32
    /* для вывода русских букв в консоль для ОС Windows*/
    setlocale(LC_ALL, "");
#endif

    err = f_get_params(argc, argv, &par);

    if (err == LTR_OK) {
        DWORD in_flags = par.reopen ? LTR_OPENINFLG_REOPEN : 0;
        LTR24_Init(&hltr24);
        /* устанавливаем соединение с модулем находящемся в первом слоте крейта.
         * для сетевого адреса, сетевого порта ltr-сервера и серийного номера
         * крейта используем значения по умолчанию
         */
        err = LTR24_OpenEx(&hltr24, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot, in_flags,
            &out_flags);
        //err = LTR24_Open(&hltr24, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot);
        if (err == LTR_OK) {
            printf("Модуль открыт успешно!\n");
            if (out_flags & LTR_OPENOUTFLG_REOPEN)
                printf("Было осуществлено подключение к работающему модулю\n");
        } else {
             fprintf(stderr, "Не удалось открыть модуль. Ошибка %d (%s)\n", err,
                 LTR24_GetErrorString(err));
        }
    }

    if (err == LTR_OK) {
        INT close_err;
        INT ch_cnt = 0;

        if (!(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* Читаем информацию о модуле, включая калибровки, из Flash-памяти
             * модуля. Без вызова этой функции нельзя будет получить правильные
             * откалиброванные значения
             */
            err = LTR24_GetConfig(&hltr24);
            if (err != LTR_OK) {
                fprintf(stderr,
                    "Не удалось прочитать информацию о модуле из Flash-памяти. Ошибка %d (%s)\n",
                    err, LTR24_GetErrorString(err));
            }
        }

        if (err == LTR_OK) {
            /* Выводим прочитанную информацию о модуле */
            printf("Модуль открыт успешно! Информация о модуле: \n");
            printf("  Название модуля    = %s\n", hltr24.ModuleInfo.Name);
            printf("  Серийный номер     = %s\n", hltr24.ModuleInfo.Serial);
            printf("  Версия PLD         = %d\n", hltr24.ModuleInfo.VerPLD);
            printf("  Поддержка ICP      = %s\n", hltr24.ModuleInfo.SupportICP ? "Есть" : "Нет");
            fflush(stdout);
        }

        /* Настройка модуля */
        if ((err == LTR_OK) && !(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* Формат - 24 или 20 битный */
            hltr24.DataFmt = LTR24_FORMAT_24;
            /* Устанавливаем частоту с помощью одной из констант (Для 24-битного режима
             * макс. частота только при 2-х каналах, все 4 - только пр 58)
             */
            hltr24.ADCFreqCode = par.freq_rate;
            /* Вкл./откл. тестовых режимов (измерение нуля/ICP-тест) */
            hltr24.TestMode = FALSE;

            /* Настройка режимов каналов */
            hltr24.ChannelMode[0].Enable = TRUE;
            hltr24.ChannelMode[0].Range = LTR24_RANGE_10;
            hltr24.ChannelMode[0].AC = FALSE;
            hltr24.ChannelMode[0].ICPMode = FALSE;

            hltr24.ChannelMode[1].Enable = TRUE;
            hltr24.ChannelMode[1].Range = LTR24_RANGE_2;
            hltr24.ChannelMode[1].AC = TRUE;
            hltr24.ChannelMode[1].ICPMode = FALSE;

            hltr24.ChannelMode[2].Enable = FALSE;
            hltr24.ChannelMode[3].Enable = FALSE;

            err = LTR24_SetADC(&hltr24);
            if (err != LTR_OK) {
                fprintf(stderr, "Не удалось установить настройки АЦП: Ошибка %d (%s)\n", err,
                    LTR24_GetErrorString(err));
            }
        }

        if (err == LTR_OK) {
            INT i;
            INT recv_len;
            DWORD *prbuf;
            const int sample_len = (hltr24.DataFmt == LTR24_FORMAT_24) ? 2 : 1;
            DWORD recvd_blocks = 0;
            INT recv_data_cnt = 0;
            INT recv_wrd_cnt = 0;
            DWORD *rbuf = NULL;
            double *data = NULL;
            BOOL *ovlds = NULL;

            /* подсчитываем кол-во разрешенных каналов */
            for (i = 0, ch_cnt = 0; (i < LTR24_CHANNEL_NUM); i++) {
                if (hltr24.ChannelMode[i].Enable)
                    ch_cnt++;
            }

            recv_data_cnt = RECV_BLOCK_CH_SIZE * ch_cnt;
            /* В 24-битном формате каждому отсчету соответствует два слова от модуля,
             * а в 20-битном - одно
             */
            recv_wrd_cnt = recv_data_cnt * sample_len;
            rbuf = malloc(recv_wrd_cnt*sizeof(rbuf[0]));
            data = malloc(recv_data_cnt*sizeof(data[0]));
            ovlds = malloc(recv_data_cnt*sizeof(ovlds[0]));

            /* после SetADC() обновляется поле AdcFreq. Становится равной действительной
             * установленной частоте
             */
            printf("Настройки АЦП установлены успешно. Частота = %.2f Гц, "
                "Кол-во каналов = %d\n", hltr24.ADCFreq, ch_cnt);

            if ((rbuf == NULL) || (data == NULL) || (ovlds == NULL)) {
                fprintf(stderr, "Ошибка выделения памяти!\n");
                err = LTR_ERROR_MEMORY_ALLOC;
            }

            if (err == LTR_OK) {
                if (!(out_flags & LTR_OPENOUTFLG_REOPEN)) {
                    /* Запуск сбора данных */
                    err = LTR24_Start(&hltr24);
                    if (err != LTR_OK) {
                        fprintf(stderr, "Не удалось запустить сбор данных! Ошибка %d (%s)\n", err,
                            LTR24_GetErrorString(err));
                    }
                    recv_len = recv_wrd_cnt;
                    prbuf = rbuf;
                } else {
                    const DWORD tout = RECV_TOUT +
                        (DWORD)(1000.0 * RECV_BLOCK_CH_SIZE / hltr24.ADCFreq + 1);

                    const INT recvd = LTR24_Recv(&hltr24, rbuf, NULL, recv_wrd_cnt, tout);

                    /* Значение меньше нуля соответствуют коду ошибки */
                    if (recvd < 0) {
                        err = recvd;
                        fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                            LTR24_GetErrorString(err));
                    } else {
                        INT offs;
                        if ((err = LTR24_FindFrameStart(&hltr24, rbuf, recvd, &offs)) == LTR_OK) {
                            prbuf = &rbuf[offs];
                            memmove(rbuf, prbuf, offs * sizeof(rbuf[0]));
                            recv_len = recv_wrd_cnt - offs;
                        } else {
                            fprintf(stderr, "Ошибка выравнивания данных. Ошибка %d:%s\n", err,
                                LTR24_GetErrorString(err));
                        }
                    }
                }
            }

            if (err == LTR_OK) {
                printf("Сбор данных запущен. Для останова нажмите %s\n",
#ifdef _WIN32
                       "любую клавишу"
#else
                       "CTRL+C"
#endif
                       );
                fflush(stdout);
            }


            /* ведем сбор данных до возникновения ошибки или до
             * запроса на завершение
             */
            while (!f_out && (err == LTR_OK)) {
                INT recvd;
                /* В таймауте учитываем время сбора запрашиваемого числа отсчетов */
                DWORD tout = RECV_TOUT +
                    (DWORD)(1000.0 * RECV_BLOCK_CH_SIZE / hltr24.ADCFreq + 1);

                /* Прием данных от модуля.  */
                recvd = LTR24_Recv(&hltr24, prbuf, NULL, recv_len, tout);

                /* Значение меньше нуля соответствуют коду ошибки */
                if (recvd < 0) {
                    err = recvd;
                    fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                        LTR24_GetErrorString(err));
                } else if (recvd != recv_len) {
                    fprintf(stderr, "Принято недостаточно данных. Запрашивали %d, приняли %d\n",
                        recv_len, recvd);
                    err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                } else {
                    err = LTR24_ProcessData(&hltr24, rbuf, data, &recvd,
                        LTR24_PROC_FLAG_VOLT|LTR24_PROC_FLAG_CALIBR|LTR24_PROC_FLAG_AFC_COR,
                        ovlds);
                    if (err != LTR_OK) {
                        fprintf(stderr, "Ошибка обработки данных. Ошибка %d:%s\n", err,
                            LTR24_GetErrorString(err));
                    } else {
                        unsigned cur_pos, ch;
                        recvd_blocks++;
                        /* выводим по первому слову на канал */
                        printf("Блок %4d:", recvd_blocks);
                        for (ch = 0, cur_pos = 0; (ch < LTR24_CHANNEL_NUM); ch++) {
                            if (hltr24.ChannelMode[ch].Enable) {
                                printf(" Канал %d = %9.6f", ch+1, data[cur_pos]);
                                cur_pos++;
                            }
                        }
                        printf("\n");

                        fflush(stdout);
                    }
                }

#ifdef _WIN32
                /* проверка нажатия клавиши для выхода */
                if (err == LTR_OK) {
                    if (_kbhit())
                        f_out = 1;
                }
#endif
                recv_len = recv_wrd_cnt;
                prbuf = rbuf;
            } /*while (!f_out && (err == LTR_OK))*/

            /* по завершению останавливаем сбор, если был запущен */
            if (hltr24.Run) {
                if (par.stop_card) {
                    INT stop_err = LTR24_Stop(&hltr24);
                    if (stop_err != LTR_OK) {
                        fprintf(stderr, "Не удалось остановить сбор данных. Ошибка %d:%s\n",
                            stop_err, LTR24_GetErrorString(stop_err));
                        if (err == LTR_OK)
                            err = stop_err;
                    } else {
                        printf("Сбор остановлен успешно.\n");
                    }
                } else {
                    printf("Работа программы завершена. Модуль не остановлен.\n");
                }
            }
        } /*if (err == LTR_OK)*/

        if (((err == LTR_OK) || (err == LTR_ERROR_RECV_INSUFFICIENT_DATA)) && par.storeconfig_on) {
            TLTR_SETTINGS cfg;
            TLTR h_ltr;

            printf("Сохранение конфигурации модуля и крейта\n");
            if ((err = LTR24_StoreConfig(&hltr24, par.start_mode)) != LTR_OK) {
                fprintf(stderr, "Не удалось сохранить конфигурацию модуля. Ошибка %d (%s)\n", err,
                    LTR24_GetErrorString(err));
            } else {
                printf("LTR24_StoreConfig()->OK\n");
            }

            if (err == LTR_OK) {
                if ((err = LTR_Init(&h_ltr)) != LTR_OK) {
                    fprintf(stderr, "Не удалось проинициализировать дескриптор крейта. "
                        "Ошибка %d (%s).\n", err, LTR_GetErrorString(err));
                } else {
                    printf("LTR_Init()->OK\n");
                }
            }

            if (err == LTR_OK) {                
                if ((err = LTR_OpenCrate(&h_ltr, hltr24.Channel.saddr, hltr24.Channel.sport,
                                         LTR_CRATE_IFACE_UNKNOWN, hltr24.Channel.csn)) != LTR_OK) {
                    fprintf(stderr, "Не удалось открыть соединение с крейтом. Ошибка %d (%s).\n", err,
                        LTR_GetErrorString(err));
                } else {
                    printf("LTR_OpenCrate()->OK\n");
                }
            }

            if (err == LTR_OK) {
                cfg.size = sizeof(TLTR_SETTINGS);
                cfg.autorun_ison = par.autorun_en;
                if ((err = LTR_PutSettings(&h_ltr, &cfg)) != LTR_OK) {
                    fprintf(stderr, "Не удалось сохранить конфигурацию крейта. Ошибка %d (%s).\n", err,
                        LTR_GetErrorString(err));
                    LTR_Close(&h_ltr);
                } else {
                    printf("LTR_PutSettings()->OK\n");
                }
            }

            if ((err = LTR_Close(&h_ltr)) != LTR_OK) {
                fprintf(stderr, "Не удалось закрыть соединение с крейтом. Ошибка %d (%s).\n", err,
                    LTR_GetErrorString(err));
            } else {
                printf("LTR_Close()->OK\n");
            }
        } /*if (par.storeconfig_on)*/

        /* закрываем связь с модулем */
        close_err = LTR24_Close(&hltr24);
        if (close_err != LTR_OK) {
            fprintf(stderr, "Не удалось закрыть связь с модулем. Ошибка %d:%s\n", close_err,
                LTR24_GetErrorString(close_err));
            if (err == LTR_OK)
                err = close_err;
        } else {
            printf("Связь с модулем успешно закрыта.\n");
        }
    } /*if (err == LTR_OK)*/

    return err;
}
