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

    Данный пример использует факт, что в бибилотеках 1.30.0 и выше можно передать
    пустую строку в качестве имени TTF-файла для загурузки и будет взят
    файл из ресурса библиотеки или по стандартному пути.

    Пример принимает данные от первых двух каналов и на основании принятых
    параметров M и N рассчитывает интервалы между заданным количеством фронтов.
    При появлении первого фронта для канала просто выводится сообщение, при
    появлении REQ_FRONT_CNT-ого фронта рассчитывается время с первого.
    При отсутствии заданного кол-ва фронтов за MAX_MEAS_INTERVAL_MS мс подсчет времени сбрасывается
    и снова ожидается первый фронт.
    Интервал между двумя фронтами должен быть не менее MIN_FRONT_INTERVAL_MS мс.

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

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

    Сборка в VisualStudio:
    Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
    (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
    -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
    на нужный в зависимаости от пути установки библиотек (ltr51api.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)
    и сохраните изменения в файле. А также следует убедится, что в настройках
    консоли стоит шрифт с поддержкой русского языка (например Consolas).
*/

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


/* минимальный интервал между фронтами в мс */
#define MIN_FRONT_INTERVAL_MS  2
/* максимальный интервал измерения в мс */
#define MAX_MEAS_INTERVAL_MS  500000
/* количество фронтов, между которыми замеряется интервал */
#define REQ_FRONT_CNT    2
/* путь к прошивке ПЛИС. Пустая строка => используем встроенную прошивку */
#define LTR51_TTF_FILE ""

typedef struct {
    int slot;
    const char *serial;
    DWORD addr;
} t_open_param;


static 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) {
    int err = 0;
    par->slot = LTR_CC_CHNUM_MODULE1;
    par->serial = "";
    par->addr = LTRD_ADDR_DEFAULT;


    if (argc > 1)
        par->slot = atoi(argv[1]);
    if (argc > 2)
        par->serial = argv[2];
    if (argc > 3) {
        int a[4],i;
        if (sscanf(argv[3], "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3])!=4) {
            fprintf(stderr, "Неверный формат адреса сервера!!\n");
            err = -1;
        }

        for (i=0; (i < 4) && !err; i++) {
            if ((a[i]<0) || (a[i] > 255)) {
                fprintf(stderr, "Неверный формат адреса сервера!!\n");
                err = -1;
            }
        }

        if (!err) {
            par->addr = (a[0] << 24) | (a[1]<<16) | (a[2]<<8) | a[3];
        }
    }
    return err;
}

int main(int argc, char** argv) {
    INT err = LTR_OK;
    TLTR51 hltr51;
    t_open_param par;
#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
    setlocale(LC_ALL, "");
#endif

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

    if (!err) {
        LTR51_Init(&hltr51);
        err = LTR51_Open(&hltr51, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot,
                         LTR51_TTF_FILE);

        if (err!=LTR_OK) {
            fprintf(stderr, "Не могу открыть модуль. Ошибка %d (%s)\n",
                    err, LTR51_GetErrorString(err));
        } else {
            double HighThreshold, LowThreshold;
            printf("Модуль открыт успешно. Информация:\n Название модуля: %s\n Серийный номер: %s\n"
                   " Версия прошивки: %s\n Дата создания прошивки: %s\n Версия ПЛИС: %s\n",
                   hltr51.ModuleInfo.Name,
                   hltr51.ModuleInfo.Serial,
                   hltr51.ModuleInfo.FirmwareVersion,
                   hltr51.ModuleInfo.FirmwareDate,
                   hltr51.ModuleInfo.FPGA_Version
                   );
            fflush(stdout);

            
            HighThreshold = 4;
            LowThreshold = 1;
            /* Для примера настраиваем сбор по первым двум каналам (можно изменить) */
            hltr51.LChQnt = 2;
            hltr51.LChTbl[0] = LTR51_CreateLChannel(1, &HighThreshold, &LowThreshold,
                                                    LTR51_THRESHOLD_RANGE_10V, LTR51_EDGE_MODE_RISE);
            hltr51.LChTbl[1] = LTR51_CreateLChannel(2, &HighThreshold, &LowThreshold,
                                                    LTR51_THRESHOLD_RANGE_10V, LTR51_EDGE_MODE_RISE);
            hltr51.AcqTime = 1000;

            /* Значения Base и FS задаем вручную */
            hltr51.SetUserPars = TRUE;
            hltr51.Fs = LTR51_FS_MAX; /* устанавливаем макс. частоту для макс. разрешения
                                         500 KГц */
            /* рассчитываем base, чтобы период измерения был хотя бы в 2 раза
             * меньше минимального интервала следования фронтов, так как для
             * измерения длительности одиночных интервалов нужно, чтобы
             * каждый фронт был в своем интервале измерения */
            hltr51.Base = (WORD)(((double)MIN_FRONT_INTERVAL_MS * hltr51.Fs/1000)/2);


            err = LTR51_Config(&hltr51);
            if (err!=LTR_OK) {
                fprintf(stderr, "Не удалось настроить модуль! Ошибка %d:%s\n",
                        err, LTR51_GetErrorString(err));
            } else {
                printf("Установлены следующие параметры:\n Частота дискретизации = %.3f Гц\n Счетчик BASE = %d\n Время счета = %d мс\n Количество периодов измерения = %d\n",
                       hltr51.Fs, hltr51.Base, hltr51.AcqTime, hltr51.TbaseQnt);
                fflush(stdout);
            }

            if (err==LTR_OK) {
                /* выделяем массивы под буферы данных */
                DWORD read_cnt = 2*LTR51_CHANNEL_CNT*hltr51.TbaseQnt;
                DWORD res_size = hltr51.LChQnt*hltr51.TbaseQnt;
                DWORD *rbuf = malloc(read_cnt*sizeof(rbuf[0]));
                DWORD *out_res = malloc(res_size*sizeof(out_res[0]));
                struct {
                    DWORD fronts_cnt; /* признак, что был найден фронт */
                    DWORD dt_cnt; /* кол-во периодов дескретизации, которое длится текущий интервал */
                } *detect_ch_info = calloc(hltr51.LChQnt, sizeof(detect_ch_info[0]));
                DWORD dt_cnt_max;
                /* рассчитываем кол-во периодов дескретизации, которое соответствует
                 * максимальному отслеживаемому интервалу */
                dt_cnt_max =  (DWORD)((double)MAX_MEAS_INTERVAL_MS*hltr51.Fs/1000 + 0.5);




                if ((rbuf==NULL) || (out_res==NULL) || (detect_ch_info==NULL)) {
                    err = LTR_ERROR_MEMORY_ALLOC;
                    fprintf(stderr, "Ошибка выделения памяти!\n");
                } else {
                    /* Запускаем сбор данных */
                    err = LTR51_Start(&hltr51);
                    if (err!=LTR_OK)       {
                        fprintf(stderr, "Не запустить сбор данных! Ошибка %d:%s\n",
                                err, LTR51_GetErrorString(err));
                    } else {
                        INT stop_err;

                        DWORD tout = LTR51_CalcTimeOut(&hltr51, hltr51.TbaseQnt);

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

                        /* выполняем сбор либо пока не произойдет ошибка, либо
                         * пока не будет запрос на завершение */
                        while ((err==LTR_OK) && !f_out) {
                            INT recvd = LTR51_Recv(&hltr51, rbuf, NULL, read_cnt, tout);
                            if (recvd<0) {
                                err = recvd;
                                fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n",
                                        err, LTR51_GetErrorString(err));
                            } else if (recvd!=(INT)read_cnt) {
                                fprintf(stderr, "Принято недостаточно данных. Запрашивали %d, приняли %d\n",
                                        read_cnt, recvd);
                                err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
                            } else {
                                err = LTR51_ProcessData(&hltr51, rbuf, out_res, NULL, (DWORD*)&recvd);
                                if (err!=LTR_OK) {
                                    fprintf(stderr, "Ошибка обработки данных. Ошибка %d:%s\n",
                                            err, LTR51_GetErrorString(err));
                                } else {
                                    INT ch;
                                    INT i;

                                    for (i=0; i < recvd; i+=hltr51.LChQnt) {
                                        for (ch=0; ch < hltr51.LChQnt; ch++) {
                                            DWORD n,m;
                                            BOOL check_drop = FALSE;
                                            n = (out_res[i+ch]>>16)&0xFFFF;
                                            m = (out_res[i+ch]&0xFFFF);
                                            if (n!=0) {
                                                if (n > 1) {
                                                    printf("Канал %d: %d фронтов за один интервал измерения!! Неправильные настройки!\n", ch+1, n);
                                                    /* если больше одного фронта,
                                                     * то точно определить можем только время последнего.
                                                     * если вели рассчеты, то эту информацию отбрасываем и
                                                     * начинаем рассчет нового периода с последнего фронта */
                                                    detect_ch_info[ch].fronts_cnt = 0;
                                                    detect_ch_info[ch].dt_cnt = m;
                                                } else {
                                                    if (detect_ch_info[ch].fronts_cnt == 0) {
                                                        printf("Канал %d: найден первый фронт\n", ch+1);
                                                        detect_ch_info[ch].fronts_cnt = 1;
                                                        detect_ch_info[ch].dt_cnt = m;
                                                    } else if (detect_ch_info[ch].fronts_cnt == (REQ_FRONT_CNT-1)) {
                                                        double period_ms;
                                                        detect_ch_info[ch].dt_cnt += (hltr51.Base - m);
                                                        period_ms = 1000.*detect_ch_info[ch].dt_cnt/hltr51.Fs;
                                                        printf("Канал %d: найден последний фронт. Интервал с первого: %.2f мс\n",
                                                               ch+1, period_ms);
                                                        /* начинаем измерять время от этого фронта */
                                                        detect_ch_info[ch].dt_cnt = m;
                                                        detect_ch_info[ch].fronts_cnt = 1;
                                                    } else {
                                                        /* для промежуточных фронтов просто
                                                         * прибавляем весь интервал
                                                         * и увеличиваем кол-во найденных фронтов */
                                                        detect_ch_info[ch].fronts_cnt++;
                                                        detect_ch_info[ch].dt_cnt+=hltr51.Base;
                                                        check_drop = TRUE;
                                                        printf("Канал %d: найден промежуточный фронт %d\n",
                                                               ch+1, detect_ch_info[ch].fronts_cnt);
                                                    }
                                                }
                                            } else {
                                                /* n==0 => hltr51.Base отсчетов
                                                 * были без фронта => прибавляем
                                                 * к текущему интервалу */
                                                if (detect_ch_info[ch].fronts_cnt > 0) {
                                                    detect_ch_info[ch].dt_cnt+=hltr51.Base;
                                                    check_drop = TRUE;
                                                }                                                    
                                            }

                                            /* проверка, что превышен максимальный интервал
                                             * и нужно сбросить подсчеты */
                                            if (check_drop && (detect_ch_info[ch].dt_cnt > dt_cnt_max)) {
                                                printf("Канал %d: не было последующего фронта за заданный интервал\n", ch+1);
                                                detect_ch_info[ch].fronts_cnt = 0;
                                                detect_ch_info[ch].dt_cnt = 0;
                                            }
                                        }
                                    }
                                }
                            }

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



                        stop_err = LTR51_Stop(&hltr51);
                        if (stop_err!=LTR_OK) {
                            fprintf(stderr, "Ошибка останова сбора данных. Ошибка %d:%s\n",
                                    stop_err, LTR51_GetErrorString(stop_err));
                            if (err==LTR_OK)
                                err = stop_err;
                        } else {
                            printf("Сбор данных остановлен успешно!\n");
                        }
                    }
                }


                free(rbuf);
                free(out_res);
                free(detect_ch_info);
            }
        }

        LTR51_Close(&hltr51);
    }

    return err;
}
