/* Данный пример демонстрирует возможность установки циклического сигнала на
   вывод в модуль E16 / E502 / L502 (ввод не используется вообще).

   В примере определена таблица сигналов, которая задает несколько
   комбинаций сигналов для примера. Каждый сигнал задается количеством точек
   на период (общее значение для всех трех сигналов), амплитудой для ЦАП'ов и
   функцией для генерации очередного отсчета по его номеру и полному размеру.

   Пользовтель может ввести номер сигнала, которых он хочет выставить и нажать Enter - и этот
   сигнал будет выставлен на выходе.

   Смена сигнала происходит по концу периода предыдущего.
   Надо также помнить, что хотя мы можем загружать новый сигнал на фоне вывода
   предыдущего, однако сразу после выполнения L502_OutCycleSetup() до того
   времени как реально произойдет смена старого сигнала на новый нельзя
   начинать загружать еще один новый сигнал.


   Данный пример содержит проект для Visual Studio 2008, а также может быть собран
   gcc в Linux или mingw в Windows через makefile или с помощью cmake (подробнее
   в коментариях в соответствующих файлах).

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

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

#include "l502api.h"
#include "e502api.h"
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "dev_funcs.h"

#define ADC_FREQ          2000000
#define ADC_LCH_CNT  1
#define READ_BLOCK_SIZE   4096*66*3
#define READ_TIMEOUT     2000
#define TCP_CONNECTION_TOUT 5000
#define OUT_SIGNAL_SIZE           2000
#define OUT_BLOCK_SIZE            256
#define SEND_TOUT                 500

#ifndef M_PI
    #define M_PI 3.14159265358979323846
#endif

uint16_t random_hex[] = {
    0x93f8,
    0x05f4,
    0x9bf6,
    0x7ffe,
    0x161e,
    0xe7ba,
    0xf1af,
    0x1e0f,
    0x5ec0,
    0xc8ff,
    0x55e2,
    0x9df0,
    0x6a6c,
    0xa649,
    0x1ade,
    0x1b24,
    0x343d,
    0x7b6b,
    0x76b9,
    0xc8fc,
    0x2e3a,
    0x24d9,
    0xf189,
    0xf1d1,
    0xff71,
    0xe23c,
    0xa77f,
    0x4d8e,
    0x2858,
    0xa96d,
    0xf289,
    0x7337,
    0x640b,
    0x57e9,
    0x39ce,
    0xdbd9,
    0xb832,
    0x315d,
    0x441c,
    0x863e,
    0xf7b9,
    0x8ecf,
    0x1010,
    0x88da,
    0xeb76,
    0x9302,
    0xc160,
    0x7238,
    0xdda0,
    0x4f94

};

typedef double (*f_gen_sig_word)(uint32_t cntr, uint32_t total_size, double amp);
typedef uint32_t (*f_gen_dout_word)(uint32_t cntr, uint32_t total_size);
/* структура, задающая сигналы на 2-х каналах ЦАП и/или1
1на DOUT */
typedef struct {
    uint32_t size; /* количество точек в сигнале */
    double amp_dac1; /* амплитуда сигнала для ЦАП1 */
    f_gen_sig_word gen_func_dac1; /* функция для генерации отсчета для ЦАП1 */
    double amp_dac2; /* амплитуда сигнала для ЦАП2 */
    f_gen_sig_word gen_func_dac2; /* функция для генерации отсчета для ЦАП2 */
    f_gen_dout_word gen_dout; /* функция для генерации слова на цифровой вывод */
} t_sig_struct;

/* номера используемых физических каналов */
static uint32_t f_channels[] = {1};
/* режимы измерения для каналов */
static uint32_t f_ch_modes[] = {X502_LCH_MODE_COMM, X502_LCH_MODE_DIFF, X502_LCH_MODE_DIFF};
/* диапазоны измерения для каналов */
static uint32_t f_ch_ranges[] = {X502_ADC_RANGE_10, X502_ADC_RANGE_10, X502_ADC_RANGE_02};

/* генерация пилы на весь период */
static double f_gen_saw(uint32_t cntr, uint32_t total_size, double amp) {
    return amp*( (int32_t)(cntr%total_size)-(int32_t)total_size/2)/(total_size/2);
}

/* генерация синуса на весь период */
static double f_gen_sin(uint32_t cntr, uint32_t total_size, double amp) {
    return amp*sin(2*M_PI*cntr/total_size);
}

/* генерация тестового счетчика */
static double f_gen_cntr(uint32_t cntr, uint32_t total_size, double amp) {
    return cntr + 0x123;
}

