/*
    При использовании заглушки соединяющей DOUT -> DIN есть возмость проверить корректность приема/передачи цифровых каналов в петле

    При использовании флага DCI_TEST_MODE по каналу АЦП должен приниматься 32-битный счетчик
*/

/*    
   Данный пример представляет из себя консольную программу на языке C,
   демонстрирующую работу с модулями L502 и E502 на примере синхронного
   ввода данных с АЦП и цифровых линий.

   Перед сбором в примере идет поиск модулей, подключенных по интерфейсам PCI-Express и USB,
   и предоставляется список для выбора модуля, с которым нужно работать.
   Для подключения по Ethernet нужно указать IP-адреса интересующих модулей
   в качестве аргументов командной строки при вызове примера, тогда эти адреса
   будут добавлены в конец списка выбора модуля.
   Например, если интересуют модули с адресами 192.168.1.5 и 192.168.1.6,
   то пример можно вызвать:
   x502_stream_read 192.168.1.5 192.168.1.6
   и две дополнительные строки с этими адресами появятся в списке выбора.

   Настройки частот, количества принимаемых данных и т.д. задаются с помощью макросов в
   начале программы.
   Настройки логических каналов - с помощью таблиц f_channels/f_ch_modes/f_ch_ranges.

   Пример выполняет прием блоков данных заданного размера.
   Сбор идет до нажатия любой клавиши на Windows или  CTRL+C на Linux.

   Пример также показывает как выполнять обработку данных и определять начало кадра,
   в случае если в X502_ProcessData() передается не целое число кадров.

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

   Для того чтобы собрать проект в Visual Studio, измените путь к заголовочным файлам
   (Проект (Project) -> Свойства (Properties) -> Свойства конфигурации (Configuration Properties)
   -> С/С++ -> Общие (General) -> Дополнительные каталоги включения (Additional Include Directories))
   на тот, где у вас лежат заголовочный файлы x502api.h, l502api.h и e502api.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 "dev_funcs.h"

#include "../../devs/e502/e502_fpga_regs.h"
#include "../../devs/e502/e502_fpga_regs.h"

#ifdef _WIN32
    #include <locale.h>
    #include <conio.h>
#else
    #include <signal.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/time.h>
    #include <unistd.h>
#endif

#include "timespec_funcs.h"

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

#include "x502api_private.h"

#define STREAM_IN_WRD_TYPE(wrd) wrd & 0x80000000 ? STREAM_IN_WRD_ADC : \
      (wrd & 0xFF000000) == 0x0 ? STREAM_IN_WRD_DIN : \
    ((wrd & 0xFF000000)>>24) == 0x01 ? STREAM_IN_WRD_MSG : STREAM_IN_WRD_USR
    
    

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

/* количество используемых логических каналов */
#define ADC_LCH_CNT  3

/* частота сбора АЦП в Гц*/
//#define ADC_FREQ          2000000
#define ADC_FREQ          400000
/* частота кадров (на логический канал). При ADC_FREQ/ADC_LCH_CNT - межкадровой задержки нет */
#define ADC_FRAME_FREQ    (ADC_FREQ/ADC_LCH_CNT)
/* частота синхронного ввода в Гц*/
#define DIN_FREQ          20000
//#define DIN_FREQ          50000
#define DOUT_FREQ          20000

#define ENABLE_DOUT
//#define ENABLE_DAC1
//#define ENABLE_DAC2

//#define STREAM_IN_ENABLE     (X502_STREAM_ADC | X502_STREAM_DIN)
#define STREAM_IN_ENABLE     (X502_STREAM_DIN)
//#define STREAM_IN_ENABLE     (X502_STREAM_ADC)
//#define STREAM_IN_ENABLE     (0)
//#define STREAM_IN_ENABLE     (X502_STREAM_ADC)

#define TEST_ADC_CNTR_FLAG      (1 << 7)
#define TEST_WR_DISABLE_FLAG    (1 << 6)
#define TEST_RING_MODE_FLAG    (1 << 5)

