//we only compile for some boards
#ifdef BOARD_NODEMCU32S_OBP60
#include "GwOBP60Task.h"
#include "GwApi.h"
#include "OBP60Hardware.h"              // PIN definitions
#include <Ticker.h>                     // Timer Lib for timer interrupts
#include <Wire.h>                       // I2C connections
#include <MCP23017.h>                   // MCP23017 extension Port
#include <NMEA0183.h>
#include <NMEA0183Msg.h>
#include <NMEA0183Messages.h>
#include <GxEPD.h>                      // GxEPD lib for E-Ink displays
#include <GxGDEW042T2/GxGDEW042T2.h>    // 4.2" Waveshare S/W 300 x 400 pixel
#include <GxIO/GxIO_SPI/GxIO_SPI.h>     // GxEPD lip for SPI display communikation
#include <GxIO/GxIO.h>                  // GxEPD lip for SPI
#include "OBP60ExtensionPort.h"         // Functions lib for extension board
#include "OBP60Keypad.h"                // Functions lib for keypad

// True type character sets
#include "Ubuntu_Bold8pt7b.h"
#include "Ubuntu_Bold20pt7b.h"
#include "Ubuntu_Bold32pt7b.h"
#include "DSEG7Classic-BoldItalic16pt7b.h"
#include "DSEG7Classic-BoldItalic42pt7b.h"
#include "DSEG7Classic-BoldItalic60pt7b.h"

// Pictures
//#include GxEPD_BitmapExamples         // Example picture
#include "MFD_OBP60_400x300_sw.h"       // MFD with logo
#include "Logo_OBP_400x300_sw.h"        // OBP Logo

#include "OBP60QRWiFi.h"                // Functions lib for WiFi QR code
#include "OBP60Data.h"                  // Data stucture
#include "Page_0.h"                     // Page 0 Depth
#include "Page_1.h"                     // Page 1 Speed
#include "Page_2.h"                     // Page 2 VBat
#include "Page_3.h"                     // Page 3 Depht / Speed
#include "OBP60Pages.h"                 // Functions lib for show pages
// new comment Adrien

tNMEA0183Msg NMEA0183Msg;
tNMEA0183 NMEA0183;

// Timer Interrupts for hardware functions
Ticker Timer1;  // Under voltage detection
Ticker Timer2;  // Keypad
Ticker Timer3;  // Binking flash LED

// Global vars
bool initComplete = false;      // Initialization complete
int taskRunCounter = 0;         // Task couter for loop section
bool gps_ready = false;         // GPS initialized and ready to use

#define INVALID_COORD -99999
class GetBoatDataRequest: public GwMessage{
    private:
        GwApi *api;
    public:
        double latitude;
        double longitude;
        GetBoatDataRequest(GwApi *api):GwMessage(F("boat data")){
            this->api=api;
        }
        virtual ~GetBoatDataRequest(){}
    protected:
        /**
         * this methos will be executed within the main thread
         * be sure not to make any time consuming or blocking operation
         */    
        virtual void processImpl(){
            //api->getLogger()->logDebug(GwLog::DEBUG,"boatData request from example task");
            /*access the values from boatData (see GwBoatData.h)
              by using getDataWithDefault it will return the given default value
              if there is no valid data available
              so be sure to use a value that never will be a valid one
              alternatively you can check using the isValid() method at each boatData item
              */
            latitude=api->getBoatData()->Latitude->getDataWithDefault(INVALID_COORD);
            longitude=api->getBoatData()->Longitude->getDataWithDefault(INVALID_COORD);
        };
};

String formatValue(GwApi::BoatValue *value){
    if (! value->valid) return "----";
    static const int bsize=30;
    char buffer[bsize+1];
    buffer[0]=0;
    if (value->getFormat() == "formatDate"){
        time_t tv=tNMEA0183Msg::daysToTime_t(value->value);
        tmElements_t parts;
        tNMEA0183Msg::breakTime(tv,parts);
        snprintf(buffer,bsize,"%02d.%02d.%04d",parts.tm_mday,parts.tm_mon+1,parts.tm_year+1900);
    }
    else if(value->getFormat() == "formatTime"){
        double inthr;
        double intmin;
        double intsec;
        double val;
        val=modf(value->value/3600.0,&inthr);
        val=modf(val*3600.0/60.0,&intmin);
        modf(val*60.0,&intsec);
//        snprintf(buffer,bsize,"%02.0f:%02.0f:%02.0f",inthr,intmin,intsec);
        snprintf(buffer,bsize,"%02.0f:%02.0f",inthr,intmin);
    }
    else if (value->getFormat() == "formatFixed0"){
        snprintf(buffer,bsize,"%.0f",value->value);
    }
    else{
        snprintf(buffer,bsize,"%.4f",value->value);
    }
    buffer[bsize]=0;
    return String(buffer);
}

