#include "ltr/include/ltr27api.h"

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

#ifdef _WIN32
#include <locale.h>
#endif

#include "getopt.h"


/*================================================================================================*/
/* количество отсчетов на каждый мезонин, принимаемых за раз (блок) */
#define RECV_FRAMES     (4)
/* количество принятых блоков по RECV_FRAMES отсчетов */
#define RECV_BLOCK_CNT  (3)
/* таймаут в мс на прием блока */
#define RECV_BLOCK_TOUT  (10000)


/*================================================================================================*/
typedef struct {
    int slot;
    const char *serial;
    int reopen;
    int stop_card;
    int store_config;
    int auto_run;
    TLTR_CARD_START_MODE start_mode;
    DWORD addr;
} t_open_param;


/*================================================================================================*/
static int f_get_params(int argc, char **argv, t_open_param *par);


/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
static int f_get_params(int argc, char **argv, t_open_param *par) {
    int optch;
    INT err = 0;
    const char optstr[] = "p:ocgsmn:a:";

    par->slot = LTR_CC_CHNUM_MODULE1;
    par->serial = "";
    par->reopen = 0;
    par->stop_card = 1;
    par->store_config = 0;
    par->addr = LTRD_ADDR_DEFAULT;
    par->auto_run = 0;
    par->start_mode = LTR_CARD_START_OFF;

    while ((optch = getopt_long(argc, argv, optstr, NULL, NULL)) != -1) {
        switch (optch) {
        case 'p':
            par->slot = atoi(optarg);
            break;
        case 'o':
            par->auto_run = 1;
            break;
        case 'c':
            par->reopen = 1;
            break;
        case 'g':
            par->stop_card = 0;
            break;
        case 's':
            par->store_config = 1;
            break;
        case 'm':
            par->start_mode = LTR_CARD_START_RUN;
            break;
        case 'n':
            par->serial = optarg;
            break;
        case 'a': {
            int a[4];
            int i;
            if (sscanf(optarg, "%d.%d.%d.%d", &a[0], &a[1], &a[2], &a[3]) != 4) {
                fprintf(stderr, "Неверный формат адреса сервера!!\n");
                return -1;
            }
            for (i = 0; (i < 4); i++) {
                if ((a[i] < 0) || (255 < a[i])) {
                    fprintf(stderr, "Неверный формат адреса сервера!!\n");
                    return -1;
                }
            }
            par->addr = (a[0] << 24) | (a[1] << 16) | (a[2] << 8) | a[3];
            break;
        }
        default:
            fprintf(stderr, "Ошибка в опциях!!\n");
            return -1;
        }
    }

    return 0;
}

