/* Пример демонстрирует вывод синусоидального сигнала в циклическом режиме
   или от арифметических генераторов модуля */
#include "ltr/include/ltr35api.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <getopt.h>
#ifdef _WIN32
#include <locale.h>
#include <conio.h>
#else
#include <signal.h>
#include <unistd.h>
#endif

#define OPT_HELP                'h'
#define OPT_AMP                 0x1000
#define OPT_AMP_CODE            0x1001
#define OPT_POINTS              0x1002
#define OPT_DAC_CHANNEL         0x1006
#define OPT_SIG_MODE            0x1007


#define ERR_INVALID_USAGE           -1
#define ERR_MODULE_NOT_FOUND        -2

typedef struct {
    e_LTR35_RATE rate;
    DWORD points;
    double amp;
    int ch_msk;
    DWORD flags;
    INT slot;
    int arith;
    char csn[LTR_CRATE_SERIAL_SIZE];
} t_ltr35_opts;


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

/* опции программы */
static const struct option f_long_opt[] = {
    {"help",            no_argument,       0, OPT_HELP},
    {"amp",             required_argument, 0, OPT_AMP},
    {"amp-codes",       required_argument, 0, OPT_AMP_CODE},
    {"points",          required_argument, 0, OPT_POINTS},
    {"ch",              required_argument, 0, OPT_DAC_CHANNEL},
    {"sig-mode",        required_argument, 0, OPT_SIG_MODE},
    {0,0,0,0}
};

static const char* f_opt_str = "h";


static const char* f_usage_descr = \
"\nИспользование: ltr35 [OPTIONS]\n\n" \
" Утилита ltr35 находит первый модуль LTR35 и устанавливает синус на всех \n"
"   каналах ЦАП с заданными параметрами (кол-во точек и амплитуда)\n";

static const char* f_options_descr =
"Опции:\n" \
"     --amp=val         - Задает амплитуду сигнала в Вольтах.\n"
"     --apm-codes=val   - Задает амплитуду сигнала в кодах ЦАП.\n"
"     --ch=val          - Указание канала, на который будет выведен сигнал \n"
"                           (от 1 до 8). Может использоваться несколько раз.\n"
"                           Если ни разу не указана - вывод на все каналы.\n"
"     --points          - Задает количество точек в сигнале на канал.\n"
"     --sig-mode=mode   - режим генерации сигнала:\n"
"                          - sdram - точки загружаются в память модуля с ПК\n"
"                          - arith - сигнал генерируе арифметическими\n"
"                                    генераторами модуля\n";

static void  f_print_usage(void) {
    fprintf(stdout, "%s", f_usage_descr);
    fprintf(stdout,"\n");
    fprintf(stdout, "%s", f_options_descr);
}


