/*
    Данный пример демонстрирует работу с модулем LTR24.
    По умолчанию идет работа с первым слотом первого крейта, но это можно изменить
        через параметры командной строки:
    ltr25_recv  slot crate_serial srvip
    где:
        slot         - номер слота (от 1 до 16)
        crate_serial - серийный номер крейта (если крейтов больше одного)
        srvip        - ip-адрес LTR-сервера (если он запущен на другой машине)
    Параметры опциональны: можно указать только слот, слот и серийный номер крейта или все

    Пример принимает данные от первых двух каналов на максимальной частоте сбора.
    На экране отображается только по первому отсчету каждого принятого блока.

    Пользователь может изменить настройки на свои, поменяв заполнение полей
    структуры перед запуском сбора.

    Сбор идет до нажатия любой клавиши на Windows или  CTRL+C на Linux

    Сборка в VisualStudio:
    Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
    -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
    на нужный в зависимаости от пути установки библиотек (ltr24api.h  и остальные заголовочные
    файлы должны находится в поддиректории ltr/include относительно указанного пути)
    и измените путь к .lib файлам на <путь установки библиотек>/lib/msvc
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties) ->
    Компоновщик (Linker) -> Общие (General) -> Дополнительные каталоги библиотек (Additional Library Directories)).

    Внимание!: Если Вы собираете проект под Visual Studio и у Вас некорректно
    отображается вывод русских букв, то нужно изменить кодировку:
    выберите Файл (File) -> Дополнительные параметры сохранения (Advanced Save Options)...
    и в поле Кодировка (Encoding) выберите Юникод (UTF8, с сигнатурой)/Unicode (UTF-8 with signature)
    и сохраните изменения в файле.
*/


#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 "ltr/include/ltr25api.h"

#include "getopt.h"


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


/*================================================================================================*/
struct Options {
    int slot;
    int reopen;
    int stop_card;
    int autorun_en;
    TLTR_CARD_START_MODE start_mode;
    int storeconfig_on;
};


/*================================================================================================*/
static const int err_any = LTR_OK + 1;
static const int err_none = LTR_OK;
/* признак необходимости завершить сбор данных */
static int f_out = 0;


/*================================================================================================*/
static int parse_opts(int argc, char *argv[], struct Options *opts);


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

/*------------------------------------------------------------------------------------------------*/
static int parse_opts(int argc, char *argv[], struct Options *opts) {
    int optch;
    const char optstr[] = "p:rgRMCt";

    /* Set default options values */
    opts->slot = LTR_CC_CHNUM_MODULE1;
    opts->autorun_en = 0;
    opts->reopen = 0;
    opts->stop_card = 1;
    opts->start_mode = LTR_CARD_START_OFF;
    opts->storeconfig_on = 0;

    while ((optch = getopt_long(argc, argv, optstr, NULL, NULL)) != -1) {
        switch (optch) {
        case 'p':
            opts->slot = atoi(optarg);
            break;
        case 'r':
            opts->reopen = 1;
            break;
        case 'g':
            opts->stop_card = 0;
            break;
        case 'R':
            opts->autorun_en = 1;
            break;
        case 'M':
            opts->start_mode = LTR_CARD_START_RUN;
            break;
        case 'C':
            opts->storeconfig_on = 1;
            break;
        default:
            return err_any;
        }
    }

    return err_none;
}