// Hardware initialisation before start all services
//##################################################
void OBP60Init(GwApi *api){
    GwLog *logger=api->getLogger();

    // Define timer interrupts
//    Timer1.attach_ms(1, underVoltageDetection);     // Maximum speed with 1ms
    Timer2.attach_ms(40, readKeypad);               // Timer value nust grater than 30ms
    Timer3.attach_ms(500, blinkingFlashLED); 

    // Extension port MCP23017
    // Check I2C devices MCP23017
    Wire.begin(OBP_I2C_SDA, OBP_I2C_SCL);
    Wire.beginTransmission(MCP23017_I2C_ADDR);
    if (Wire.endTransmission() != 0) {
        LOG_DEBUG(GwLog::ERROR,"MCP23017 not found, check wiring");
        initComplete = false;
    }
    else{ 
        // Start communication
        mcp.init();
        mcp.portMode(MCP23017Port::A, 0b00110000);  //Port A, 0 = out, 1 = in
        mcp.portMode(MCP23017Port::B, 0b11110000);  //Port B, 0 = out, 1 = in

        // Extension Port A set defaults
        setPortPin(OBP_DIGITAL_OUT1, false);    // PA0
        setPortPin(OBP_DIGITAL_OUT2, false);    // PA1
        setPortPin(OBP_FLASH_LED, false);       // PA2
        setPortPin(OBP_BACKLIGHT_LED, false);   // PA3
        setPortPin(OBP_POWER_50, true);         // PA6
        setPortPin(OBP_POWER_33, true);         // PA7

        // Extension Port B set defaults
        setPortPin(PB0, false);     // PB0
        setPortPin(PB1, false);     // PB1
        setPortPin(PB2, false);     // PB2
        setPortPin(PB3, false);     // PB3

        // Settings for 1Wire
        bool enable1Wire = api->getConfig()->getConfigItem(api->getConfig()->use1Wire,true)->asBoolean();
        if(enable1Wire == true){
            LOG_DEBUG(GwLog::DEBUG,"1Wire Mode is On");
        }
        else{
            LOG_DEBUG(GwLog::DEBUG,"1Wire Mode is Off");
        }
        
        // Settings for backlight
        String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
        LOG_DEBUG(GwLog::DEBUG,"Backlight Mode is: %s", backlightMode);
        if(String(backlightMode) == "On"){
            setPortPin(OBP_BACKLIGHT_LED, true);
        }
        if(String(backlightMode) == "Off"){
            setPortPin(OBP_BACKLIGHT_LED, false);
        }
        if(String(backlightMode) == "Control by Key"){
            setPortPin(OBP_BACKLIGHT_LED, false);
        }

        // Settings flash LED mode
        String ledMode = api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString();
        LOG_DEBUG(GwLog::DEBUG,"Backlight Mode is: %s", ledMode);
        if(String(ledMode) == "Off"){
            blinkingLED = false;
        }
        if(String(ledMode) == "Limits Overrun"){
            blinkingLED = true;
        }

        // Start serial stream and take over GPS data stream form internal GPS
        bool gpsOn=api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asBoolean();
        if(gpsOn == true){
            Serial2.begin(9600, SERIAL_8N1, OBP_GPS_TX, -1); // GPS RX unused in hardware (-1)
            if (!Serial2) {     
                LOG_DEBUG(GwLog::ERROR,"GPS modul NEO-6M not found, check wiring");
                gps_ready = false;
                }
            else{
                LOG_DEBUG(GwLog::DEBUG,"GPS modul NEO-M6 found");
                NMEA0183.SetMessageStream(&Serial2);
                NMEA0183.Open();
                gps_ready = true;
            }
        }

        // Marker for init complete
        // Used in OBP60Task()
        initComplete = true;

        // Buzzer tone for initialization finish
        buzPower = uint(api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt());
        buzzer(TONE4, buzPower, 500);

    }
}