static int f_parse_params(int argc, char** argv, t_ltr35_opts *cfg, int *out) {
    int err=0;
    int opt = 0;

    memset(cfg, 0, sizeof(t_ltr35_opts));
    cfg->flags = 0;
    cfg->points = 2;

    /************************** разбор опций *****************************/
    while ((opt!=-1) && !err && !*out) {
        opt = getopt_long(argc, argv, f_opt_str,
                          f_long_opt, 0);
        switch (opt) {
            case -1:
                break;
            case OPT_HELP:
                f_print_usage();
                *out = 1;
                break;
            case OPT_AMP: {
                    char *endptr=0;

                    cfg->amp = strtod(optarg, &endptr);
                    if (!endptr || (endptr==optarg) || *endptr || (cfg->amp < 0)) {
                        fprintf(stderr, "Ошибка: Неверно задана амплитуда!\n");
                        err = ERR_INVALID_USAGE;
                    }
                    cfg->flags |= LTR35_PREP_FLAGS_VOLT;
                }
                break;
            case OPT_AMP_CODE: {
                    DWORD val;
                    char *endptr=0;
                    val = strtol(optarg, &endptr, 0);
                    if (!endptr || (endptr==optarg) || *endptr || (val > 0x7FFFFF)) {
                        fprintf(stderr, "Ошибка: Неверно задан код амплитуды!\n");
                        err = ERR_INVALID_USAGE;
                    }
                    cfg->amp = val;
                    cfg->flags &= ~LTR35_PREP_FLAGS_VOLT;
                }
                break;
            case OPT_POINTS: {
                    char *endptr=0;
                    cfg->points = strtol(optarg, &endptr, 0);
                    if (!endptr || (endptr==optarg) || *endptr || (cfg->points==0)) {
                        fprintf(stderr, "Ошибка: Неверно задано количество точек!\n");
                        err = ERR_INVALID_USAGE;
                    } else if  (cfg->points > (LTR35_MAX_POINTS_PER_PAGE/LTR35_DAC_CHANNEL_CNT)) {
                        fprintf(stderr, "Ошибка! Количество точек не должно быть больше %d\n", LTR35_MAX_POINTS_PER_PAGE/LTR35_DAC_CHANNEL_CNT);
                        err = ERR_INVALID_USAGE;
                    }
                }
                break;
            case OPT_SIG_MODE:
                if (!strcmp(optarg, "arith")) {
                    cfg->arith = 1;
                } else if (strcmp(optarg, "sdram")) {
                    fprintf(stderr, "Ошибка: Неверный режим источника сигнала!\n");
                    err = ERR_INVALID_USAGE;
                }
                break;
            case OPT_DAC_CHANNEL: {
                    char *endptr=0;
                    int ch = strtol(optarg, &endptr, 0);
                    if (!endptr || (endptr==optarg) || *endptr || (ch==0)) {
                        fprintf(stderr, "Ошибка: Неверно задан канал ЦАП!\n");
                        err = ERR_INVALID_USAGE;
                    } else if  (ch > LTR35_DAC_CHANNEL_CNT) {
                        fprintf(stderr, "Ошибка! Номер канала превышает кол-во каналов LTR35\n");
                        err = ERR_INVALID_USAGE;
                    } else  {
                        cfg->ch_msk |= (1 << (ch-1));
                    }
                }
                break;
        }
    }

    if (!cfg->ch_msk)
        cfg->ch_msk = 0xFF;

    return err;
}


static int f_get_slot(int *slot, char* serial) {
    TLTR srv;
    int fnd=0;
    INT err = LTR_OK;
    LTR_Init(&srv);
    err = LTR_OpenSvcControl(&srv, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT);
    if (err != LTR_OK) {
        fprintf(stderr, "Не удалось установить связь с LTR-сервером. Ошибка %d: %s!\n",
                err, LTR_GetErrorString(err));
    } else {
        char serial_list[LTR_CRATES_MAX][LTR_CRATE_SERIAL_SIZE];
        err=LTR_GetCrates(&srv, (BYTE*)&serial_list[0][0]);
        LTR_Close(&srv);
        if (err != LTR_OK) {
            fprintf(stderr, "Не удалось получить список крейтов. Ошибка %d: %s!\n",
                    err, LTR_GetErrorString(err));
        } else {
            int crate_ind;
            for (crate_ind = 0 ; (crate_ind < LTR_CRATES_MAX) && !fnd; crate_ind++) {
                if (serial_list[crate_ind][0] != '\0') {
                    TLTR crate;
                    INT crate_err = LTR_OK;
                    LTR_Init(&crate);
                    crate_err =  LTR_OpenCrate(&crate, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT,
                                               LTR_CRATE_IFACE_UNKNOWN, serial_list[crate_ind]);
                    if (crate_err == LTR_OK) {
                        WORD mids[LTR_MODULES_PER_CRATE_MAX];
                        crate_err = LTR_GetCrateModules(&crate, mids);
                        if (crate_err == LTR_OK) {
                            int module_ind;
                            for (module_ind = 0; (module_ind < LTR_MODULES_PER_CRATE_MAX) && !fnd; module_ind++) {
                                if (mids[module_ind] == LTR_MID_LTR35) {
                                    fnd = 1;
                                    strcpy(serial, crate.csn);
                                    *slot = module_ind+LTR_CC_CHNUM_MODULE1;
                                }
                            }
                        }
                        LTR_Close(&crate);
                    }
                }
            }
        }
    }

    if (err == LTR_OK) {
        if (!fnd) {
            fprintf(stderr, "Ни одного модуля LTR35 не было найдено!\n");
            err = ERR_MODULE_NOT_FOUND;
        } else {
            printf("Найден модуль LTR35: крейт %s, слот %d\n", serial, *slot);
        }
    }
    return err;
}


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


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