//#define WRD_MASK    0x3ffff
//#define WRD_MASK    0xffffff
#define WRD_MASK    0xffff
//#define WRD_MASK    0x3ff

//#define ENABLE_DCI_TEST_MODE (TEST_RING_MODE_FLAG)
//#define ENABLE_DCI_TEST_MODE (TEST_ADC_CNTR_FLAG | TEST_WR_DISABLE_FLAG | TEST_RING_MODE_FLAG)
//#define ENABLE_DCI_TEST_MODE TEST_ADC_CNTR_FLAG
//#define ENABLE_DCI_TEST_MODE (TEST_ADC_CNTR_FLAG | TEST_WR_DISABLE_FLAG)
//#define ENABLE_DCI_TEST_MODE TEST_WR_DISABLE_FLAG


#define TEST_MODE 0
//#define TEST_MODE TEST_ADC_CNTR_FLAG

// при ADC_FREQ==2000000 и DIN_FREQ==1000000 через Ethernet обязательно будет X502_STREAM_IN_MSG_OVERFLOW




/* сколько отсчетов считываем за блок */
#define READ_BLOCK_SIZE   4096*66*3

/* таймаут на прием блока (мс) */
#define READ_TIMEOUT     500


uint32_t tstCntr = 0x7788;
uint32_t tstCntr24 = 0x7788;

int dac2_once = 1;
volatile int stream_ingibit_cntr = 1;

int g_test_mode;

#ifdef _WIN32
DWORD WINAPI ThreadFunc(void* arg)
#else
void * threadFunc(void * arg)
#endif
{
    t_x502_hnd hnd = (t_x502_hnd)arg;
    int32_t g_snd_cnt = 0;
    static uint32_t snd_buf[READ_BLOCK_SIZE];
    struct timespec start_time;
    struct timespec cur_time;
    struct timespec spent_time;

    clock_gettime(CLOCK_MONOTONIC, &start_time);

    while(!f_out) {
        int32_t snd_cnt = 0;
        uint32_t to_send = READ_BLOCK_SIZE;
        uint32_t *to_send_ptr = snd_buf;

        for (int i = 0; i < READ_BLOCK_SIZE; ) {
            if (g_test_mode == TEST_RING_MODE_FLAG) {
                snd_buf[i] = (tstCntr | ((tstCntr << 14) & 0xC0000000));
                i++;
            } else {

#ifdef ENABLE_DOUT
            snd_buf[i] = tstCntr24 & 0xffff;
            i++;
#endif
#ifdef ENABLE_DAC1
            snd_buf[i] = (tstCntr & 0xffff) | X502_STREAM_OUT_WORD_TYPE_DAC1;
            i++;
#endif
#ifdef ENABLE_DAC2
            snd_buf[i] = (tstCntr & 0xffff) | X502_STREAM_OUT_WORD_TYPE_DAC2;
            i++;
#endif
            }
            tstCntr++;
            tstCntr24++;
        }
        
        while (to_send && !f_out) {
            snd_cnt = X502_Send(hnd, to_send_ptr, to_send, READ_TIMEOUT);
            if (snd_cnt < 0 || f_out) {
                printf("thread exiting X502_Send err=%d\n", snd_cnt);
                return 0;
            }
            to_send -= snd_cnt;
            to_send_ptr += snd_cnt;
            if (stream_ingibit_cntr > 0) {
                stream_ingibit_cntr -=snd_cnt; 
            }
        }

        g_snd_cnt += READ_BLOCK_SIZE;

        clock_gettime(CLOCK_MONOTONIC, &cur_time);
        if (cur_time.tv_sec  - start_time.tv_sec >= 5) 
        {
            timespec_diff(&cur_time, &start_time, &spent_time);

            printf("cnt=%d sec=%f snd speed=%f wrds/sec\n", g_snd_cnt, timespec_to_double(&spent_time), g_snd_cnt / timespec_to_double(&spent_time));
            start_time = cur_time;
            g_snd_cnt = 0;
        }
    }
    return 0;
}

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


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


