//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "MainUnit.h"


//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "CSPIN"
#pragma resource "*.dfm"

TMainForm *MainForm;
//---------------------------------------------------------------------------

/* Метод разрешает/запрещает разные элементы управления в зависимости от
  текущего состояния программы */
void TMainForm::updateControls()
{
    bool module_opened=LTR210_IsOpened(&hltr210)==LTR_OK;
    bool devsel = (ltr210_list.Length > 0) && (cbbModulesList->ItemIndex >= 0);


    //обновление списка устройств и выбор можно делать только пока не открыто конкретное устройство
    btnRefreshDevList->Enabled = !module_opened;
    cbbModulesList->Enabled = !module_opened;

    //установить связь можно только если выбрано устройство
    btnOpen->Enabled = devsel && !load_progr;
    if (module_opened)
        btnOpen->Caption = "Закрыть соединение";
    else
        btnOpen->Caption = "Установить соединение";

    btnStart->Enabled = module_opened && !threadRunning && !load_progr;
    btnStop->Enabled = module_opened && threadRunning;
    //програмный запуск кадра имеет смысл только при SyncMode = LTR210_SYNC_MODE_INTERNAL
    btnFrameStart->Enabled = threadRunning && (hltr210.Cfg.SyncMode == LTR210_SYNC_MODE_INTERNAL);

    //изменение настроек возможно только при открытом устройстве и не запущенном сборе
    bool change_en = module_opened && !threadRunning && !load_progr;
    chkChEn1->Enabled = change_en;
    chkChEn2->Enabled = change_en;
    cbbRange1->Enabled = change_en;
    cbbRange2->Enabled = change_en;
    cbbMode1->Enabled = change_en;
    cbbMode2->Enabled = change_en;
    edtSyncLevelL1->Enabled = change_en;
    edtSyncLevelL2->Enabled = change_en;
    edtSyncLevelH1->Enabled = change_en;
    edtSyncLevelH2->Enabled = change_en;
    cbbDigBit1->Enabled = change_en;
    cbbDigBit2->Enabled = change_en;
    seFrameSize->Enabled = change_en;
    seHistSize->Enabled = change_en;
    edtAdcFreq->Enabled = change_en;
    edtFrameFreq->Enabled = change_en;
    cbbSyncMode->Enabled = change_en;
    cbbGroupMode->Enabled = change_en;
    chkKeepaliveEn->Enabled = change_en;
    chkWriteAutoSusp->Enabled = change_en;

}


void TMainForm::refreshDeviceList() {
    INT err = LTR_OK;
    int modules_cnt = 0;
    ltr210_list.Length = 0;
    cbbModulesList->Items->Clear();

    // устанавливаем связь с управляющим каналом сервера, чтобы получить список крейтов
    TLTR srv;
    LTR_Init(&srv);
    err=LTR_OpenSvcControl(&srv, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT);
    if (err!=LTR_OK) {
        MessageDlg("Не удалось установить связь с сервером: " + String(LTR_GetErrorString(err)),
                    mtError, TMsgDlgButtons() << mbOK,NULL);
    } else {
        char serial_list[LTR_CRATES_MAX][LTR_CRATE_SERIAL_SIZE];
        //получаем список серийных номеров всех подключенных крейтов
        err=LTR_GetCrates(&srv, &serial_list[0][0]);
        //серверное соединение больше не нужно - можно закрыть
        LTR_Close(&srv);
        if (err!=LTR_OK) {
             MessageDlg("Не удалось получить список крейтов: " + String(LTR_GetErrorString(err)),
                    mtError, TMsgDlgButtons() << mbOK,NULL);
        } else {
            for (int crate_ind=0 ; crate_ind < LTR_CRATES_MAX; crate_ind++) {
                //каждому крейту соответствует непустой серийный номер
                if (serial_list[crate_ind][0]!='\0') {
                    // Устанавливаем связь с каждым крейтом, чтобы получить список модулей
                    TLTR crate;
                    LTR_Init(&crate);
                    err =  LTR_OpenCrate(&crate, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT,
                                         LTR_CRATE_IFACE_UNKNOWN, serial_list[crate_ind]);
                    if (err==LTR_OK) {
                        WORD mids[LTR_MODULES_PER_CRATE_MAX];
                        err=LTR_GetCrateModules(&crate, mids);
                        if (err==LTR_OK) {
                            for (int module_ind=0; module_ind < LTR_MODULES_PER_CRATE_MAX; module_ind++) {
                                //ищем модули LTR210
                                if (mids[module_ind]==LTR_MID_LTR210) {
                                    // сохраняем информацию о найденном модуле, необходимую для
                                    // последующего установления соединения с ним, в список
                                    modules_cnt++;
                                    ltr210_list.Length=modules_cnt;
                                    ltr210_list[modules_cnt-1].csn = String(serial_list[crate_ind]);
                                    ltr210_list[modules_cnt-1].slot = module_ind+LTR_CC_CHNUM_MODULE1;
                                    // и добавляем в ComboBox для возможности выбора нужного
                                    cbbModulesList->AddItem("Крейт " + ltr210_list[modules_cnt-1].csn +
                                            ", Слот " + IntToStr(ltr210_list[modules_cnt-1].slot), NULL);
                                }
                            }
                        }
                        //закрываем соединение с крейтом
                        LTR_Close(&crate);
                    }
                }
            }
        }
    }

    cbbModulesList->ItemIndex = 0;
    updateControls();    
}