/* генерация синуса с периодом в два раза меньше периоду определяемому размером буфера */
static double f_gen_sin2(uint32_t cntr, uint32_t total_size, double amp) {
    return amp*sin(2*2*M_PI*cntr/total_size);
}

static double f_gen_meander(uint32_t cntr, uint32_t total_size) {
    return cntr < total_size/2 ? 5 : -1;
}

/* генерация меандра на всех выходах путем выдачи в первой половине на выходы 0xAA, а на второй 0x55 */
static uint32_t f_gen_dout_meander(uint32_t cntr, uint32_t total_size) {
    return cntr < total_size/2 ? 0xAA : 0x55;
}

/* генерация счетчика на цифроых выхоах*/
static uint32_t f_gen_dout_cntr(uint32_t cntr, uint32_t total_size) {
    return cntr;
}


/* таблица, задающая сигналы для выдачи на вход */
/* для модуля E16 частота выходного сигнала будет в 2 раза меньше, т.к. частота ЦАП 500КГц против 1МГц у E502 */
static t_sig_struct f_sig_tbl[] = {
    {1000, X502_DAC_RANGE, f_gen_saw, 0, NULL, NULL}, /* пила на одном канале с частотой 250?? Гц */
    {2000, 2.5, f_gen_meander, 0, NULL, NULL}, /* синусоидальный сигнал на одном канале с частотой 500 Гц */
    {100, X502_DAC_RANGE/2, f_gen_sin, X502_DAC_RANGE, f_gen_saw, NULL}, /* синус ампл. 2.5 и пила по 10 КГц */
    {50, 0, NULL, X502_DAC_RANGE, f_gen_sin, f_gen_dout_cntr}, /* синус только на втором канале с частотой 20 КГц */
    {2550, 1.5, f_gen_sin, 2.5, f_gen_sin2, f_gen_dout_meander}, /* на втором канале синус с частотой в 2 раза больше. меандр на цифровых линиях */
};


/* Запись в буфер драйвера блока данных от сигнала
   cntr - номер отчета, соответствующего первому отсчету блока
   size - количество отсчетов на канал (т.е. записывается ch_cnt*size отсчетов)
   sig  - номер сигнала
   ch_cnt - кол-во используемых каналов (это кол-во можно определить по f_sig_tbl[sig],
            но так как мы его уже определили, то передаем сюда, чтобы опять не определять */
static int32_t f_load_block(t_x502_hnd hnd, uint32_t cntr, uint32_t size, uint32_t sig, uint32_t ch_cnt) {
    static double dac_data1[OUT_BLOCK_SIZE], dac_data2[OUT_BLOCK_SIZE];
    static uint32_t dout_data[OUT_BLOCK_SIZE];
    /* массив слов на запись в модуль - содержит смешенные подготовленные данные
       для всех каналов (максимум для 3-х - 2 ЦАП + DOUT) */
    static uint32_t sbuf[3*OUT_BLOCK_SIZE];
    uint32_t i;
    int32_t err = 0;

    /* заполняем массив на вывод */
    for (i=0; i < size; i++) {
        if (f_sig_tbl[sig].gen_func_dac1 != NULL) {
            dac_data1[i] = f_sig_tbl[sig].gen_func_dac1(cntr+i, f_sig_tbl[sig].size, f_sig_tbl[sig].amp_dac1);
        }
        if (f_sig_tbl[sig].gen_func_dac2 != NULL) {
            dac_data2[i] = f_sig_tbl[sig].gen_func_dac2(cntr+i, f_sig_tbl[sig].size, f_sig_tbl[sig].amp_dac2);
        }
        if (f_sig_tbl[sig].gen_dout != NULL) {
            dout_data[i] = f_sig_tbl[sig].gen_dout(cntr+i, f_sig_tbl[sig].size);
        }
    }

    /* Если нужная функция определена, значит мы испоьлзуем этот канал, и
     * подаем на вход сформированный массив. Иначе - канал не используется
     * и передаем на вход NULL */
    err = X502_PrepareData(hnd,
                           f_sig_tbl[sig].gen_func_dac1 ? dac_data1 : NULL,
                           f_sig_tbl[sig].gen_func_dac2 ? dac_data2 : NULL,
                           f_sig_tbl[sig].gen_dout ? dout_data : NULL,
                           size, X502_DAC_FLAGS_VOLT | X502_DAC_FLAGS_CALIBR,
                           sbuf);
    if (err != X502_ERR_OK) {
        fprintf(stderr, "Ошибка подкотовки данных на передачу: %s\n",
                X502_GetErrorString(err));
    } else {
        /* посылаем данные */
        int32_t snd_cnt = X502_Send(hnd, sbuf, size*ch_cnt, SEND_TOUT);
        if (snd_cnt < 0) {
            err = snd_cnt;
            fprintf(stderr, "Ошибка передачи данных: %s\n", X502_GetErrorString(err));
        } else if ((uint32_t)snd_cnt != size*ch_cnt) {
            /* так как мы шлем всегда не больше чем готово, то должны
               всегда передать все */
            fprintf(stderr, "Переданно недостаточно данных: передавали = %d, передано = %d\n",
                    size*ch_cnt, snd_cnt);
            err = X502_ERR_SEND_INSUFFICIENT_WORDS;
        }
    }
    return err;
}