/* настройка параметров модуля */
int32_t f_setup_params(t_x502_hnd hnd) {
    int32_t err = X502_ERR_OK, i;
    int ch_cnt = 1;//ADC_LCH_CNT;
    
    X502_SetSyncMode(hnd, X502_SYNC_INTERNAL);
    //X502_SetSyncMode(hnd, X502_SYNC_DI_SYN1_FALL);
    X502_StreamsStop(hnd);

    /* устанавливаем параметры логической таблицы АЦП */
    err = X502_SetLChannelCount(hnd, ch_cnt);
    for (i=0; (i < ch_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, f_din = DIN_FREQ;

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

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

    /* разрешаем синхронные потоки */
    if (err == X502_ERR_OK) {
        int streams = 0;
#if defined(STREAM_IN_ENABLE)
        streams |= STREAM_IN_ENABLE;
#endif
#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
        streams |= X502_STREAM_ALL_OUT;
#endif
        err = X502_StreamsEnable(hnd, streams);        
    }
    return err;
}

void recv_proc(t_x502_hnd hnd) {
        struct timespec start_time;
        int32_t err = X502_ERR_OK;
        int32_t g_rcv_cnt = 0;
        int cntr = 0;
        int rcv_cntr = 1;//0x02000000;
        int din_rcv_cntr = 1;//0x02000000;

        clock_gettime(CLOCK_MONOTONIC, &start_time);
        
#if !defined(STREAM_IN_ENABLE)
        if(1) {
            while(!f_out) {}
        } else
#endif

        while (err == X502_ERR_OK && !f_out) {
            int32_t rcv_size;
            uint32_t adc_size, din_size;
            /* массив для приема необработанных данных */
            static uint32_t rcv_buf[READ_BLOCK_SIZE];
            int i;
            struct timespec cur_time;
            
            /* принимаем данные (по таймауту) */
            rcv_size = X502_Recv(hnd, rcv_buf, READ_BLOCK_SIZE, READ_TIMEOUT);
            //printf("rcv_size=%d\n", rcv_size);
            g_rcv_cnt += rcv_size;
            int error = 0;
            
            clock_gettime(CLOCK_MONOTONIC, &cur_time);
            if (cur_time.tv_sec  - start_time.tv_sec >= 5) {
                printf("rcv speed=%ld wrds/sec adc_cntr=%x din_cntr=%x\n", (g_rcv_cnt) / (cur_time.tv_sec  - start_time.tv_sec), rcv_cntr, din_rcv_cntr);
                start_time.tv_sec = cur_time.tv_sec;
                g_rcv_cnt = 0;
            }

            for (i = 0; i < rcv_size; i++) {
                
                // тест с заглушкой DOUT -> DIN
                t_stream_in_wrd_type type = STREAM_IN_WRD_TYPE(rcv_buf[i]);
                /* проверяем - это данные от АЦП или цифровых входов */
#define INC_VAL 1
#define ADC_WRD_MASK 0xFFFFFF
                if (g_test_mode == TEST_ADC_CNTR_FLAG) {
                    if (rcv_buf[i] != din_rcv_cntr) 
                    {
                        printf("DIN %x[%i] %x expected\n", rcv_buf[i], i, din_rcv_cntr);
                        din_rcv_cntr = rcv_buf[i];
                        error = 1;
                    }                    
                    din_rcv_cntr += INC_VAL;
                } else
                if (rcv_buf[i] == X502_STREAM_IN_MSG_OVERFLOW) {
                    printf("overflow msg received!\n");
                }
#if 1
                else
                if (type == STREAM_IN_WRD_ADC) {
                    if ((rcv_buf[i] & ADC_WRD_MASK) != (rcv_cntr & WRD_MASK )) 
                    {
                        printf("ADC %x[%i] %x expected\n", rcv_buf[i], i, rcv_cntr);
                        rcv_cntr = rcv_buf[i];
                        error = 1;
                    }                    
                    rcv_cntr += INC_VAL;
                } else
                if (type == STREAM_IN_WRD_DIN) {
                    if ((rcv_buf[i]  & WRD_MASK)!= (din_rcv_cntr & WRD_MASK)) 
                    {
                        printf("DIN %x[%i] %x expected\n", rcv_buf[i], i, din_rcv_cntr);
                        din_rcv_cntr = rcv_buf[i];
                        error = 1;
                    }                    
                    din_rcv_cntr += INC_VAL;
                } else {
                        printf("unknown wrd %x[%i]\n", rcv_buf[i], i);
                }
#endif
            }

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

int main(int argc, char** argv) {
    t_x502_hnd hnd;
    int32_t err = X502_ERR_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 в CP1251 без перевода в OEM */
    setlocale(LC_CTYPE, "");
#endif
    
    hnd = select_dev_from_list(argc, argv, 0);
    /********************************** Работа с модулем **************************/
    /* если успешно выбрали модуль и установили с ним связь - продолжаем работу */
    if (hnd == NULL) {
        return -1;
    }
    /* настраиваем параметры модуля */
    err = f_setup_params(hnd);
    if (err != X502_ERR_OK) {
        fprintf(stderr, "Ошибка настройки модуля: %s!", X502_GetErrorString(err));
        return -1;
    }

#define E502_REGS_ARM_DCI_TEST_MODE     0x10C

    g_test_mode = TEST_MODE;
    // включаем режим когда по DCI должен передаваться счетчик начиная с 1
    X502_FpgaRegWrite(hnd, E502_REGS_ARM_DCI_TEST_MODE, g_test_mode);
    fprintf(stderr, "TEST_MODE = %x\n", g_test_mode);

    X502_FpgaRegWrite(hnd, E502_REGS_IOHARD_GO_SYNC_IO, 0);
    X502_FpgaRegWrite(hnd, E502_REGS_BF_CTL, X502_REGBIT_BF_CTL_BF_RESET_Msk);

#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
    
#ifdef _WIN32

    HANDLE thread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
    while(stream_ingibit_cntr > 0);
    Sleep(2);
#else
    pthread_t threadId;
    // Create a thread that will function threadFunc()
    err = pthread_create(&threadId, NULL, &threadFunc, hnd);
    while(stream_ingibit_cntr > 0);
    sleep(2);
#endif
#endif

    /* запуск синхронного ввода-вывода */
    if (err == X502_ERR_OK) {
        err = X502_StreamsStart(hnd);
        if (err != X502_ERR_OK) {
            fprintf(stderr, "Ошибка запуска сбора данных: %s!\n", X502_GetErrorString(err));
        }
    }

    if (err == X502_ERR_OK) {
        int block;
        int32_t stop_err;

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

        recv_proc(hnd);
        
#if defined(ENABLE_DOUT) || defined(ENABLE_DAC1) || defined(ENABLE_DAC2)
    
#ifdef _WIN32
        //TODO: pthread_join
#else
        pthread_join(threadId, NULL);
#endif
#endif

        /* останавливаем поток сбора данных (независимо от того, была ли ошибка) */
        stop_err = X502_StreamsStop(hnd);
        if (stop_err != X502_ERR_OK) {
            fprintf(stderr, "Ошибка останова сбора данных: %s\n", X502_GetErrorString(err));
            if (err == X502_ERR_OK)
                err = stop_err;
        } else {
            printf("Сбор данных остановлен успешно\n");
        }
    }

    // выключаем режим когда по DCI должен передаваться счетчик начиная с 1
    X502_FpgaRegWrite(hnd, E502_REGS_ARM_DCI_TEST_MODE, 0);

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