void TMainForm::closeDevice() {
    // остановка сбора и ожидание завершения потока
    if (threadRunning) {
        thread->stop=true;
        thread->WaitFor();
    }
    LTR210_Close(&hltr210);
}

//функция, вызываемая по завершению потока сбора данных
//разрешает старт нового, устанавливает threadRunning
void __fastcall TMainForm::OnThreadTerminate(TObject *obj) {
    if (thread->err != LTR_OK) {
        MessageDlg("Сбор данных завершен с ошибкой: " + String(LTR210_GetErrorString(thread->err)),
                    mtError, TMsgDlgButtons() << mbOK,NULL);
    }

    threadRunning = false;
    updateControls();
}


void APIENTRY updateProg(void *cb_data, TLTR210 *hnd, DWORD done_size, DWORD full_size) {
    TProgressBar* bar = (TProgressBar*)cb_data;
    //обновляем ProgressBar в соответствии с процентом загрузки прошивки
    bar->Position = (int)(100.*done_size/full_size);
    //чтобы изменения отобразились в интерфейсе выполняем обработку сообщений интерфейсу
    Application->ProcessMessages();
}



__fastcall TMainForm::TMainForm(TComponent* Owner)
        : TForm(Owner) {
    refreshDeviceList();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormCreate(TObject *Sender) {
    thread = 0;
    LTR210_Init(&hltr210);

    refreshDeviceList();
}
//---------------------------------------------------------------------------



void __fastcall TMainForm::btnRefreshDevListClick(TObject *Sender) {
   refreshDeviceList();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::btnOpenClick(TObject *Sender) {
    // если соединение с модулем закрыто - то открываем новое
    if (LTR210_IsOpened(&hltr210)!=LTR_OK) {
        INT err;
        // информацию о крейте и слоте берем из сохраненного списка по индексу
        // текущей выбранной записи
        TLTR_MODULE_LOCATION location = ltr210_list[ cbbModulesList->ItemIndex ];
        LTR210_Init(&hltr210);
        err=LTR210_Open(&hltr210, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT, location.csn.c_str(), location.slot);
        if (err!=LTR_OK) {
            MessageDlg("Не удалось установить связь с модулем: " + String(LTR210_GetErrorString(err)),
                    mtError, TMsgDlgButtons() << mbOK,NULL);
        } else {
            if (LTR210_FPGAIsLoaded(&hltr210) != LTR_OK) {
                load_progr = true;
                updateControls();
                /* Загружаем прошивку, встроенную в библиотеку. Для отображения прогресса
                    обновления используем статическую функцию, в которую передаем указатель
                    на ProgressBar, который нужно обновить */
                err=LTR210_LoadFPGA(&hltr210, NULL, updateProg, pbFpgaLoad);

                load_progr=false;
                updateControls();
                if (err!=LTR_OK) {
                    MessageDlg("Не удалось загрузить прошивку ПЛИС: " + String(LTR210_GetErrorString(err)),
                                mtError, TMsgDlgButtons() << mbOK,NULL);
                }
            } else {
                //если ПЛИС загружен сразу устанавливаем индикатор на 100%
                pbFpgaLoad->Position = 100;
            }
        }

        if (err==LTR_OK) {
            edtDevSerial->Text = String(hltr210.ModuleInfo.Serial);
            edtVerPld->Text = IntToStr(hltr210.ModuleInfo.VerPLD);
            edtVerFPGA->Text = IntToStr(hltr210.ModuleInfo.VerFPGA);
        } else {
            LTR210_Close(&hltr210);
        }
    } else {
        closeDevice();
    }

    updateControls();
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::btnStartClick(TObject *Sender) {
    /* Сохраняем значения из элементов управления в соответствующие
        поля описателя модуля. Для простоты здесь не делается доп. проверок, что
        введены верные значения... */

    hltr210.Cfg.Ch[0].Enabled = chkChEn1->Checked;
    hltr210.Cfg.Ch[1].Enabled = chkChEn2->Checked;

    hltr210.Cfg.Ch[0].Range = cbbRange1->ItemIndex;
    hltr210.Cfg.Ch[1].Range = cbbRange2->ItemIndex;

    hltr210.Cfg.Ch[0].Mode = cbbMode1->ItemIndex;
    hltr210.Cfg.Ch[1].Mode = cbbMode2->ItemIndex;

    hltr210.Cfg.Ch[0].SyncLevelL = StrToFloat(edtSyncLevelL1->Text);
    hltr210.Cfg.Ch[1].SyncLevelL = StrToFloat(edtSyncLevelL2->Text);
    hltr210.Cfg.Ch[0].SyncLevelH = StrToFloat(edtSyncLevelH1->Text);
    hltr210.Cfg.Ch[1].SyncLevelH = StrToFloat(edtSyncLevelH2->Text);

    hltr210.Cfg.Ch[0].DigBitMode = cbbDigBit1->ItemIndex;
    hltr210.Cfg.Ch[1].DigBitMode = cbbDigBit2->ItemIndex;

    hltr210.Cfg.SyncMode  = cbbSyncMode->ItemIndex;
    hltr210.Cfg.GroupMode = cbbGroupMode->ItemIndex;
    hltr210.Cfg.FrameSize = seFrameSize->Value;
    hltr210.Cfg.HistSize  = seHistSize->Value;



    hltr210.Cfg.Flags = 0;
    if (chkKeepaliveEn->Checked)
        hltr210.Cfg.Flags |= LTR210_CFG_FLAGS_KEEPALIVE_EN;
    if (chkWriteAutoSusp->Checked)
        hltr210.Cfg.Flags |= LTR210_CFG_FLAGS_WRITE_AUTO_SUSP;

    INT err = LTR210_FillAdcFreq(  &hltr210.Cfg, StrToFloat(edtAdcFreq->Text), 0, NULL);
    if (err==LTR_OK)
        err = LTR210_FillFrameFreq(  &hltr210.Cfg, StrToFloat(edtFrameFreq->Text), NULL);
    if (err==LTR_OK)
        err = LTR210_SetADC(&hltr210);
    if (err!=LTR_OK) {
        MessageDlg("Не удалось установить настройки: " + String(LTR210_GetErrorString(err)),
                   mtError, TMsgDlgButtons() << mbOK,NULL);
    }

    /* По завершению записи настроек выполняем измерение собственного нуля для его коррекции */
    if (err==LTR_OK){
        err = LTR210_MeasAdcZeroOffset(&hltr210, 0);
        if (err!=LTR_OK) {
            MessageDlg("Не удалось выполнить измерение собственного нуля: " + String(LTR210_GetErrorString(err)),
                   mtError, TMsgDlgButtons() << mbOK,NULL);
        }
    }

    if (err==LTR_OK) {
        /*  Обновляем значения частот на реально установленные */
        edtAdcFreq->Text = FloatToStrF(hltr210.State.AdcFreq, ffFixed, 8, 2);
        if (hltr210.Cfg.SyncMode == LTR210_SYNC_MODE_PERIODIC)
            edtFrameFreq->Text = FloatToStrF(hltr210.State.FrameFreq, ffFixed, 8, 2);

        if (thread) {
            delete thread;
            thread = 0;
        }

        thread = new LTR210_ProcessThread(true);
        /* Так как структура должна быть одна и та же, что используемая потоком,
            что в классе основного окна, то вынуждены передать ее как указатель */
        thread->phltr210 = &hltr210;
        /* Сохраняем элементы интерфейса, которые должны изменяться обрабатывающим
            потоком в класс потока */
        thread->edtValidFrameCntr = edtValidFrameCntr;
        thread->edtInvalidFrameCntr = edtInvalidFrameCntr;
        thread->edtSyncSkipCntr = edtSyncSkipCntr;
        thread->edtOverlapCntr = edtOverlapCntr;
        thread->edtInvalidHistCntr = edtInvalidHistCntr;
        thread->edtChAvg[0]=edtCh1Avg;
        thread->edtChAvg[1]=edtCh2Avg;

         /* устанавливаем функцию на событие завершения потока (в частности,
            чтобы отследить, если поток завершился сам из-за ошибки при сборе
            данных) */
        thread->OnTerminate = OnThreadTerminate;
        thread->Resume();
        threadRunning = true;

        updateControls();
    }
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::FormClose(TObject *Sender, TCloseAction &Action) {
    closeDevice();
    delete thread;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::btnStopClick(TObject *Sender) {
    // устанавливаем запрос на завершение потока
    if (threadRunning)
        thread->stop=true;
    btnStop->Enabled = false;
}
//---------------------------------------------------------------------------

void __fastcall TMainForm::btnFrameStartClick(TObject *Sender) {
    INT err = LTR210_FrameStart(&hltr210);
    if (err!=LTR_OK) {
        MessageDlg("Не удалось выполнить программный запуск кадра: " + String(LTR210_GetErrorString(err)),
                    mtError, TMsgDlgButtons() << mbOK,NULL);
    }
}
//---------------------------------------------------------------------------