int main(int argc, char** argv) {
    t_ltr35_opts opts;
    INT err = 0;
    INT out = 0;

#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


    /* разбор параметров входной строки */
    err = f_parse_params(argc, argv, &opts, &out);
    /* находим модуль LTR35 */
    if ((err == LTR_OK) && !out) {
        err = f_get_slot(&opts.slot, opts.csn);
    }

    if ((err == LTR_OK) && !out) {
        TLTR35 hltr35;
        LTR35_Init(&hltr35);
        /* Устанавливаем соединение с модулем */
        err = LTR35_Open(&hltr35, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT, opts.csn, opts.slot);
        if (err!=LTR_OK) {
            fprintf(stderr, "Не удалось установить связь с модулем. Ошибка %d (%s)\n",
                    err, LTR35_GetErrorString(err));
        } else {
            double fnd_freq;
            /* вывод информации о модуле */

            printf("Модуль открыт успешно!\n");
            printf("  Название модуля    = %s\n", hltr35.ModuleInfo.Name);
            printf("  Серийный номер     = %s\n", hltr35.ModuleInfo.Serial);
            printf("  Модификация        = LTR35-%d\n", hltr35.ModuleInfo.Modification);
            printf("  Версия PLD         = %d\n", hltr35.ModuleInfo.VerPLD);
            printf("  Версия ПЛИС        = %d\n", hltr35.ModuleInfo.VerFPGA);
            printf("  Выходов ЦАП        = %d\n", hltr35.ModuleInfo.DacChCnt);
            printf("  Цифровых выходов   = %d\n", hltr35.ModuleInfo.DoutLineCnt);

            fflush(stdout);

            /* настройка частоты генерации ЦАП */
            err = LTR35_FillOutFreq(&hltr35.Cfg, 192*1000, &fnd_freq);
            if (err == LTR_OK) {
                int ch_num = 0, src_num=0;
                BYTE sources[LTR35_DAC_CHANNEL_CNT] = {
                    LTR35_CH_SRC_SIN1,
                    LTR35_CH_SRC_COS1,
                    LTR35_CH_SRC_SIN2,
                    LTR35_CH_SRC_COS2,
                    LTR35_CH_SRC_SIN3,
                    LTR35_CH_SRC_COS3,
                    LTR35_CH_SRC_SIN4,
                    LTR35_CH_SRC_COS4
                };

                /* Настройка разрешения каналов и источник данных
                   (из памяти или выбранный арифметический генератор */
                for (ch_num = 0; ch_num < hltr35.ModuleInfo.DacChCnt; ++ch_num) {
                    hltr35.Cfg.Ch[ch_num].Enabled = ((1 << ch_num) & opts.ch_msk) != 0;
                    hltr35.Cfg.Ch[ch_num].Source = opts.arith ? sources[ch_num] : LTR35_CH_SRC_SDRAM;
                }

                for (src_num = 0; src_num < LTR35_ARITH_SRC_CNT; ++src_num) {
                    LTR35_FillArithPhaseDegree(&hltr35.Cfg.ArithSrc[src_num].Phase, 360*src_num/4, NULL);
                    LTR35_FillArithPhaseDegree(&hltr35.Cfg.ArithSrc[src_num].Delta, 360*(src_num+1)/(opts.points), NULL);
                }
                hltr35.Cfg.OutMode = LTR35_OUT_MODE_CYCLE;
                hltr35.Cfg.OutDataFmt = LTR35_OUTDATA_FORMAT_24;

                /* запись настроек в модуль */
                err = LTR35_Configure(&hltr35);
                if (err != LTR_OK) {
                    fprintf(stderr, "Не удалось настроить модуль. Ошибка %d (%s)\n",
                            err, LTR35_GetErrorString(err));
                }
            }

            /* если были настроенные каналы с источником данных из SDRAM,
               то загружаем сигнал в память перед запуском */
            if ((err == LTR_OK) && (hltr35.State.SDRAMChCnt != 0)) {
                DWORD* wrds = NULL;
                double* sig = NULL;
                DWORD snd_size;

                wrds = malloc(opts.points*2*hltr35.State.SDRAMChCnt*sizeof(wrds[0]));
                sig = malloc(opts.points*hltr35.State.SDRAMChCnt*sizeof(sig[0]));
                if ((wrds==NULL) || (sig==NULL)) {
                    fprintf(stderr, "Ошибка выделения памяти");
                    err = LTR_ERROR_MEMORY_ALLOC;
                }

                if (err == LTR_OK) {
                    INT ch_num;
                    DWORD i;
                    const double pi = 3.1415926535897932384626433832795;

                    for (i=0; i < opts.points; ++i) {
                        for (ch_num=0; ch_num < hltr35.State.SDRAMChCnt; ++ch_num) {
                            sig[hltr35.State.SDRAMChCnt*i+ch_num] = opts.amp * cos(2.*pi*i*(ch_num+1)/(opts.points));
                        }
                    }
                    err = LTR35_PrepareDacData(&hltr35, sig, opts.points*hltr35.State.SDRAMChCnt,
                                               opts.flags, wrds, &snd_size);
                    if (err!=LTR_OK) {
                        fprintf(stderr, "Ошибка подготовки данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
                    }
                }

                if (!err) {
                    INT sent_cnt = LTR35_Send(&hltr35, wrds, snd_size, 10000);

                    if (sent_cnt < 0) {
                        err = sent_cnt;
                        fprintf(stderr, "Ошибка передачи данных (%d) : %s!\n", err, LTR35_GetErrorString(err));
                    } else if (sent_cnt != (INT)snd_size)  {
                        fprintf(stderr, "Ошибка! Передано меньше слов чем запрашивали (запрашивали %d, передали %d)\n",
                                snd_size, sent_cnt);
                        err = LTR_ERROR_SEND_INSUFFICIENT_DATA;
                    }
                }
            }


            if (!err) {
                /* запуск генерации данных переключением на первую страницу */
                err = LTR35_SwitchCyclePage(&hltr35, 0, 30000);
                if (err!=LTR_OK) {
                    fprintf(stderr, "Ошибка переключения страницы (%d) : %s!\n", err, LTR35_GetErrorString(err));
                } else {
                    printf("Данные установленны успешно!\n"); fflush(stdout);
                    printf("Для останова нажмите %s\n",
       #ifdef _WIN32
                           "любую клавишу"
       #else
                           "CTRL+C"
       #endif
                           );
                    fflush(stdout);
                    while (!f_check_out()) {
                        LTRAPI_SLEEP_MS(100);
                    }

                    err = LTR35_Stop(&hltr35, 0);
                    if (err != LTR_OK) {
                        fprintf(stderr, "Ошибка останова генерации данных (%d) : %s!\n",
                                err, LTR35_GetErrorString(err));
                    }
                }
            }
        }
        LTR35_Close(&hltr35);
    }

    return err;
}