/*------------------------------------------------------------------------------------------------*/
int main(int argc, char **argv) {
    DWORD i;
    DWORD j;
    TLTR27 ltr27;
    t_open_param par;
    INT err = 0;
    DWORD out_flags = 0;
    INT stop_err = 0;

#ifdef _WIN32
    setlocale(LC_ALL, "");
#endif

    err = f_get_params(argc, argv, &par);

    if (err == LTR_OK) {
        DWORD in_flags = par.reopen ? LTR_OPENINFLG_REOPEN : 0;
        LTR27_Init(&ltr27);
        /* устанавливаем соединение с модулем находящемся в первом слоте крейта.
         * для сетевого адреса, сетевого порта ltr-сервера и серийного номера
         * крейта используем значения по умолчанию
         */
        err = LTR27_OpenEx(&ltr27, par.addr, LTRD_PORT_DEFAULT, par.serial, par.slot, in_flags,
            &out_flags);
        if (err == LTR_OK) {
            printf("Модуль открыт успешно!\n");
            if (out_flags & LTR_OPENOUTFLG_REOPEN)
                printf("Было осуществлено подключение к работающему модулю\n");
        } else {
             fprintf(stderr, "Не удалось открыть модуль. Ошибка %d (%s)\n", err,
                 LTR27_GetErrorString(err));
        }
    }

    if (err == LTR_OK) {
        if (!(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* получаем конфигурацию модуля:
             * тип установленных мезонинов и параметры сбора данных АЦП
             */
            err = LTR27_GetConfig(&ltr27);
            if (err != LTR_OK) {
                fprintf(stderr, "Не удалось прочитать конфигурацию. Ошибка %d (%s)\n", err,
                    LTR27_GetErrorString(err));
            }
        }

        if ((err == LTR_OK) && !(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* считываем описание модуля */
            err = LTR27_GetDescription(&ltr27,  LTR27_MODULE_DESCRIPTION);

            /* считываем информацию о мезонинах */
            for (i = 0; ((i < LTR27_MEZZANINE_CNT) && (err == LTR_OK)); i++) {
                if (strcmp(ltr27.Mezzanine[i].Name, "EMPTY") != 0)
                    err = LTR27_GetDescription(&ltr27, LTR27_MEZZANINE1_DESCRIPTION << i);
            }

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


        if (err == LTR_OK) {
            printf("Прочитана информация о модуле :\n");
            printf("    Название       : %s\n", ltr27.ModuleInfo.Module.DeviceName);
            printf("    Серийный номер : %s\n", ltr27.ModuleInfo.Module.SerialNumber);
            if (ltr27.ModuleInfo.Cpu.Active) {
                printf("   Информация о процессоре :\n");
                printf("     Название        : %s\n", ltr27.ModuleInfo.Cpu.Name);
                printf("     Частота клока   : %.1f Hz\n", ltr27.ModuleInfo.Cpu.ClockRate);
                printf("     Версия прошивки : %d.%d.%d.%dX\n",
                    (ltr27.ModuleInfo.Cpu.FirmwareVersion>>24) & 0xFF,
                    (ltr27.ModuleInfo.Cpu.FirmwareVersion>>16) & 0xFF,
                    (ltr27.ModuleInfo.Cpu.FirmwareVersion>>8) & 0xFF,
                    ltr27.ModuleInfo.Cpu.FirmwareVersion & 0xFF);
                printf("     Комментарий     : %s\n", ltr27.ModuleInfo.Cpu.Comment);
            } else {
                printf("  Не найдено действительное описание процессора!!!\n");
            }

            for (i = 0; (i < LTR27_MEZZANINE_CNT); i++) {
                if (ltr27.ModuleInfo.Mezzanine[i].Active) {
                    printf("    Информация о мезонине в слоте %d :\n", i+1);

                    printf("      Название        : %s\n", ltr27.ModuleInfo.Mezzanine[i].Name);
                    printf("      Серийный номер  : %s\n", ltr27.ModuleInfo.Mezzanine[i].SerialNumber);
                    printf("      Ревизия         : %d\n", (int)ltr27.ModuleInfo.Mezzanine[i].Revision);
                    for (j = 0; (j < 4); j++) {
                        printf("      Калибр. коэф. %d : %.3f\n", j,
                            ltr27.ModuleInfo.Mezzanine[i].Calibration[j]);
                    }
                }
            }

            fflush(stdout);
        }

        if ((err == LTR_OK) && !(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* выбираем частоту дискретизции 1000Гц */
            ltr27.FrequencyDivisor = 255;
            /* копируем калибровочные коэффициенты (в противном случае не будут применяться) */
            for (i = 0; (i < LTR27_MEZZANINE_CNT); i++) {
                for (j = 0; (j < 4); j++) {
                    ltr27.Mezzanine[i].CalibrCoeff[j] =
                        ltr27.ModuleInfo.Mezzanine[i].Calibration[j];
                }
            }
            /* передаем параметры сбора данных в модуль */
            err = LTR27_SetConfig(&ltr27);
            if (err) {
                fprintf(stderr, "Не удалось записать настройки модуля. Ошибка %d (%s)\n", err,
                    LTR27_GetErrorString(err));
            }
        }

        if ((err == LTR_OK) && !(out_flags & LTR_OPENOUTFLG_REOPEN)) {
            /* запускаем сбор данных АЦП */
            err = LTR27_ADCStart(&ltr27);
            if (err) {
                fprintf(stderr, "Не удалось запустить сбор данных. Ошибка %d (%s)\n", err,
                    LTR27_GetErrorString(err));
            }
        }

        if (err == LTR_OK) {
            DWORD b;
            DWORD buf[RECV_FRAMES][2*LTR27_MEZZANINE_CNT];
            double data[RECV_FRAMES][2*LTR27_MEZZANINE_CNT];
            INT recv_res = 0;
            DWORD *pdata = &buf[0][0];
            DWORD size = sizeof(buf) / sizeof(buf[0][0]);

            printf("Сбор данных запущен успешно\n");

            if (out_flags & LTR_OPENOUTFLG_REOPEN) {
                recv_res = LTR27_Recv(&ltr27, &buf[0][0], NULL, size, RECV_BLOCK_TOUT);
                if (recv_res < 0) {
                    err = recv_res;
                    fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                        LTR27_GetErrorString(err));
                } else if ((DWORD)recv_res != size) {
                    fprintf(stderr, "Принято неверное количество данных. Запрашивали %d, приняли %d\n",
                        size, recv_res);
                    err = LTR_ERROR_RECV;
                } else {
                    DWORD frame_idx;
                    err = LTR27_SearchFirstFrame(&ltr27, &buf[0][0], size, &frame_idx);
                    if (err == LTR_OK) {
                        if (frame_idx > 0) {
                            size -= frame_idx;
                            memmove(&buf[0][0], &buf[0][0] + frame_idx, size * sizeof buf[0][0]);
                            pdata += size;
                            size = frame_idx;
                        }
                    } else {
                        fprintf(stderr, "Ошибка поиска начала кадра - %d:%s\n", err,
                            LTR27_GetErrorString(err));
                    }
                }
            }

            for (b = 0;
                (err == LTR_OK)
#if RECV_BLOCK_CNT > 0
                 && (b < RECV_BLOCK_CNT)
#endif
                 ;
                b++) {
                /* забираем данные АЦП */
                if (size > 0)
                    recv_res = LTR27_Recv(&ltr27, pdata, NULL, size, RECV_BLOCK_TOUT);
                if (recv_res < 0) {
                    err = recv_res;
                    fprintf(stderr, "Ошибка приема данных. Ошибка %d:%s\n", err,
                        LTR27_GetErrorString(err));
                } else if ((DWORD)recv_res != size) {
                    fprintf(stderr,
                        "Принято неверное количество данных. Запрашивали %d, приняли %d\n", size,
                        recv_res);
                    err = LTR_ERROR_RECV;
                } else {
                    /* применяем калибровку, переводим в вольты */
                    DWORD n = sizeof(buf) / sizeof(buf[0][0]);
                    err = LTR27_ProcessData(&ltr27, &buf[0][0], &data[0][0], &n, 1, 1);
                    if (err == LTR_OK) {
                        /* отображаем данные */
                        printf("Успешно приняли блок данных %d:\n", b+1);
                        for (i = 0; (i < RECV_FRAMES); i++) {
                            for (j = 0; (j < 2 * LTR27_MEZZANINE_CNT); j++) {
                                if (strcmp(ltr27.Mezzanine[j>>1].Name, "EMPTY") != 0)
                                    printf("  %.4f %s", data[i][j], ltr27.Mezzanine[j>>1].Unit);
                            }
                            printf("\n");
                        }

                        fflush(stdout);
                    } else {
                        fprintf(stderr, "Ошибка обработки данных! Ошибка %d (%s)\n", err,
                            LTR27_GetErrorString(err));
                    }
                }
                pdata = &buf[0][0];
                size = sizeof(buf) / sizeof(buf[0][0]);
            }

            if (par.stop_card) {
                /* останавливаем АЦП */
                stop_err = LTR27_ADCStop(&ltr27);
                if (stop_err != LTR_OK) {
                    fprintf(stderr, "Не удалось остановить сбор данных. Ошибка %d (%s)\n", stop_err,
                        LTR27_GetErrorString(stop_err));
                    if (err == LTR_OK)
                        err = stop_err;
                } else {
                    printf("Сбор данных остановлен успешно\n");
                }
            } else {
                printf("Работа программы завершена. Модуль не остновлен.\n");
            }
        } /*if (err == LTR_OK)*/

        if ((err == LTR_OK) && par.store_config) {
            err = LTR27_StoreConfig(&ltr27, par.start_mode);
            if (err == LTR_OK) {
                printf("Конфигурация успешно сохранена.\n");
            } else {
                fprintf(stderr, "Не удалось сохранить конфигурацию модуля. Ошибка %d (%s)\n", err,
                    LTR27_GetErrorString(err));
            }
        }

        if ((err == LTR_OK) && par.store_config) {
            TLTR h_ltr;
            err = LTR_Init(&h_ltr);
            if (err == LTR_OK) {
                err = LTR_OpenCrate(&h_ltr, ltr27.Channel.saddr, ltr27.Channel.sport,
                                    LTR_CRATE_IFACE_UNKNOWN, ltr27.Channel.csn);
            }
            if (err == LTR_OK) {
                TLTR_SETTINGS cfg;
                cfg.size = sizeof(TLTR_SETTINGS);
                cfg.autorun_ison = par.auto_run;
                err = LTR_PutSettings(&h_ltr, &cfg);
                stop_err = LTR_Close(&h_ltr);
                if (stop_err != LTR_OK) {
                    fprintf(stderr, "Не удалось закрыть соединение с крейтом. Ошибка %d (%s)\n",
                        stop_err, LTR27_GetErrorString(stop_err));
                    if (err == LTR_OK)
                        err = stop_err;
                }
            }
            if (err != LTR_OK) {
                fprintf(stderr, "Не удалось сохранить конфигурацию крейта. Ошибка %d (%s)\n",
                    err, LTR27_GetErrorString(err));
            }
        }

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

    return err;
}

