//#DOC Main, program entry and main loop
#include "main.h"
#include <otSerial.h>
#include <otTimer.h>
#include <otRTC.h>
#include <otFPGA.h>
#include <otSpi.h>
#include <otI2cBus.h>
#include <otPrintf.h>
#include <otMalloc.h>
#include <otGraphics.h>
#include <otTTY.h>
#include <otFILE.h>
#include <otRotaryEncoder.h>
#include <otString.h>
#include <otPlot.h>
#include <otMath.h>
#include <otWYSIWYG.h>
#include <otMAX7219.h>
#include <otBME280.h>

#define MAX_VALUE   100

#define C_OBJECTS   32

#define II_YEAR      _objIndex[0]
#define II_MONTH     _objIndex[1]
#define II_DAY       _objIndex[2]
#define II_HOUR      _objIndex[3]
#define II_MINUTE    _objIndex[4]
#define II_SECOND    _objIndex[5]
#define II_APPLY     _objIndex[6]
#define II_RESET     _objIndex[7]
#define II_TIMER     _objIndex[8]
#define II_CPS       _objIndex[9]
#define II_CPM       _objIndex[10]
#define II_USV       _objIndex[11]
#define II_CPS_MIN   _objIndex[12]
#define II_CPS_MAX   _objIndex[13]
#define II_EEVENT   _objIndex[14]
#define II_EHOUR     _objIndex[15]
#define II_EMINUTE   _objIndex[16]
#define II_ESECOND   _objIndex[17]
#define II_EV_TEMP   _objIndex[18]
#define II_EV_PRES   _objIndex[19]
#define II_EV_UMID   _objIndex[20]

// For time measure
#define TimeMark(n) _timeMark[n] = TimerGetCounts(TIMER0)
#define TimeMeasure(n) ElapsedTimeUs(_timeMark[n], TimerGetCounts(TIMER0))

typedef struct {
    U16     cps;
    U16     cpm;
    F32     evTemp;
    U16     evPres;
    U8      evUmid;
}dataLog;

// Time related switch
enum PROCESS_ID {
    TL_SCREEN,      // CPs
};

SDRAM_DATA otGraphics       tft;
SDRAM_DATA otTTY            tty;

SDRAM_DATA otFILE           file;
SDRAM_DATA otRotaryEncoder  enc;
SDRAM_DATA otPlot           plot;
SDRAM_DATA otWYSIWYG        mainPanel;
SDRAM_DATA otMAX7219        tabPanel;
SDRAM_DATA otBME280         env;