static int32_t f_load_cycle_signal(t_x502_hnd hnd, int sig) {
    int32_t err = 0;
    uint32_t cntr = 0;
    uint32_t ch_cnt=0;

    /* определяем, сколько каналов используем */
    if (f_sig_tbl[sig].gen_func_dac1)
        ch_cnt++;
    if (f_sig_tbl[sig].gen_func_dac2)
        ch_cnt++;
    if (f_sig_tbl[sig].gen_dout)
        ch_cnt++;


    /* задаем размер буфера под все каналы! */
    err = X502_OutCycleLoadStart(hnd, f_sig_tbl[sig].size*ch_cnt);
    if (err != X502_ERR_OK)
        fprintf(stderr, "Ошибка старта загрузки данных: %s!", X502_GetErrorString(err));

    /* в примере показано, что загружать можно поблочно, чтобы не выделять
     * большой размер памяти на весь сигнал в программе. Но можно записать
     * весь сигнал и за один L502_Send() */
    while ((cntr != f_sig_tbl[sig].size) && (err == X502_ERR_OK)) {
        uint32_t block_size = OUT_BLOCK_SIZE;
        /* последний блок может быть меньшего размера, если размер буфера не кратен
         * блоку */
        if (block_size >(f_sig_tbl[sig].size-cntr))
            block_size=f_sig_tbl[sig].size-cntr;

        err = f_load_block(hnd, cntr, block_size, sig, ch_cnt);
        if (!err)
            cntr+=block_size;
    }

    /* делаем активным загруженный сигнал */
    if (err == X502_ERR_OK) {
       err = X502_OutCycleSetup(hnd, X502_OUT_CYCLE_FLAGS_WAIT_DONE);
       if (err != X502_ERR_OK)
            fprintf(stderr, "Ошибка установки циклического сигнала: %s!", X502_GetErrorString(err));
    }
    return err;
}

int32_t f_setup_params(t_x502_hnd hnd) {
    int32_t err = X502_ERR_OK, i;

    /* устанавливаем параметры логической таблицы АЦП */
    err = X502_SetLChannelCount(hnd, ADC_LCH_CNT);
    for (i=0; (i < ADC_LCH_CNT) && (err == X502_ERR_OK); i++)
        err = X502_SetLChannel(hnd, i, f_channels[i], f_ch_modes[i], f_ch_ranges[i], 0);

    /* устанавливаем частоты ввода для АЦП и цифровых входов */
    if (err == X502_ERR_OK) {
        double f_adc = ADC_FREQ;

        err = X502_SetAdcFreq(hnd, &f_adc, NULL);
        if (err == X502_ERR_OK) {
            /* выводим реально установленные значения - те что вернули функции */
            printf("Установлены частоты:\n    Частота сбора АЦП = %0.0f\n", f_adc);
        }
    }

    /* записываем настройки в модуль */
    if (err == X502_ERR_OK)
        err = X502_Configure(hnd, 0);
}

#define VAL_TOLERANCE 0.01
#define LEN_TOLERANCE 10


int f_test_length(const double *data, const size_t size, const int print){
    uint32_t len = 0;
    uint32_t old_len = 0;
    uint32_t count = 0;
    for (uint32_t i = 0; i < size - 1; i++){
        if( abs(data[i] - data[i + 1]) > VAL_TOLERANCE) {
            if(print){
                printf("Front: %d, Length: %d\n", count, len);
            }

            if ((count > 1) && (abs(old_len - len) > LEN_TOLERANCE) ){
                return 1;
            }

            i++;
            count++;
            old_len = len;
            len = 0;
        } else {
            len++;
        }
    }
    return 0;
}