// OBP60 Task
//#######################################
void OBP60Task(void *param){

    GwApi *api=(GwApi*)param;
    GwLog *logger=api->getLogger();
    GwApi::Status status;

    bool hasPosition = false;
    
    // Get configuration data from webside
    // OBP60 Settings
    bool exampleSwitch = api->getConfig()->getConfigItem(api->getConfig()->obp60Config,true)->asBoolean();
    LOG_DEBUG(GwLog::DEBUG,"example switch ist %s",exampleSwitch?"true":"false");
    api->getConfig()->getConfigItem(api->getConfig()->dateFormat,true)->asString().toCharArray(busInfo.dateformat, 3);
    busInfo.timezone = api->getConfig()->getConfigItem(api->getConfig()->timeZone,true)->asInt();
    busInfo.draft = api->getConfig()->getConfigItem(api->getConfig()->draft,true)->asString().toFloat();
    busInfo.fueltank = api->getConfig()->getConfigItem(api->getConfig()->fuelTank,true)->asString().toFloat();
    busInfo.fuelconsumption = api->getConfig()->getConfigItem(api->getConfig()->fuelConsumption,true)->asString().toFloat();
    busInfo.watertank = api->getConfig()->getConfigItem(api->getConfig()->waterTank,true)->asString().toFloat();
    busInfo.wastetank = api->getConfig()->getConfigItem(api->getConfig()->wasteTank,true)->asString().toFloat();
    busInfo.batvoltage = api->getConfig()->getConfigItem(api->getConfig()->batteryVoltage,true)->asString().toFloat();
    api->getConfig()->getConfigItem(api->getConfig()->batteryType,true)->asString().toCharArray(busInfo.battype, 16);
    busInfo.batcapacity = api->getConfig()->getConfigItem(api->getConfig()->batteryCapacity,true)->asString().toFloat();
    // OBP60 Hardware
    busInfo.gps = api->getConfig()->getConfigItem(api->getConfig()->useGPS,true)->asBoolean();
    busInfo.bme280 = api->getConfig()->getConfigItem(api->getConfig()->useBME280,true)->asBoolean();
    busInfo.onewire = api->getConfig()->getConfigItem(api->getConfig()->use1Wire,true)->asBoolean();
    String powerMode = api->getConfig()->getConfigItem(api->getConfig()->powerMode,true)->asString();
    busInfo.simulation = api->getConfig()->getConfigItem(api->getConfig()->useSimuData,true)->asBoolean();
    // OBP60 Display
    String displayMode = api->getConfig()->getConfigItem(api->getConfig()->display,true)->asString();
    busInfo.statusline = api->getConfig()->getConfigItem(api->getConfig()->statusLine,true)->asBoolean();
    busInfo.refresh = api->getConfig()->getConfigItem(api->getConfig()->refresh,true)->asBoolean();
    String backlightMode = api->getConfig()->getConfigItem(api->getConfig()->backlight,true)->asString();
    api->getConfig()->getConfigItem(api->getConfig()->flashLED,true)->asString().toCharArray(busInfo.flashled, 16);
    // OBP60 Buzzer
    busInfo.buzerror = api->getConfig()->getConfigItem(api->getConfig()->buzzerError,true)->asBoolean();
    busInfo.buzgps = api->getConfig()->getConfigItem(api->getConfig()->buzzerGps,true)->asBoolean();
    busInfo.buzlimits = api->getConfig()->getConfigItem(api->getConfig()->buzzerLim,true)->asBoolean();
    api->getConfig()->getConfigItem(api->getConfig()->buzzerMode,true)->asString().toCharArray(busInfo.buzmode, 16);
    busInfo.buzpower = api->getConfig()->getConfigItem(api->getConfig()->buzzerPower,true)->asInt();
    // OBP60 Pages
    busInfo.numpages = api->getConfig()->getConfigItem(api->getConfig()->numberPages,true)->asInt();

    // Initializing all necessary boat data
    GwApi::BoatValue *cog=new GwApi::BoatValue(F("COG"));
    GwApi::BoatValue *twd=new GwApi::BoatValue(F("TWD"));
    GwApi::BoatValue *awd=new GwApi::BoatValue(F("AWD"));
    GwApi::BoatValue *sog=new GwApi::BoatValue(F("SOG"));
    GwApi::BoatValue *stw=new GwApi::BoatValue(F("STW"));
    GwApi::BoatValue *tws=new GwApi::BoatValue(F("TWS"));
    GwApi::BoatValue *aws=new GwApi::BoatValue(F("AWS"));
    GwApi::BoatValue *maxtws=new GwApi::BoatValue(F("MaxTws"));
    GwApi::BoatValue *maxaws=new GwApi::BoatValue(F("MaxAws"));
    GwApi::BoatValue *awa=new GwApi::BoatValue(F("AWA"));
    GwApi::BoatValue *heading=new GwApi::BoatValue(F("Heading"));
    GwApi::BoatValue *mheading=new GwApi::BoatValue(F("MagneticHeading"));
    GwApi::BoatValue *rot=new GwApi::BoatValue(F("ROT"));
    GwApi::BoatValue *variation=new GwApi::BoatValue(F("Variation"));

    //#################################################################

    GwApi::BoatValue *date=new GwApi::BoatValue(F("GpsDate"));
    GwApi::BoatValue *time=new GwApi::BoatValue(F("GpsTime"));
    GwApi::BoatValue *longitude=new GwApi::BoatValue(F("Longitude"));
    GwApi::BoatValue *latitude=new GwApi::BoatValue(F("Latitude"));
    GwApi::BoatValue *waterdepth=new GwApi::BoatValue(F("WaterDepth"));
    GwApi::BoatValue *hdop=new GwApi::BoatValue(F("HDOP"));
    GwApi::BoatValue *pdop=new GwApi::BoatValue(F("PDOP"));
    GwApi::BoatValue *valueList[]={cog, twd, awd, sog, stw, tws, aws, maxtws, maxaws, awa, heading, mheading, rot, variation, date, time, longitude, latitude, waterdepth, hdop, pdop};

    //Init E-Ink display
    display.init();                         // Initialize and clear display
    display.setTextColor(GxEPD_BLACK);      // Set display color
    display.setRotation(0);                 // Set display orientation (horizontal)
    display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen
    display.update();                       // Full update (slow)
        
    if(displayMode == "Logo + QR Code" || displayMode == "Logo"){
        display.drawExampleBitmap(gImage_Logo_OBP_400x300_sw, 0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw start logo
//        display.drawExampleBitmap(gImage_MFD_OBP60_400x300_sw, 0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw start logo
        display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true);   // Partial update (fast)
        delay(SHOW_TIME);                                // Logo show time
        display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen
        display.updateWindow(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, true);    // Partial update (fast)
        if(displayMode == "Logo + QR Code"){
            qrWiFi();                                        // Show QR code for WiFi connection
            delay(SHOW_TIME);                                // Logo show time
            display.fillRect(0, 0, GxEPD_WIDTH, GxEPD_HEIGHT, GxEPD_WHITE); // Draw white sreen
        }
    }

    // Task Loop
    //###############################
    while(true){
        // Task cycle time
        delay(10);  // 10ms

        // Backlight On/Off Subtask 100ms
        if((taskRunCounter % 10) == 0){
            // If key controled
            if(backlightMode == "Control by Key"){
                if(keystatus == "6s"){
                    LOG_DEBUG(GwLog::DEBUG,"Toggle Backlight LED");
                    togglePortPin(OBP_BACKLIGHT_LED);
                    keystatus = "0";
                }
            }
            // Change page number
            if(keystatus == "5s"){
                pageNumber ++;
                if(pageNumber > MAX_PAGE_NUMBER - 1){
                    pageNumber = 0;
                }
                first_view = true;
                keystatus = "0";
            }
            if(keystatus == "1s"){
                pageNumber --;
                if(pageNumber < 0){
                    pageNumber = MAX_PAGE_NUMBER - 1;
                }
                first_view = true;
                keystatus = "0";
            }
        }

        // Subtask all 3000ms
        if((taskRunCounter % 300) == 0){
 //           LOG_DEBUG(GwLog::DEBUG,"Subtask 2");
            //Clear swipe code
            if(keystatus == "left" || keystatus == "right"){
                keystatus = "0";
            }
        }

        // Send NMEA0183 GPS data on several bus systems
        if(busInfo.gps == true){   // If config enabled
                if(gps_ready = true){
                    tNMEA0183Msg NMEA0183Msg;
                    while(NMEA0183.GetMessage(NMEA0183Msg)){
                        api->sendNMEA0183Message(NMEA0183Msg);
                    }
                }
        }

        // Read the status values from gateway
        api->getStatus(status);
        busInfo.wifiApOn = status.wifiApOn;
        busInfo.wifiClientOn = status.wifiClientOn;
        busInfo.wifiClientConnected = status.wifiClientConnected;
        busInfo.wifiApIp = status.wifiApIp;
        busInfo.systemName = status.systemName;
        busInfo.wifiApPass = status.wifiApPass;
        busInfo.wifiClientIp = status.wifiClientIp;
        busInfo.wifiClientSSID = status.wifiClientSSID;
        busInfo.usbRx = status.usbRx;
        busInfo.usbTx = status.usbTx;
        busInfo.serRx = status.serRx;
        busInfo.serTx = status.serTx;
        busInfo.tcpSerRx = status.tcpSerRx;
        busInfo.tcpSerTx = status.tcpSerTx;
        busInfo.tcpClients = status.tcpClients;
        busInfo.tcpClRx = status.tcpClRx;
        busInfo.tcpClTx = status.tcpClTx;
        busInfo.tcpClientConnected = status.tcpClientConnected;
        busInfo.n2kRx = status.n2kRx;
        busInfo.n2kTx = status.n2kTx;

        // Read the current bus data and copy to stucture
        api->getBoatDataValues(20, valueList);

        busInfo.COG.fvalue = cog->value;
        cog->getFormat().toCharArray(busInfo.COG.unit, 8, 0);
        busInfo.COG.valid = int(cog->valid);

        busInfo.TWD.fvalue = twd->value;
        twd->getFormat().toCharArray(busInfo.TWD.unit, 8, 0);
        busInfo.TWD.valid = int(twd->valid);

        busInfo.AWD.fvalue = awd->value;
        awd->getFormat().toCharArray(busInfo.AWD.unit, 8, 0);
        busInfo.AWD.valid = int(awd->valid);

        busInfo.SOG.fvalue = sog->value;
        sog->getFormat().toCharArray(busInfo.SOG.unit, 8, 0);
        busInfo.SOG.valid = int(sog->valid);

        busInfo.STW.fvalue = stw->value;
        stw->getFormat().toCharArray(busInfo.STW.unit, 8, 0);
        busInfo.STW.valid = int(stw->valid);

        busInfo.TWS.fvalue = tws->value;
        tws->getFormat().toCharArray(busInfo.TWS.unit, 8, 0);
        busInfo.TWS.valid = int(tws->valid);

        busInfo.AWS.fvalue = aws->value;
        aws->getFormat().toCharArray(busInfo.AWS.unit, 8, 0);
        busInfo.AWS.valid = int(aws->valid);

        busInfo.MaxTws.fvalue = maxtws->value;
        maxtws->getFormat().toCharArray(busInfo.MaxTws.unit, 8, 0);
        busInfo.MaxTws.valid = int(maxtws->valid);

        busInfo.MaxAws.fvalue = maxaws->value;
        maxaws->getFormat().toCharArray(busInfo.MaxAws.unit, 8, 0);
        busInfo.MaxAws.valid = int(maxaws->valid);

        busInfo.AWA.fvalue = awa->value;
        awa->getFormat().toCharArray(busInfo.AWA.unit, 8, 0);
        busInfo.AWA.valid = int(awa->valid);

        busInfo.Heading.fvalue = heading->value;
        heading->getFormat().toCharArray(busInfo.Heading.unit, 8, 0);
        busInfo.Heading.valid = int(heading->valid);

        busInfo.MagneticHeading.fvalue = mheading->value;
        mheading->getFormat().toCharArray(busInfo.MagneticHeading.unit, 8, 0);
        busInfo.MagneticHeading.valid = int(mheading->valid);

        busInfo.ROT.fvalue = rot->value;
        rot->getFormat().toCharArray(busInfo.ROT.unit, 8, 0);
        busInfo.ROT.valid = int(rot->valid);

        busInfo.Variation.fvalue = variation->value;
        variation->getFormat().toCharArray(busInfo.Variation.unit, 8, 0);
        busInfo.Variation.valid = int(variation->valid);


        //######################################################################

        busInfo.WaterDepth.fvalue = waterdepth->value;
        waterdepth->getFormat().toCharArray(busInfo.WaterDepth.unit, 8, 0);
        busInfo.WaterDepth.valid = int(waterdepth->valid);

        formatValue(date).toCharArray(busInfo.Date.svalue, 16, 0);
        busInfo.Date.valid = date->valid;

        formatValue(time).toCharArray(busInfo.Time.svalue, 16, 0);
        busInfo.Time.valid = time->valid;

        busInfo.HDOP.fvalue = hdop->value;
        busInfo.HDOP.valid = hdop->valid;

        busInfo.PDOP.fvalue = pdop->value;
        busInfo.PDOP.valid = pdop->valid;


        // Subtask all 500ms show pages
        if((taskRunCounter % 50) == 0  || first_view == true){
            LOG_DEBUG(GwLog::DEBUG,"Keystatus: %s", keystatus);
            LOG_DEBUG(GwLog::DEBUG,"Pagenumber: %d", pageNumber);
            if(displayMode == "Logo + QR Code" || displayMode == "Logo" || displayMode == "White Screen"){
                showPage(busInfo);
            }
        }

        // Subtask E-Ink full refresh
        if((taskRunCounter % (FULL_REFRESH_TIME * 100)) == 0){
            LOG_DEBUG(GwLog::DEBUG,"E-Ink full refresh");
            display.update(); 
        }

        taskRunCounter++;
    }
    vTaskDelete(NULL);
    
}

#endif