U32     _screenSaver =  600;
U32     _counts = 0;
// Environment variables
F32     _eTemperature;
S32     _ePressure, _eUmidity;
// Object index
U16     _objIndex[C_OBJECTS];
S32     _year, _month, _day, _cpm = 0;
S8      _hour, _minute, _second;
S8      _eHour = 0, _eMinute = 0, _eSecond = 0;     // Exceptional events time
S32     _eYear, _eMonth, _eDay;                     // Exceptional events date
U32     _timeMark[16], _pCnt = 0, _cnt, _cpmT = 0;
U32     _cps, _min = 100, _max = 0, _exEvents = 0;
U32     _cpsHist[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, _cpsHistPtr = 0;
F32     _usv;
dataLog * _data;
U32     _iData = 0;
char    * _buff;
bool    _display = false;

SDRAM_DATA const char * _screen =
" DATE-TIME  : YYYY/MM/DD  hh:mm:ss                                      CHANGE  "
"                                                                                "
" MEASURES   : TIMER s     CPSS cp/s    CPMMMM cp/m   USV.VVV uSv/h       RESET  "
"                                                                                "
" STATISTICS :  MINN cp/s  MAXX cp/s    EXEVNT       eh:em:es                    "
"                                                                                "
" ENVIRONMENT: TEM.P C    PRESS mBar      UMID %                                 "
"                                                                                "
"                                                                                "
"                                                                                "
" Vert. 10 cp/s Div. - Hor. 20s Div.                                             "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                "
"                                                                                ";

FAST_CODE_2 void _coreTimerCallBack()
{
    U32     cnt;
   
    if(_screenSaver)
    {
        _screenSaver--;
        if(_screenSaver == 0)
        {
            _screenSaver =  600;
            tft.backlight(false);
        }
    }
   
    FpgaEventGate(false);
    cnt = FpgaEventRead();
    FpgaEventGate(true);
   
    _counts++;
    if(cnt != -1 && cnt >= _pCnt)
    {
        _cnt = cnt;
        _cps = _cnt - _pCnt;
        _pCnt = _cnt;
    }
    else
    {
        FpgaEventReset();
        _cnt = 0;
        _pCnt = _cnt;
        _cps = 0;
    }
    // x averaged cps
    _cpsHist[_cpsHistPtr++] = _cps;
    _cpsHistPtr %= 16;
   
    _cpmT += _cps;
   
    // Evaluate cpm
    if((_counts % 60) == 0)
    {
        _cpm = _cpmT;
        _cpmT = 0;
    }

    _display = true;
}

void tftOn()
{
    _screenSaver =  600;
    tft.backlight(true);
}

bool TimeCheckUs(U8 n, U32 val)
{
    bool    ret = false;
    U32     et = ElapsedTimeUs(_timeMark[n], TimerGetCounts(TIMER0));
    if(et >= val) ret = true;
    return ret;
}

bool TimeCheck(U8 n, U32 val)
{
    bool    ret = false;
    U32     et = ElapsedTimeUs(_timeMark[n], TimerGetCounts(TIMER0)) / 1000;
    if(et >= val) ret = true;
    return ret;
}

void logo(const char * fname, U16 delay = 3000, U16 color = RGB565_BLACK)
{
    //printf("Start loading ...\n");
    FILE fd = file.fopen(fname, otFILE::A_READ);
    if(fd)
    {
        U16     ** mem;
        U16     xw, yw, xo, yo, xs, ys;
        U32     nx, rlen, sz, n, i;
        U8      ec, co;
        
        tft.size(xs, ys);
        
        file.fread(fd, &xw, 2, rlen);
        file.fread(fd, &yw, 2, rlen);
        file.fread(fd, &nx, 4, rlen);
        file.fread(fd, &ec, 1, rlen);
        file.fread(fd, &co, 1, rlen);
        
        sz = xw * yw * 2;
        
        printf("%20s (%03dx%03d) Size = %6u, Frames = %4u, Used memory: %5u KByte\n", fname, xw, yw, sz, nx, nx * sz / 1024);
        
        mem = (U16 **) _malloc(sizeof(U16 *) * nx);
        
        // Allocate frame buffer
        for(i = 0; i < nx; i++)
            mem[i] = (U16 *) _malloc(sz);
        
        // Read all frames
        for(int i = 0; i < nx; i++)
            file.fread(fd, mem[i], sz, rlen);
        file.fclose(fd);
        
        tft.clear(color);
        
        // Center the image
        xo = (xs - xw) / 2;
        yo = (ys - yw) / 2;
        
        // Display all frames
        for(int n = 0; n < nx; n++)
            tft.draw565Bitmap(xo, yo, mem[n], xw, yw);
        
        DelayMs(delay);
        
        // Free all allocated memory
        for(i = 0; i < nx; i++)
            _free(mem[i]);
        _free(mem);
    }
    else
        tty.print("Can't load %s file\n", fname);
    //printf("End loading ...\n");
}

void createStringDateTime(char * date, char * time)
{
    sprintf(date, "%04d%02d%02d", _year, _month, _day);
    if(time)
        sprintf(time, "%02d%02d%02d", _hour, _minute, _second);
}

void createPath(const char * mainPath, char * out)
{
    createStringDateTime(&out[32], &out[64]);
    file.mkdir(mainPath);
    sprintf(out, "%s/%s", mainPath, &out[32]);
    file.mkdir(out);
    sprintf(out, "%s/%s/%s", mainPath, &out[32], &out[64]);
    //file.mkdir(out);
}

void createPathDate(const char * mainPath, const char * fp, char * out)
{
    createStringDateTime(&out[32], &out[64]);
    createPath(mainPath, out);
    // Complete file path
    _strcat(out, "/");      // Add dir separator
    _strcat(out, fp);       // Add file name prefix
}

void createFilePath(const char * path, const char * extension, char * output)
{
    // Complete file path
    _strcpy(output, path);
    //_strcat(output, "/");         // Add dir separator
    _strcat(output, extension);     // Add extension file name
}

void SaveData()
{
    char    * fileName, * filePath;
    fileName = (char *) _malloc(1024);
    filePath = (char *) _malloc(1024);
   
    createPath("data", filePath);
    createFilePath(filePath, ".txt", fileName);
   
    FILE fd = file.fopen(fileName, otFILE::A_CREATE_ALWAYS | otFILE::A_WRITE);

    for(U32 i = 0; i < 3600; i++)
        file.fprintf(fd, "%u,%u,%u,%.1lf,%u,%u\n", i, _data[i].cps, _data[i].cpm, _data[i].evTemp, _data[i].evPres, _data[i].evUmid);

    file.fclose(fd);
    
    _free(filePath);
    _free(fileName);
}

void saveExceptionalEvents()
{
    FILE fd = file.fopen("exEvents.txt", otFILE::A_OPEN_APPEND | otFILE::A_WRITE);
    file.fprintf(fd, "%4d/%02d/%02d, %02d:%02d:%02d, %u\n", _eYear, _eMonth, _eDay, _eHour, _eMinute, _eSecond, _cps);
    file.fclose(fd);
}

void setup()
{
    RtcOn();
    UartEnableSignals(UART0);
    UartSetBaud(UART0, 115200);
    setStdout(4096, UART0); // printf output is now directed on UART0
    
    TimerSetAsCounter(TIMER0);
    TimerEnable(TIMER0, true);
    
    FpgaInit();
    FpgaHiddenPort(HP_EXT_I2C, true);
    FpgaPortPxSet(PD_INPUT, PD_INPUT, PD_INPUT, PD_OUTPUT, PD_OUTPUT, PD_OUTPUT, PD_INPUT, PD_INPUT);
    FpgaEventFunction(EI_PX_7, EVF_COUNTER, 0); // PX[7] Read event pulses
    FpgaEventGate(true);
    FpgaEventReset();
    
    I2cBusReset();
    I2cBusFrequency(400, 30);
    
    tft.init(otGraphics::ILI9488);  // Use ILI9341 or ILI9486 or ILI9488
    tft.setRotation(otGraphics::IR_270);
    tft.clear();
    tft.setTextColor(RGB565_YELLOW, RGB565_BLACK);
    
    tty.init(1, &tft);
    tty.print("SYSTEM INIT, PLEASE WAIT ...\n");
    tty.print("*** NUCLEAR RAIN ***\n");
    tty.print("Code revision: %s\n", __RevisionAlpha__);
   
    tabPanel.init(SA_MAX7219_B);
    tabPanel.displayText("-HELLO-");
    SpiSet(SA_LCD);
    
    if(file.init())
        file.setStdout(printf);
    else
    {
        tty.print("SD not found! System halted.\n");
        IDLE;
    }
   
    if(!env.init())
    {
        tty.print("Can't find BME280\n");
        IDLE;
    }
    
    // Set PX0-1 for encoder phases and PX2 for the button
    enc.init(0, 2);
   
    // Temporary buffer
    _buff = (char *) _malloc(1024);
    // Data buffer
    _data = (dataLog *) _malloc(sizeof(dataLog) * 3600);
   
    // Prescaler action 400MHz / 40 (39+1) = 10MHz (100ns)
    // Period: 10000000 * 100ns = 1.0s
    CoreTimerSetInterruptService(39, 10000000, true, _coreTimerCallBack);
    CoreTimerEnable(true);
    
    // Logo
    logo("sys/logo.emb");
    tft.clear();
    
    // Plot area
    plot.init(0, 90, 480, 200, &tft);
    plot.setRange(0, MAX_VALUE);
    // GUI init
    mainPanel.init(C_OBJECTS, _screen, 80, 0, 0, &tft, 1);
    II_YEAR   = mainPanel.addInteger("YYYY",    &_year,   otWYSIWYG::OBJ_VAR_S32);
    II_MONTH  = mainPanel.addInteger("MM",      &_month,  otWYSIWYG::OBJ_VAR_S32);
    II_DAY    = mainPanel.addInteger("DD",      &_day,    otWYSIWYG::OBJ_VAR_S32);
    II_HOUR   = mainPanel.addInteger("hh",      &_hour,   otWYSIWYG::OBJ_VAR_S8);
    II_MINUTE = mainPanel.addInteger("mm",      &_minute, otWYSIWYG::OBJ_VAR_S8);
    II_SECOND = mainPanel.addInteger("ss",      &_second, otWYSIWYG::OBJ_VAR_S8);
   
    II_APPLY  = mainPanel.addButton("CHANGE", 0);
    II_RESET  = mainPanel.addButton("RESET", 0);
   
    II_TIMER  = mainPanel.addInteger("TIMER",  &_counts, otWYSIWYG::OBJ_VAR_S32);
    II_CPS    = mainPanel.addInteger("CPSS",   &_cps, otWYSIWYG::OBJ_VAR_S32);
    II_CPM    = mainPanel.addInteger("CPMMMM", &_cpm, otWYSIWYG::OBJ_VAR_S32);
    II_USV    = mainPanel.addReal("USV.VVV",   &_usv);
    II_CPS_MIN= mainPanel.addInteger("MINN",   &_min, otWYSIWYG::OBJ_VAR_S32);
    II_CPS_MAX= mainPanel.addInteger("MAXX",   &_max, otWYSIWYG::OBJ_VAR_S32);
    II_EEVENT = mainPanel.addInteger("EXEVNT", &_exEvents, otWYSIWYG::OBJ_VAR_S32);
    II_EHOUR  = mainPanel.addInteger("eh",     &_eHour,   otWYSIWYG::OBJ_VAR_S8);
    II_EMINUTE= mainPanel.addInteger("em",     &_eMinute, otWYSIWYG::OBJ_VAR_S8);
    II_ESECOND= mainPanel.addInteger("es",     &_eSecond, otWYSIWYG::OBJ_VAR_S8);
    II_EV_TEMP= mainPanel.addReal("TEM.P",     &_eTemperature);
    II_EV_PRES= mainPanel.addInteger("PRESS", &_ePressure, otWYSIWYG::OBJ_VAR_S32);
    II_EV_UMID= mainPanel.addInteger("UMID",  &_eUmidity, otWYSIWYG::OBJ_VAR_S32);
   
    mainPanel.regen();
}


void loop()
{
    int     sel = 7, psel = 7, a, i;
    bool    action = false, edit = false;
   
    mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_SELECTED);
   
    while(1)
    {
        if(!edit)
        {
            F32     cpsm = 0;
            for(i = 0; i < 16; i++)
                cpsm += _cpsHist[i];
            cpsm /= 16.0f;
            sprintf(_buff, "[%06u] : %6u cp/s : %6.1f cp/s", _cnt, _cps, cpsm);
            _usv = (F32) _cpm * 0.0057;
            if(_cps < _min) _min = _cps;
            if(_cps > _max) _max = _cps;
            tft.setTextSize(2);
            tft.writeString(300, _buff);
            tft.setTextSize(1);
            // Tab panel display
            SpiSet(SA_MAX7219_B);
            sprintf(_buff, "%3u %4.1f", _cps, cpsm);
            tabPanel.displayText(_buff);
            SpiSet(SA_LCD);
        }
       
        if(enc.button(false))
        {
           
            switch(sel)
            {
                case 0: case 1: case 2: case 3:
                case 4: case 5: case 6:
                case 7:
                    if(mainPanel.state(sel) == otWYSIWYG::OBJ_STAT_SELECTED)
                        mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_ACTIVATED);
                    else
                        mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_SELECTED);
                break;
            }
            enc.release();
            edit = true;
        }
        a = enc.read();
        if(a) tftOn();
        // Change parameters if requested
        if(mainPanel.state(sel) == otWYSIWYG::OBJ_STAT_SELECTED)
        {
            // Change the selected object
            if(a > 0)
            {
                sel++;
                if(sel > 7) sel = 0;
                action = true;
            }
            if(a < 0)
            {
                sel--;
                if(sel < 0) sel = 7;
                action = true;
            }
            if(action)
            {
                mainPanel.setState(psel, otWYSIWYG::OBJ_STAT_NORMAL);
                action = false;
                mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_SELECTED);
                psel = sel;
            }
        }
        else
        {
            if(a || sel == 6 || // Apply
                    sel == 7)   // Reset
                // Action for selected object
                switch(sel)
                {
                    case 0: // Year
                            _year += a;
                            mainPanel.regen(II_YEAR);
                            break;
                    case 1: // Month
                            _month += a;
                            if(_month < 1) _month = 12;
                            if(_month > 12) _month = 1;
                            mainPanel.regen(II_MONTH);
                            break;
                    case 2: // Day
                            _day += a;
                            if(_day < 1) _day = 31;
                            if(_day > 31) _day = 1;
                            mainPanel.regen(II_DAY);
                            break;
                    case 3: // Hour
                            _hour += a;
                            if(_hour < 0) _hour = 23;
                            if(_hour > 23) _hour = 0;
                            mainPanel.regen(II_HOUR);
                            break;
                    case 4: // Minute
                            _minute += a;
                            if(_minute < 0) _minute = 59;
                            if(_minute > 59) _minute = 0;
                            mainPanel.regen(II_MINUTE);
                            break;
                    case 5: // Seconds
                            _second += a;
                            if(_second < 0) _second = 59;
                            if(_second > 59) _second = 0;
                            mainPanel.regen(II_SECOND);
                            break;
                    case 6: // Apply
                            DelayMs(200);
                            mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_SELECTED);
                            RtcSetDate(_year, _month, _day);
                            RtcSetTime(_hour, _minute, _second);
                            edit = false;
                            break;
                    case 7:
                            _counts = 0;
                            _pCnt = 0;
                            _min = 100;
                            _max = 0;
                            _exEvents = 0;
                            _iData = 0;
                            FpgaEventReset();
                            edit = false;
                            mainPanel.setState(sel, otWYSIWYG::OBJ_STAT_SELECTED);
                            DelayMs(1000);
                            plot.clear();
                            break;
                }
        }
       
        if(_display)
        {
            _display = false;
            if(!edit)
            {
                plot.addData(_cps);
                plot.plot();
           
                RtcGetTime((U8 *) &_hour, (U8 *) &_minute, (U8 *) &_second);
                RtcGetDate(&_year, &_month, &_day);
           
                // Show current values
                mainPanel.regen(II_YEAR);
                mainPanel.regen(II_MONTH);
                mainPanel.regen(II_DAY);
                mainPanel.regen(II_HOUR);
                mainPanel.regen(II_MINUTE);
                mainPanel.regen(II_SECOND);
                mainPanel.regen(II_TIMER);
            }
           
            if(_cps > MAX_VALUE)
            {
                _exEvents++;
                _eHour = _hour;
                _eMinute = _minute;
                _eSecond = _second;
                _eYear = _year;
                _eMonth = _month;
                _eDay = _day;
                saveExceptionalEvents();
            }
           
            if(env.read())
            {
                _eTemperature = env.temperature();
                _ePressure = env.pressure();
                _eUmidity = env.humidity();
            }
           
            _data[_iData].cps = _cps;
            _data[_iData].cpm = _cpm;
            _data[_iData].evTemp = _eTemperature;
            _data[_iData].evPres = _ePressure;
            _data[_iData].evUmid = _eUmidity;
            _iData++;
            if(_iData == 3600)
            {
                _iData = 0;
                SaveData();
            }
            mainPanel.regen(II_CPS);
            mainPanel.regen(II_CPM);
            mainPanel.regen(II_USV);
            mainPanel.regen(II_CPS_MIN);
            mainPanel.regen(II_CPS_MAX);
            mainPanel.regen(II_EEVENT);
            mainPanel.regen(II_EHOUR);
            mainPanel.regen(II_EMINUTE);
            mainPanel.regen(II_ESECOND);
            mainPanel.regen(II_EV_TEMP);
            mainPanel.regen(II_EV_PRES);
            mainPanel.regen(II_EV_UMID);
        }
    }
}