/* Пример демонстрирует синхронный ввод с цифровых входов на фоне потокового
   вывода.
   Вызов:
   ltr35_in_stream  <in_mode> <repeat_mode> <din_div> <out_freq> <max_cycles> <sync_mode> <sdram_ch_cnt> <slot> <csn> <ltrd_ip_addr>

   Если парамтров меньше, то для остальных используются значения по умолчанию.

   Параметры командной строки:
   in_mode - режим потокового ввода:
             0 - не используется
             1 - эхо канал
             2 - ввод с цифровой линии DI1 (по умолчанию)
             3 - ввод с цифровой линии DI2
             4 - ввод с цифровых линий DI1 и DI2
    repeat_mode - режим перезапуска при переходе к следующему циклу.
             0 - рестарт. При переходе к следующему циклу ввод-вывод останавливается
                 с помощью LTR35_Stop(), а затем сразу запускается заново
                 с помощью LTR35_StreamStart().
             1 - реинициализация (по умолчанию). При переходе к следующему циклу соединение
                 с модулем полностью закрывается и открывается. В результате
                 чего выполняется полный сброс и перенастройка модуля.
             2 - реконфигураци. Аналогично 0, но дополнительно между
                 остановом и запуском ввода-вывода идет перенастройка модуля
                 с помощью Configure()
             3 - без перезапуска. Ввод-вывод запускается один раз, после
                 просто выводтся значения раз в заданный интервал без
                 останова ввода-вывода.
    din_div - степень числа два в делителе частоты синхронного ввода с цифровых линий -
              от 0 (для DI1 + DI2 - от 1) до 5. По умолчанию - 0.
    out_freq - частота вывода в ГЦ (стандартные частоты 192000, 96000, 48000). По умолчанию - 192000.
    max_cycles - количество циклов, после которого программа автоматически завершается
                 (если 0 - выполнется до нажатия кнопки (Windows) или CTRL+C (Linux)).
                 По умолчанию - 0.
    sync_mode  - Режим работы при многомодульной синхронизации запуска
               0 - многомодульная синхронизация не используется (по умолчанию)
               1 - модуль является ведущим
               2 - модуль является подчиненным
    sdram_ch_cnt - количество каналов, данные для которых подгуржаются
                  с ПК через SDRAM модуля (для остальных в примере включается
                  арифметический режим при EN_ARITH_CHS == TRUE).
                  При EN_DIGOUT = TRUE один из каналов - цифровой вывод.
                  По умолчанию 2.
                  Не должна превышаться максимальная скорость обмена с модулем
                  в 500 КСлов/с.
    slot       - номер слота крейта, в котором установлен модуль LTR35 (от 1 до 16).
                 По умолчанию - 1.
    csn        - серийный номер крейта, в который установлен модуль LTR35.
                 По умолчанию используется первый найденный крейт.
    ltrd_add   - адрес ПК, на котором запущена служба ltrd (в формате a.b.c.d).
                 По умолчанию предполагается, что служба запущена на том же ПК.
********************************************************************************/

#include "ltr/include/ltr35api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

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

#define DAC_SIG_AMP   5
#define DAC_SIG_OFFS  0
#define DAC_SIG_FREQ  50
#define DAC_SIG_PHASE 0


#define READ_SHOW_SIZE         (20)
#define WRITE_BLOCK_MAX_SIZE   (10*192*1000)
#define READ_BLOCK_MAX_SIZE    (10*392*1000)

#define FIRST_BLOCK_TIME_MS   (1000)
#define CONT_BLOCK_TIME_MS    (1000)
#define RECV_BLOCK_TIME_MS    (1000)


#define CH1_SIN_POINTS     128.
#define CH2_SIN_POINGS     512


#define EN_ARITH_CHS          TRUE
#define EN_DIGOUT             TRUE

typedef enum {
    OPT_IN_MODE_OFF    = 0,
    OPT_IN_MODE_ECHO   = 1,
    OPT_IN_MODE_DI1    = 2,
    OPT_IN_MODE_DI2    = 3,
    OPT_IN_MODE_DI1_DI2= 4,
} e_OPT_IN_MODE;

typedef enum {
    OPT_REPEAT_MODE_RESTART         = 0,
    OPT_REPEAT_MODE_REINIT          = 1,
    OPT_REPEAT_MODE_RECONFIGURE     = 2,
    OPT_REPEAT_MODE_CONTINUOUS      = 3,
} e_OPT_REPEAT_MODE;