int main(int argc, char **argv) {
    int32_t err = 0;
    uint32_t ver;
    t_x502_hnd hnd = NULL;

    int flag_verbose = 0;
    int flag_exit_on_err = 1;

    int32_t rcv_size;
    uint32_t adc_size;
    static uint32_t rcv_buf[READ_BLOCK_SIZE];
    static double   adc_data[READ_BLOCK_SIZE];

#ifdef _WIN32
    /* устанавливаем локаль, чтобы можно было выводить по-русски в CP1251 без перевода в OEM */
    setlocale(LC_CTYPE, "");
#endif

    for (int i = 0; i < argc; i++){
        if (!strcmp(argv[i], "verbose")){
            flag_verbose = 1;
        }
        if(!strcmp(argv[i], "continue-on-err")){
            flag_exit_on_err = 0;
        }
    }

    /********** Получение списка устройств и выбор, с каким будем работать ******************/
    hnd = select_dev_from_list(argc, argv, 0);




    /********************************** Работа с модулем **************************/
    /* если успешно выбрали модуль и установили с ним связь - продолжаем работу */
    if (hnd != NULL) {

        /* Разрешаем dac1 на вывод and enable adc input */
        err = X502_StreamsEnable(hnd, X502_STREAM_ADC | X502_STREAM_DAC1);
        if (err != X502_ERR_OK) {
            fprintf(stderr, "Ошибка разрешения потоков (%d): %s!", err,
                    X502_GetErrorString(err));
        } else {
            err = f_setup_params(hnd);
            if (err != X502_ERR_OK){
                fprintf(stderr, "Ошибка настройки модуля: %s!\n", X502_GetErrorString(err));
            }
        }



        if (err == X502_ERR_OK) {
            /* если хотим, то можем загрузить синхнал до разрешения
             * синхронного ввода. Тогда мы могли бы его запустить, наприемер,
               всесте с АЦП по L502_StreamsStart() */
            err = f_load_cycle_signal(hnd, 1);

            /* разрешаем синхронный ввод. В принципе можно его разрешить только
             * после загрузки первого синала в нашем примере. Но данный пример
             * показывает что мы можем сделать это и до выставления сигнала,
             * например если захотим запустить ввод АЦП до того как будет нужно
             * подать циклический сигнал на вывод */
            if (err == X502_ERR_OK)
                err = X502_StreamsStart(hnd);

            if (err == X502_ERR_OK) {
                int exit = 0;

                uint32_t err_cnt = 0;
                /* Цикл ввода команд */
                for (int block = 0; (err == X502_ERR_OK); block++) {
                    X502_AsyncOutDig(hnd, random_hex[block % sizeof(random_hex)], 0);
                    // printf("0x%x\n", random_hex[block % sizeof(random_hex)]);
                    rcv_size = X502_Recv(hnd, rcv_buf, READ_BLOCK_SIZE, READ_TIMEOUT);
                    if (rcv_size < 0) {
                        err = rcv_size;
                        fprintf(stderr, "Ошибка приема данных: %s\n", X502_GetErrorString(err));
                    }
                    if (rcv_size > 0) {
                        uint32_t first_lch;
                        /* получаем номер логического канала, которому соответствует первый отсчет АЦП в массиве */
                        X502_GetNextExpectedLchNum(hnd, &first_lch);

                        adc_size = sizeof(adc_data)/sizeof(adc_data[0]);

                        /* обрабатываем принятые данные, распределяя их на данные АЦП и цифровых входов */
                        err = X502_ProcessData(hnd, rcv_buf, rcv_size, X502_PROC_FLAGS_VOLT, adc_data, &adc_size, NULL, NULL);
                        if (err != X502_ERR_OK) {
                            fprintf(stderr, "Ошибка обработки данных: %s\n", X502_GetErrorString(err));
                        } else {
                            uint32_t lch;

                            printf("Блок %3d. Обработано данных АЦП =%d\n", block, adc_size);

                            int ret = f_test_length(adc_data, adc_size, flag_verbose);
                            if (ret) {
                                err_cnt++;
                                printf("Cycle shifted %d times!\n", err_cnt);
                                if (flag_exit_on_err) {
                                    err = 1;
                                }
                            }

                            // for (uint32_t i = 0; i < adc_size; i++){
                            //     printf("%6.4f\n", adc_data[i]);
                            // }

                            printf("\n");
                            fflush(stdout);
                        }
                    }
                }

                X502_StreamsStop(hnd);
            }
        }

        /* закрываем связь с модулем */
        X502_Close(hnd);
        /* освобождаем описатель */
        X502_Free(hnd);
    }
    return err;
}