/*------------------------------------------------------------------------------------------------*/
int main(int argc, char *argv[]) {
    TLTR25 hltr25;
    struct Options opts;
    DWORD out_flags = 0;
    INT err = err_none;
#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

    if ((err = parse_opts(argc, argv, &opts)) != err_none) {
        fprintf(stderr, "Неверно заданные опции!\n");
        goto cleanup;
    }

    if (err == LTR_OK) {
        DWORD in_flags = opts.reopen ? LTR_OPENINFLG_REOPEN : 0;
        LTR25_Init(&hltr25);
        /* Устанавливаем соединение с модулем */
        err = LTR25_OpenEx(&hltr25, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT, "", opts.slot, in_flags,
            &out_flags);
        if (err == LTR_OK) {
            printf("Модуль открыт успешно!\n");
            if (out_flags & LTR_OPENOUTFLG_REOPEN)
                printf("Было осуществлено подключение к работающему модулю\n");
        } else {
             fprintf(stderr, "Не удалось открыть модуль. Ошибка %d (%s)\n", err,
                 LTR25_GetErrorString(err));
        }
    }

    if (err == LTR_OK) {
        INT close_err;

        /* Выводим прочитанную информацию о модуле */
        printf("Информация о модуле: \n");
        printf("  Название модуля    = %s\n", hltr25.ModuleInfo.Name);
        printf("  Серийный номер     = %s\n", hltr25.ModuleInfo.Serial);
        printf("  Версия PLD         = %d\n", hltr25.ModuleInfo.VerPLD);
        printf("  Версия ПЛИС        = %d\n", hltr25.ModuleInfo.VerFPGA);
        printf("  Ревизия платы      = %d\n", hltr25.ModuleInfo.BoardRev);
        printf("  Темп. диапазон     = %s\n", hltr25.ModuleInfo.Industrial ? "Индустриальный" :
            "Коммерческий");
        fflush(stdout);

        if (err == LTR_OK) {
            if (!(out_flags & LTR_OPENOUTFLG_REOPEN)) {
                /* Настройка модуля */
                /* Формат - 32 или 20 битный */
                hltr25.Cfg.DataFmt = LTR25_FORMAT_20;
                /* Устанавливаем частоту с помощью одной из констант */
                hltr25.Cfg.FreqCode = LTR25_FREQ_78K;
                /* Источник тока 2.86 или 10 мА */
                hltr25.Cfg.ISrcValue = LTR25_I_SRC_VALUE_2_86;

                /* Настройка режимов каналов */
                hltr25.Cfg.Ch[0].Enabled = TRUE;
                hltr25.Cfg.Ch[1].Enabled = TRUE;

                err = LTR25_SetADC(&hltr25);
                if (err != LTR_OK) {
                    fprintf(stderr, "Не удалось установить настройки АЦП: Ошибка %d (%s)\n", err,
                        LTR25_GetErrorString(err));
                } else {
                    /* после SetADC() обновляется поле AdcFreq. Становится равной действительной
                     * установленной частоте */
                    printf("Настройки АЦП установленны успешно."
                        " Частота = %.2f Гц, Кол-во каналов = %d\n", hltr25.State.AdcFreq,
                        hltr25.State.EnabledChCnt);
                }
            } else {
                printf("Настройки АЦП считаны из модуля. Частота = %.2f Гц, Кол-во каналов = %d\n",
                    hltr25.State.AdcFreq, hltr25.State.EnabledChCnt);
                printf("FreqCode = %i\n", hltr25.Cfg.FreqCode);
            }
        }

        if (err == LTR_OK) {
            INT recv_len = 0;
            DWORD *prbuf = NULL;
            const int sample_len = (hltr25.Cfg.DataFmt == LTR25_FORMAT_32) ? 2 : 1;
            DWORD recvd_blocks = 0;
            INT recv_data_cnt = RECV_BLOCK_CH_SIZE * hltr25.State.EnabledChCnt;
            /* В 24-битном формате каждому отсчету соответствует два слова от модуля,
               а в 20-битном - одно */
            INT recv_wrd_cnt = recv_data_cnt * sample_len;
            DWORD *rbuf = (DWORD *)malloc(recv_wrd_cnt*sizeof(rbuf));
            double *data = (double *)malloc(recv_data_cnt*sizeof(data[0]));
            DWORD *ch_status = malloc(hltr25.State.EnabledChCnt*sizeof(ch_status[0]));

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

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

                    const INT recvd = LTR25_Recv(&hltr25, rbuf, NULL, recv_wrd_cnt, tout);

                    /* Значение меньше нуля соответствуют коду ошибки */
                    if (recvd < 0) {
                        err = recvd;
                        fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                            LTR25_GetErrorString(err));
                    } else {
                        DWORD offs;
                        if ((err = LTR25_SearchFirstFrame(&hltr25, 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,
                                LTR25_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 / hltr25.State.AdcFreq + 1);

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

                /* Значение меньше нуля соответствуют коду ошибки */
                if (recvd < 0) {
                    err = recvd;
                    fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                        LTR25_GetErrorString(err));
                } else if (recvd != recv_len) {
                    fprintf(stderr, "Принято недостаточно данных. Запрашивали %d, приняли %d\n",
                        recv_len, recvd);
                    err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                } else {
                    recvd /= 2;
                    err = LTR25_ProcessData(&hltr25, rbuf, data, &recvd,
                        LTR25_PROC_FLAG_NONCONT_DATA|LTR25_PROC_FLAG_VOLT, ch_status);
                    if (err != LTR_OK) {
                        fprintf(stderr, "Ошибка обработки данных. Ошибка %d:%s\n", err,
                            LTR25_GetErrorString(err));
                    } else {
                        unsigned ch;
                        recvd_blocks++;
                        /* выводим по первому слову на канал */
                        printf("Блок %4d: ", recvd_blocks);

                        for (ch = 0; (ch < hltr25.State.EnabledChCnt); ch++) {
                            double val = 0;
                            if (ch_status[ch] == LTR25_CH_STATUS_OPEN) {
                                printf("%10s", "обрыв");
                            } else if (ch_status[ch] == LTR25_CH_STATUS_SHORT) {
                                printf("%10s", "кз");
                            } else if (ch_status[ch] == LTR25_CH_STATUS_OK) {
                                unsigned i;
                                for (i = 0; (i < RECV_BLOCK_CH_SIZE); i++) {
                                    val += data[i*hltr25.State.EnabledChCnt + ch];
                                }
                                val /= RECV_BLOCK_CH_SIZE;
                                printf("%8.2f", val);
                            } else {
                                printf("незвест. сост.!");
                            }

                            if (ch == hltr25.State.EnabledChCnt - 1U) printf("\n");
                            else                                      printf(",  ");
                        }
                        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 (hltr25.State.Run) {
                if (opts.stop_card) {
                    INT stop_err = LTR25_Stop(&hltr25);
                    if (stop_err != LTR_OK) {
                        fprintf(stderr, "Не удалось остановить сбор данных. Ошибка %d:%s\n",
                            stop_err, LTR25_GetErrorString(stop_err));
                        if (err == LTR_OK)
                            err = stop_err;
                    } else {
                        printf("Сбор остановлен успешно.\n");
                    }
                } else {
                    printf("Работа программы завершена. Модуль не остановлен.\n");
                }
            }



            free(rbuf);
            free(data);
            free(ch_status);
        } /*if (err == LTR_OK)*/

        if ((err == LTR_OK) && opts.storeconfig_on) {
            TLTR_SETTINGS cfg;
            TLTR h_ltr;

            printf("Сохранение конфигурации модуля и крейта\n");
            if ((err = LTR25_StoreConfig(&hltr25, opts.start_mode)) != LTR_OK) {
                fprintf(stderr, "Не удалось сохранить конфигурацию модуля. Ошибка %d (%s)\n", err,
                    LTR25_GetErrorString(err));
            } else {
                printf("LTR25_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, hltr25.Channel.saddr, hltr25.Channel.sport,
                                         LTR_CRATE_IFACE_UNKNOWN, hltr25.Channel.csn)) != LTR_OK) {
                    fprintf(stderr, "Не удалось открыть соединение с крейтом. Ошибка %d (%s).\n",
                        err, LTR_GetErrorString(err));
                } else {
                    printf("LTR_Open()->OK\n");
                }
            }

            if (err == LTR_OK) {
                cfg.size = sizeof(TLTR_SETTINGS);
                cfg.autorun_ison = opts.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 (opts.storeconfig_on)*/

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

cleanup:
    return err;
}