#ifndef M_PI
    #define M_PI 3.1415926535897932384626433832795
#endif

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) {
    (void)sig;
    f_out = 1;
}
#endif

static int f_check_out(void) {
#ifdef _WIN32
    /* проверка нажатия клавиши для выхода */
    if (!f_out && _kbhit()) {
        f_out = 1;
    }
#endif
    return f_out;
}



static ULONGLONG gen_cntr, rd_cntr;


static void f_clear_cntrs(void) {
    gen_cntr = 0;
    rd_cntr = 0;
}

static DWORD f_gen_dout(ULONGLONG cntr) {
    //return ((cntr == 0) ? 1 : 0) | (((cntr & 1) ^ 1) << 1);// | (1 << 7); // (ch_cntr & 0xFFFF) ^ 0xFFFF; //0xFFFF;//0;// ch_cntr & 0xFFFF;
    return cntr & 1;
}

static int f_send_data(TLTR35 *phltr35, DWORD size) {
    INT err = 0;
    static DWORD   wrds[2*(LTR35_DAC_CHANNEL_CNT+1)*WRITE_BLOCK_MAX_SIZE];
    static double  sig[LTR35_DAC_CHANNEL_CNT*WRITE_BLOCK_MAX_SIZE];
#if EN_DIGOUT
    static DWORD   douts[WRITE_BLOCK_MAX_SIZE];
#endif

    INT ch;
    DWORD i;

    DWORD snd_size;
    DWORD dac_size = 0;
#if EN_DIGOUT
    DWORD dout_size = 0;
#endif

    for (i=0; i < size; i++) {
        for (ch=0; ch < phltr35->State.SDRAMChCnt; ch++) {
            sig[phltr35->State.SDRAMChCnt*i+ch] = (double)DAC_SIG_AMP *
                    sin(2.*M_PI*gen_cntr*DAC_SIG_FREQ/phltr35->State.OutFreq + 2.*M_PI * DAC_SIG_PHASE/360) + DAC_SIG_OFFS;
            dac_size++;
        }
#if EN_DIGOUT
        douts[dout_size++] = f_gen_dout(gen_cntr);
#endif
        gen_cntr++;
    }

    snd_size = sizeof(wrds)/sizeof(wrds[0]);
    err = LTR35_PrepareData(phltr35, sig, &dac_size,
#if EN_DIGOUT
                                   douts, &dout_size,
#else
                                   NULL, NULL,
#endif
                                   LTR35_PREP_FLAGS_VOLT, wrds, &snd_size);
    if (err!=LTR_OK) {
        fprintf(stderr, "Ошибка подготовки данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
    }

    if (!err) {
        INT sent_size = 0;

        /* передаем массив, либо пока не передадим все данные, либо пока не
         * произойдет ошибка, либо не будет признака выхода */
        while (!f_check_out() && !err && (sent_size < (INT)snd_size)) {
            INT cur_sent = LTR35_Send(phltr35, &wrds[sent_size], snd_size-sent_size, 5000);
            if (cur_sent < 0) {
                err = cur_sent;
                fprintf(stderr, "Ошибка передачи данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
            } else {
                sent_size += cur_sent;
            }
        }
    }
    return err;
}

static int f_inc_cycles(int cycles, int max_cycles) {
    ++cycles;
    if ((max_cycles > 0) && (cycles >= max_cycles))
        f_out = 1;
    return cycles;
}

static int f_recv_din_data(TLTR35 *phltr35, int cycle_num, int rx_size) {
    int err = LTR_OK;
    static INT rcv_wrds[READ_BLOCK_MAX_SIZE];
    INT recv_cnt  = LTR35_RecvInStreamData(phltr35, rcv_wrds, NULL, rx_size, 5000);
    if (recv_cnt != rx_size) {
        fprintf(stderr, "Принято недостаточно данных: запрошено %d, принято %d\n", rx_size, recv_cnt);
        err = LTR_ERROR_RECV_INSUFFICIENT_DATA;
    } else {
        int i;
        printf("%6d:", cycle_num);
        for (i = 0; (i < READ_SHOW_SIZE) && (i < rx_size); ++i) {
            printf(" %06X", rcv_wrds[i] & 0xFFFFFF);
        }
        printf("\n");
        fflush(stdout);
    }
    return err;
}

/* Разбор параметров командной строки. Если указано меньше, то используются
 * значения по умолчанию:
 * 1 параметр - номер слота (от 1 до 16)
 * 2 параметр - серийный номер крейта
 * 3 параметр - ip-адрес сервера */
static int f_get_module_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, "Неверный формат IP-адреса!!\n");
            err = -1;
        }

        for (i=0; (i < 4) && !err; i++) {
            if ((a[i]<0) || (a[i] > 255)) {
                fprintf(stderr, "Недействительный IP-адрес!!\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) {
    t_open_param par;
    INT err = LTR_OK;
    int in_mode = OPT_IN_MODE_DI1;
    e_OPT_REPEAT_MODE repeat_mode = OPT_REPEAT_MODE_REINIT;
    int cycles_cnt = 0;
    int din_div = 0;
    float out_freq = LTR35_DAC_QUAD_RATE_FREQ_STD;
    int max_cycles = 0;
    int sync_mode = 0;
    int sdram_ch_cnt = 2;
#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

    /* разбор входных параметров */
    int proc_params_cnt = 0;
    if (argc > 1) {
        in_mode = atoi(argv[++proc_params_cnt]);
    }
    if (argc > 2) {
        repeat_mode = atoi(argv[++proc_params_cnt]);
    }
    if (argc > 3) {
        din_div = atoi(argv[++proc_params_cnt]);
    }
    if (argc > 4) {
        sscanf(argv[++proc_params_cnt], "%f", &out_freq);
    }
    if (argc > 5) {
        max_cycles = atoi(argv[++proc_params_cnt]);
    }
    if (argc > 6) {
        sync_mode = atoi(argv[++proc_params_cnt]);
    }
    if (argc > 7) {
        sdram_ch_cnt = atoi(argv[++proc_params_cnt]);
    }

    argc -= proc_params_cnt;
    argv = &argv[proc_params_cnt];
    err = f_get_module_params(argc, argv, &par);


    if (!err && !f_check_out()) {
        do {
            TLTR35 hltr35;
            LTR35_Init(&hltr35);
            /* Устанавливаем соединение с модулем */
            err = LTR35_Open(&hltr35, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot);
            if (err != LTR_OK) {
                fprintf(stderr, "Не удалось установить связь с модулем. Ошибка %d (%s)\n",
                        err, LTR35_GetErrorString(err));
            } else {
                INT close_err;
                do {
                    double fnd_freq;
                    if (cycles_cnt == 0) {
                        printf("\nLTR35-%d (%s). PLD = %d, FPGA = %d\n",
                               hltr35.ModuleInfo.Modification,
                               hltr35.ModuleInfo.Serial,
                               hltr35.ModuleInfo.VerPLD,
                               hltr35.ModuleInfo.VerFPGA);
                        fflush(stdout);
                    }

                    /* выбираем частоту ЦАП - 192 КГц */
                    err = LTR35_FillOutFreq(&hltr35.Cfg, out_freq, &fnd_freq);
                    if (err ==LTR_OK) {
                        BYTE ch_num;
                        hltr35.Cfg.OutDataFmt = LTR35_OUTDATA_FORMAT_20;
                        hltr35.Cfg.OutMode = LTR35_OUT_MODE_STREAM;//LTR35_OUT_MODE_CYCLE;// LTR35_OUT_MODE_STREAM;
                        hltr35.Cfg.InStream.InStreamMode = in_mode == OPT_IN_MODE_OFF ? LTR35_IN_STREAM_MODE_OFF :
                                                  in_mode == OPT_IN_MODE_ECHO ? LTR35_IN_STREAM_MODE_CH_ECHO :
                                                              LTR35_IN_STREAM_MODE_DI;
                        hltr35.Cfg.InStream.DIChEnMask = in_mode == OPT_IN_MODE_DI1 ? LTR35_IN_STREAM_DI1 :
                                                         in_mode == OPT_IN_MODE_DI2 ? LTR35_IN_STREAM_DI2 :
                                                         in_mode == OPT_IN_MODE_DI1_DI2 ? LTR35_IN_STREAM_DI1 | LTR35_IN_STREAM_DI2 : 0;
                        hltr35.Cfg.InStream.DIFreqDivPow = din_div;
                        hltr35.Cfg.Sync.SyncMode = sync_mode;

                        /* первый канал разрешаем потоковым */
                        for (ch_num = 0; ch_num < hltr35.ModuleInfo.DacChCnt; ++ch_num) {
    #if EN_DIGOUT
                            if (ch_num < (sdram_ch_cnt-1)) {
    #else
                            if (ch_num < sdram_ch_cnt) {
    #endif
                                hltr35.Cfg.Ch[ch_num].Enabled = TRUE;
                                hltr35.Cfg.Ch[ch_num].Source = LTR35_CH_SRC_SDRAM;
                            } else {
    #ifdef EN_ARITH_CHS
                                hltr35.Cfg.Ch[ch_num].Enabled = TRUE;
                                hltr35.Cfg.Ch[ch_num].Source = LTR35_CH_SRC_SIN1;
                                hltr35.Cfg.Ch[ch_num].ArithAmp = DAC_SIG_AMP;
                                hltr35.Cfg.Ch[ch_num].ArithOffs = DAC_SIG_OFFS;
    #else
                                hltr35.Cfg.Ch[ch_num].Enabled = FALSE;
    #endif
                            }
                        }
                        LTR35_FillArithPhaseDegree(&hltr35.Cfg.ArithSrc[0].Phase, DAC_SIG_PHASE, NULL);
                        LTR35_FillArithPhaseDegree(&hltr35.Cfg.ArithSrc[0].Delta, 360. * DAC_SIG_FREQ / out_freq, NULL);

                        /* настраиваем параметры модуля */
                        err = LTR35_Configure(&hltr35);
                        if (err != LTR_OK) {
                            fprintf(stderr, "Не удалось настроить модуль. Ошибка %d (%s)\n",
                                    err, LTR35_GetErrorString(err));
                        } else {
                            if (cycles_cnt == 0) {
                                printf("  Fbase = %.3f КГц, Fout = %.3f КГц, Fin = %.3f КГц\n",
                                       (hltr35.State.SyntFreq/LTR35_DIN_SYNT_FREQ_DIV)/1e3,
                                       hltr35.State.OutFreq/1e3,
                                       hltr35.State.InStreamWordFreq/1e3);
                                printf("  din_mode = %s, din_div = %d, out_freq = %d, sdram_ch_cnt = %d, sync = %s, повтор = %s",
                                       in_mode == OPT_IN_MODE_OFF ? "выкл." :
                                                  OPT_IN_MODE_ECHO ? "эхо" :
                                                  OPT_IN_MODE_DI1  ? "DI1" :
                                                  OPT_IN_MODE_DI2  ? "DI2" :
                                                  OPT_IN_MODE_DI1_DI2 ? "DI1+DI2" :
                                                                        "???",
                                       din_div, (int)out_freq, sdram_ch_cnt,
                                       sync_mode == LTR35_SYNC_MODE_INTERNAL ? "off" :
                                       sync_mode == LTR35_SYNC_MODE_MASTER ? "master" :
                                                                             "slave",
                                       repeat_mode == OPT_REPEAT_MODE_REINIT ?   "со сбросом модуля" :
                                       repeat_mode == OPT_REPEAT_MODE_RESTART ? "перезапуск" :
                                       repeat_mode == OPT_REPEAT_MODE_RECONFIGURE ? "перенастройка" :
                                       repeat_mode == OPT_REPEAT_MODE_CONTINUOUS ? "постоянная работа" :
                                                                               "????"
                                                                            );
                                if (max_cycles > 0) {
                                    printf(", циклов: %d", max_cycles);
                                }
                                printf("\n");
                                fflush(stdout);
                            }
                        }
                    }

                     if (!err && !f_check_out()) {
                        do {
                            f_clear_cntrs();
                            /* Чтобы данные уже были при старте, подкачиваем один блок данных */
                            if (err == LTR_OK) {
                                err = f_send_data(&hltr35, (DWORD)(hltr35.State.OutFreq * FIRST_BLOCK_TIME_MS / 1000));
                            }

                            if (err==LTR_OK) {
                                if (hltr35.Cfg.Sync.SyncMode == LTR35_SYNC_MODE_SLAVE) {
                                    err = LTR35_StreamStartRequest(&hltr35, 0);
                                    if (err != LTR_OK) {
                                        fprintf(stderr, "Ошибка запуска генерации (%d) : %s!\n",
                                                err, LTR35_GetErrorString(err));
                                    }


                                    if (err == LTR_OK) {
                                        fprintf(stdout, "Ожидание готовности подчиненнго....\n");
                                        fflush(stdout);

                                        do {
                                            err = LTR35_StreamStartWaitSlaveReady(&hltr35, 100);
                                            if (err == LTR_OK) {
                                                fprintf(stdout, "Подчиненный готов принимать сигнал запуска!\n");
                                                fflush(stdout);
                                            }
                                        } while (!f_check_out() && (err == LTR_ERROR_OP_DONE_WAIT_TOUT));

                                        if (err != LTR_OK)  {
                                            fprintf(stderr, "Ошибка ожидания готовности подчиненного (%d) : %s!\n",
                                                    err, LTR35_GetErrorString(err));
                                        }
                                    }


                                    if (err == LTR_OK) {
                                        fprintf(stdout, "Ожидание внешнего сигнала запуска....\n");
                                        fflush(stdout);

                                        do {
                                            err = LTR35_StreamStartWaitDone(&hltr35, 100);
                                            if (err == LTR_OK) {
                                                fprintf(stdout, "Наччалась генерация данных!\n");
                                                fflush(stdout);
                                            }
                                        } while (!f_check_out() && (err == LTR_ERROR_OP_DONE_WAIT_TOUT));

                                        if (err != LTR_OK)  {
                                            fprintf(stderr, "Ошибка ожидания старта генерации (%d) : %s!\n",
                                                    err, LTR35_GetErrorString(err));
                                        }
                                    }
                                } else {
                                    err = LTR35_StreamStart(&hltr35, 0);
                                    if (err!=LTR_OK) {
                                        fprintf(stderr, "Ошибка запуска потокового режима (%d) : %s!\n",
                                                err, LTR35_GetErrorString(err));
                                    }
                                }



                                if ((err == LTR_OK) && !f_check_out()) {
                                    if (repeat_mode == OPT_REPEAT_MODE_CONTINUOUS) {
                                        while ((err == LTR_OK) && !f_check_out()) {
                                            err = f_send_data(&hltr35, (DWORD)(hltr35.State.OutFreq * CONT_BLOCK_TIME_MS / 1000));
                                            if (hltr35.Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_OFF) {
                                                err = f_recv_din_data(&hltr35, cycles_cnt, (DWORD)(hltr35.State.InStreamWordFreq * CONT_BLOCK_TIME_MS / 1000));
                                            }
                                            if (err == LTR_OK)
                                                cycles_cnt = f_inc_cycles(cycles_cnt, max_cycles);
                                        }
                                    } else {
                                        if (hltr35.Cfg.InStream.InStreamMode != LTR35_IN_STREAM_MODE_OFF) {
                                            err = f_recv_din_data(&hltr35, cycles_cnt, (DWORD)(hltr35.State.InStreamWordFreq * RECV_BLOCK_TIME_MS / 1000));
                                        }

                                        if (err == LTR_OK)
                                            cycles_cnt = f_inc_cycles(cycles_cnt, max_cycles);
                                    }
                                }

                                if (hltr35.State.Run) {
                                    INT stop_err = LTR35_StopWithTout(&hltr35, 0, 10000);
                                    if (stop_err!=LTR_OK) {
                                        fprintf(stderr, "Не удалось остановить потоковый вывод. Ошибка %d:%s\n",
                                                stop_err, LTR35_GetErrorString(stop_err));
                                        if (err==LTR_OK)
                                            err = stop_err;
                                    }
                                }
                            }
                         } while ((err == LTR_OK) && !f_check_out() && (repeat_mode == OPT_REPEAT_MODE_RESTART));
                    }
                } while ((err == LTR_OK) && !f_check_out() && (repeat_mode == OPT_REPEAT_MODE_RECONFIGURE));


                /* закрываем связь с модулем */
                close_err = LTR35_Close(&hltr35);
                if (close_err!=LTR_OK) {
                    fprintf(stderr, "Не удалось закрыть связь с модулем. Ошибка %d:%s\n",
                            close_err, LTR35_GetErrorString(close_err));
                    if (err==LTR_OK)
                        err = close_err;
                }
            }
        } while ((err == LTR_OK) && !f_check_out() && (repeat_mode == OPT_REPEAT_MODE_REINIT));
    }

    return err;
}
