/* 
 * Данный пример демонстрирует возможность вывода отсчетов сигнала из файла на
 * вывод в модуль E16 / LTA37 / E502 / L502 (ввод не используется вообще).
 *
 * Любое число во входном файле трактуется как значение в Вольтах выхода ЦАП
 * Два числа разделенные любым символом кроме перевода строки трактуются как значения двух каналов ЦАП
 * Перевод строки тарактуется как переход к следующему отсчету
 */

#include "l502api.h"
#include "e502api.h"

#include <locale.h>
#include <stdio.h>
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>

#include "dev_funcs.h"
#include "timespec_funcs.h"

#ifndef _WIN32
    #include <signal.h>
    #include <string.h>
    #include <sys/time.h>
    #include <unistd.h>
#endif

//#define DEBUG
#ifdef DEBUG
    #define dbg_printf(...) fprintf(stderr, __VA_ARGS__)
#else
    #define dbg_printf(...)
#endif


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

#define DAC_BUF_SIZE    (1024 * 1024)
/* таймаут на отправку блока (мс) */
#define SEND_TOUT                 500

double dac_freq = 200000;
// задержка вывода в секундах между циклами
double cycle_delay;

// Выводить из файла циклически
int cycle_mode = 1;

#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_SetSyncMode(hnd, X502_SYNC_INTERNAL);
    X502_StreamsStop(hnd);
    X502_StreamsDisable(hnd, X502_STREAM_ALL_IN|X502_STREAM_ALL_OUT);

    err = X502_SetOutFreq(hnd, &dac_freq);
    if (err) {
        fprintf(stderr, "X502_SetOutFreq err=%d dout_freq = %.1f\n", err, dac_freq);
    }

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

    if (err == X502_ERR_OK) {
        /* выводим реально установленные значения - те что вернули функции */
        fprintf(stderr, "Установленная частота ЦАП: %0.0f Hz\n", dac_freq);
    }

    return err;
}

void parse_params(int argc, char **argv) {
    for (int i = 1; (int)i < argc - 1; i++) {
        if (sscanf(argv[i], "dac_freq:%lf", &dac_freq) == 1) {
            fprintf(stderr, "Запрашиваемая частота ЦАП: %0.0f Hz\n", dac_freq);
        } else
        if (sscanf(argv[i], "cycle_delay:%lf", &cycle_delay) == 1) {
            fprintf(stderr, "cycle_delay=%lf sec\n", cycle_delay);
        } else
        if (strcmp(argv[i], "once") == 0) {
            cycle_mode = 0;
            fprintf(stderr, "cycle_mode=0\n");
        }
    }
}

#define BUF_SIZE        (64 * 1024)

#define CH_ENABLED  2

void show_help(char *name) {
	fprintf(stderr,
		"Usage: %s [dac_freq:500000] [cycle_delay:0.0001] [once] input.file\n"
		"\tdac_freq: DAC freq in Hz\n"
		"\tcycle_delay: delay DAC output in seconds before new cycle start\n"
		"Select module by ip: %s [E16:192.168.0.1] [dac_freq:500000] [once] input.file\n"
		"\nTest input file (2 DAC):\n"
        "\t0.000000; -5.000000\n"
        "\t1.000000; -4.000000\n"
        "\t2.000000; -3.000000\n"
        "\t3.000000; -2.000000\n"
        "\t4.000000; -1.000000\n"
        "\t5.000000; -0.000000\n"
        "\t4.000000; -1.000000\n"
        "\t3.000000; -2.000000\n"
        "\t2.000000; -3.000000\n"
        "\t1.000000; -4.000000\n"
		, name, name);
}

int out_dac_from_file(t_x502_hnd hnd, FILE* f);

int main(int argc, char **argv) {
    int32_t err = 0;
    uint32_t ver;
    t_x502_hnd hnd = NULL;
    FILE* f;
#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
    /* устанавливаем локаль, чтобы можно было выводить по-русски в CP1251 без перевода в OEM */
    setlocale(LC_CTYPE, "");
#endif

    if (argc == 1) {
        show_help(argv[0]);
        return 1;
    }

    /********** Получение списка устройств и выбор, с каким будем работать ******************/
    hnd = select_dev_from_list(argc, argv, 0);
    
    if (hnd == NULL) {
        fprintf(stderr, "Error! Device not found!\n");
        return 1;
    }
    
    parse_params(argc, argv);

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

    /* Разрешаем все каналы на вывод (в этом примере мы не будем
        использовать асинхронный вывод), а какие каналы реально будут
        использованы определяем когда подаем данные в L502_PrepareData */
    err = X502_StreamsEnable(hnd, X502_STREAM_ALL_OUT);
    if (err != X502_ERR_OK) {
        fprintf(stderr, "Ошибка разрешения потоков (%d): %s!", err,
                X502_GetErrorString(err));
    }

    f = fopen(argv[argc - 1], "rb");
    if (!f) {
        fprintf(stderr, "Could not open file '%s' error %d\n", argv[argc - 1], errno);
        show_help(argv[0]);
        return 1;
    }

    err = out_dac_from_file(hnd, f);
    fclose(f);

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

    return err;
}

static struct timespec start_time;
static uint32_t* out_buf;
static int dac_size;
static double *dac_data[CH_ENABLED];

int send_data(t_x502_hnd hnd, uint32_t *send_ptr, uint32_t send_size) {
    static int started = 0;
    static uint32_t g_snd_cnt = 0;
    struct timespec cur_time;
    double spent_secs;
    struct timespec spent_time;

    while (send_size && !f_out) {
        int32_t sent;

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

        sent = X502_Send(hnd, send_ptr, send_size, SEND_TOUT);
        if (sent < 0) {
            fprintf(stderr, "Errror: X502_Send ret = %d\n", sent);
            return sent;
        }
        if (f_out) {
            return 0;
        }
        g_snd_cnt += sent;
        send_size -= sent;
        send_ptr += sent;

        if (started == 0) {
            int err;

            started = 1;
            err = X502_StreamsStart(hnd);
            if (err) {
                fprintf(stderr, "Error: X502_StreamsStart ret = %d\n", err);
                return err;
            }
        }
    }

    clock_gettime(CLOCK_MONOTONIC, &cur_time);

    timespec_diff(&cur_time, &start_time, &spent_time);
    spent_secs = timespec_to_double(&spent_time);

    if (spent_secs > 5) {
        fprintf(stderr, "snd speed=%f wrds/sec\n", (g_snd_cnt) / (spent_secs));
        g_snd_cnt = 0;
        start_time = cur_time;
    }
    return send_size;
}

int send_dac_data(t_x502_hnd hnd) {
    int err;

    if (dac_size != DAC_BUF_SIZE) {
        return 0;
    }

#if 0
        for (int i = 0; i < dac_size; i++){
            fprintf(stderr, "[%d]   %f; %f\n", i, dac_data[0][i], dac_data[1][i]);
        }
#endif
    // TODO: для LTA37 надо использовать PrepareData2()
    err = X502_PrepareData(hnd,
        dac_data[0],
        dac_data[1],
        NULL,
        dac_size, X502_DAC_FLAGS_VOLT | X502_DAC_FLAGS_CALIBR,
        out_buf);

    if (err) {
        fprintf(stderr, "Error: X502_PrepareData ret = %d\n", err);
        return err;
    }

    err = send_data(hnd, out_buf, dac_size * CH_ENABLED);

    dac_size = 0;

    return err;
}

int out_dac_from_file(t_x502_hnd hnd, FILE* f) {
    int32_t err;
    char *buf;
    int items_read;
    double dac_val;
    char *read_start;
    int cntr = 0;
    int dac_ch = 0;
    bool newline = false;
    bool comment_start = false;
    bool data_added = false;
    char *last_endptr;
    double last_dac_data[CH_ENABLED];

    for (int ch = 0; ch < CH_ENABLED; ch++) {
        dac_data[ch] = (double*)malloc(sizeof(double) * DAC_BUF_SIZE);
        if (!dac_data[ch]) {
            fprintf(stderr, "Out of memory\n");
            return 1;
        }
    }

    out_buf = (uint32_t*)malloc(sizeof(uint32_t) * DAC_BUF_SIZE * CH_ENABLED);
    buf = (char*)malloc(BUF_SIZE);
    if (!out_buf || !buf) {
        fprintf(stderr, "Out of memory\n");
        return 1;
    }

    read_start = buf;
    last_endptr = &buf[BUF_SIZE];

    clock_gettime(CLOCK_MONOTONIC, &start_time);

    while (!f_out) {
        size_t buf_ready = (read_start - buf);

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

        if (feof(f) && cycle_mode) {
            int delay_cntr = cycle_delay * dac_freq;
            do {
                for (; (dac_size != DAC_BUF_SIZE) && delay_cntr; dac_size++, delay_cntr--) {
                    for (int ch = 0; ch < CH_ENABLED; ch++) {
                        dac_data[ch][dac_size] = last_dac_data[ch];
                    }
                }
                if ((err = send_dac_data(hnd)) < 0) {
                    return err;
                }
            } while(delay_cntr);
        }

        if (feof(f)) {
            if (cycle_mode) {
                fseek(f, 0, SEEK_SET);
            } else {
                break;
            }
        }

        items_read = fread(read_start, 1, BUF_SIZE - buf_ready, f);
        dbg_printf("read_start=::::%.*s::::\n", (int)buf_ready, buf);
        dbg_printf("read=%d '''''%.*s'''''\n", items_read, items_read, read_start);

        for (char *ptr = buf, *endptr = buf; ; ptr = endptr) {
            char *buf_end = &buf[buf_ready + items_read];

            dac_val = strtod(ptr, &endptr);
            if (endptr >= buf_end) {
                memmove(buf, last_endptr, buf_end - last_endptr);
                read_start = buf + (buf_end - last_endptr);
                break;
            } else
            if (endptr == ptr) {
                endptr++;
                if (*ptr == '\n') {
                    newline = true;
                    comment_start = false;
                }
                if (*ptr == '#') {
                    comment_start = true;
                }
            } else {
                last_endptr = endptr;
                for (char *n_ptr = ptr; n_ptr < endptr; n_ptr++) {
                    if (*n_ptr == '\n') {
                        newline = true;
                        comment_start = false;
                        break;
                    }
                }
                if (comment_start) {
                    continue;
                }
                if (newline) {
                    dac_ch = 0;
                }

                if (dac_ch >= CH_ENABLED) {
                    fprintf(stderr, "dac_ch = %d >= %d (CH_ENABLED)\n", dac_ch, CH_ENABLED);
                    dac_ch %= CH_ENABLED;
                }

                if (newline && data_added) {
                    data_added = false;
                    dac_size++;
                    for (int ch = 0; ch < CH_ENABLED; ch++) {
                        dac_data[ch][dac_size] = dac_data[ch][dac_size - 1];
                    }
                }

                data_added = true;
                newline = false;

                dbg_printf("dac_val=%lf cntr=%d\n", dac_val, cntr);

                if ((err = send_dac_data(hnd)) < 0) {
                    return err;
                }

                dac_data[dac_ch][dac_size] = dac_val;
                last_dac_data[dac_ch] = dac_val;
                dac_ch++;
            }
        }
    }
    return 0;
}
