/*
 *  Under development, somehow produtive :)
 *
 */

#include "myClock.h"  
#include <TimeLib.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
//#include <WiFi.h>
#include <WiFiClientSecure.h>

#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"

#include <WiFiManager.h>   
#include <WiFiUdp.h>
#include <NTPClient.h>
//#include <MD_CirQueue.h> // switch to xQueueHandle 
#include <MD_REncoder.h>
#include <BH1750.h>
#include <SparkFunBME280.h>
#include "DFRobotDFPlayerMini.h"
#include <PPMax72xxPanel.h>
#include <PPmax72xxAnimate.h>
#include <myScheduler.h>
#include <Preferences.h>


PPMax72xxPanel matrix = PPMax72xxPanel(pinCS, numberOfHorizontalDisplays, numberOfVerticalDisplays);
PPmax72xxAnimate zoneClockH0 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneClockH1 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneClockM0 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneClockM1 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneClockS1 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneClockS0 = PPmax72xxAnimate(&matrix);

PPmax72xxAnimate zoneInfo0 = PPmax72xxAnimate(&matrix);
PPmax72xxAnimate zoneInfo1 = PPmax72xxAnimate(&matrix);
//PPmax72xxAnimate zoneInfo2 = PPmax72xxAnimate(&matrix);

BH1750 lightMeter;
BME280 mySensor;

temp_t tempTable[NumberOfPoints];
temp_t humiTable[NumberOfPoints];
temp_t presTable[NumberOfPoints];

//const uint8_t  QUEUE_SIZE = 15;
//MD_CirQueue Q(QUEUE_SIZE, sizeof(uint8_t)); // switch to xQueueHandle :
xQueueHandle xQueue;

// set up encoder object
MD_REncoder R = MD_REncoder(PIN_A, PIN_B);

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 0);
unsigned long NTPSyncPeriod = NTPRESYNC;
boolean DSTFlag = false; // indicate if DS is on/off based on the time for Germany / CET,CEST
boolean SyncNTPError = false;  // true if the last NTP was finished with an error due the wifi or ntp failure

ClockStates ClockState  = _Clock_init;  // current clock status
ClockStates goBackState = _Clock_init;  // last clock status


Schedular SensorUpdate(_Seconds);     // how often are the sensor read
Schedular NTPUpdateTask(_Seconds);    // how often is the clock synced with NTP server
Schedular DataDisplayTask(_Millis);   // scroll over to the next type of info for a full clock info mode 
Schedular IntensityCheck(_Millis);    // how oftenshall be check the light to stear the intensity of LED Matrix
Schedular SnakeUpdate(_Millis);       // for the snake next step
Schedular StatTask(_Millis);          // update statistic screen period
Schedular SecondsVA(_Millis);         // delay for getting VA back to zero
Schedular ScreenSaver(_Seconds);      // LED Matrix standby time after last PIR movement detection
Schedular ChimeQuarter(_Seconds);     // time for qurterly chime
Schedular ChimeHour(_Seconds);        // time for hourly chime
Schedular ChimeWait(_Millis);         // time to wait after 4 quarter chime
Schedular UpdateMQTT(_Seconds);       // Frequency of updating IOT


// MP3 Player
HardwareSerial DFPSerial(1);
DFRobotDFPlayerMini DFPlayer;


// IOT variables
WiFiClientSecure client;
// Setup the MQTT client class by passing in the WiFi client and MQTT server and login details.
Adafruit_MQTT_Client mqtt(&client, AIO_SERVER, AIO_SERVERPORT, AIO_USERNAME, AIO_KEY);
// Feeds

// Notice MQTT paths for AIO follow the form: <username>/feeds/<feedname>
Adafruit_MQTT_Publish tempMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedTemp);
Adafruit_MQTT_Publish humiMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedHumi);
Adafruit_MQTT_Publish presMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedPres);
Adafruit_MQTT_Publish brigMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedBrig);
Adafruit_MQTT_Publish onofMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedOnOf);
Adafruit_MQTT_Publish dataMQTT = Adafruit_MQTT_Publish(&mqtt, AIO_USERNAME feedData);

// Setup a feed for subscribing to changes.
Adafruit_MQTT_Subscribe ledMQTT = Adafruit_MQTT_Subscribe(&mqtt, AIO_USERNAME feedLED);

// Bug workaround for Arduino 1.6.6, it seems to need a function declaration
// for some reason (only affects ESP8266, likely an arduino-builder bug).
boolean MQTT_connect();


//globals for screensaver status main loop and MQTT updates
bool screenSaverNotActive = true;
String lastTime = "";


// Parameter section, just one for the time being
Preferences preferences;
boolean DingOnOff = true;


void clearScreen(void) {
  matrix.setClip(0, matrix.width(), 0, matrix.height());
  matrix.fillScreen(LOW);
  matrix.write();    
}


// Time management 
time_t requestSync() {return 0;} // the time will be sent later in response to serial mesg

void processSyncMessage() {
  // unsigned long pctime = 1509235190;
  unsigned long pctime = timeClient.getEpochTime();
  setTime(pctime); // Sync clock to the time received on the serial port
}

boolean SetUpDST(uint8_t _month, uint8_t _day, uint8_t _weekday) {
// Dst = True for summer time 
// _weekday = day of the week Sunday = 1, Saturday = 7

  if (_month < 3 || _month > 10)  return false; 
  if (_month > 3 && _month < 10)  return true; 

  int previousSunday = _day - _weekday;
  if (_month == 3)  return previousSunday >= 24;
  if (_month == 10) return previousSunday < 24;

  return false; // this line never gonna happend
}

void correctByDST() {
  //Check if DST has to correct the time
  if (month() == 3) {
    if (day() - weekday() >= 24) {
      if ( (hour() == 2) && (!DSTFlag) ) {
        DSTFlag = true;
        adjustTime(+SECS_PER_HOUR);
      }
    }
  } else if (month() == 10) {
    if (day() - weekday() >= 24) {
      if ( (hour() == 2) && (DSTFlag) ) {
        DSTFlag = false;
        adjustTime(-SECS_PER_HOUR);
      }      
    }
  }        
}

boolean SyncNTP() {

  PRINTS("Contacting NTP Server process\n");
  if (timeClient.forceUpdate()){
    PRINTS("NTP sync OK, UTC="); PRINTS(timeClient.getFormattedTime() ); PRINTLN;
    setTime(timeClient.getEpochTime());
    return true;
  } else {
    PRINTS("NTP sync failed!");
    return false;    
  }
}

boolean StartSyncNTP() {
  boolean SyncError = false;
    
    PRINTS("NTP Sync Init started\n");
    zoneInfo0.setText("NTP Sync", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
    zoneInfo0.Animate(true);
    if (WiFi.isConnected()) {  
        SyncError = !SyncNTP();
    } else {
        PRINTS("Recovering WiFi connection\n");
        //WiFi.mode(WIFI_STA);
        matrix.fillScreen(LOW);
        matrix.setCursor(0,0);
        zoneInfo0.setText("Connecting WiFi", _SCROLL_LEFT, _TO_FULL, InfoTick1, I0s, I0e);
        WiFi.begin();
        zoneInfo0.Animate(true);
        if (WiFi.isConnected()) {
          zoneInfo0.setText("Connecting NTP", _SCROLL_LEFT, _TO_FULL, InfoTick1, I0s, I0e);
          SyncError = !SyncNTP();
          zoneInfo0.Animate(true);
          //WiFi.mode(WIFI_OFF);
        } else {
          SyncError = true; // wifi failure
        }
    }    
  return SyncError;
}

bool shouldSaveConfig = false;
//callback notifying us of the need to save config
void saveConfigCallback () {
  PRINTS("WIFI saves config\n");
  shouldSaveConfig = true;
}

void SetupWiFi(void) {
    WiFiManager wifiManager; 
  
    //reset settings - for testing
    //wifiManager.resetSettings();
    zoneInfo0.setText("WiFi Setup", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
    zoneInfo0.Animate(true);
    PRINTS("Staring WiFi Manager\n");
    //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
    //wifiManager.setAPCallback(configModeCallback);
    wifiManager.setSaveConfigCallback(saveConfigCallback);
    wifiManager.setMinimumSignalQuality();
    // wifiManager.setAPStaticIPConfig(IPAddress(192,168,2,1), IPAddress(192,168,2,1), IPAddress(255,255,255,0));
    if (!wifiManager.autoConnect("ClockWiFi", "password")) {
      PRINTS("\nfailed to connect and hit timeout");
      delay(3000);
      // reset and try again, or maybe put it to deep sleep
      ESP.restart();
      delay(5000);
    }
    PRINTS("...WIFI connected!\n");
    zoneInfo0.setText("Connected", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
    zoneInfo0.Animate(true);
    PRINTS("WiFi connected\n");
    PRINTS("IP address: \n");
    PRINTS(WiFi.localIP());
    PRINTLN;
}

boolean updateTtime(int &value, int &lvalue, int newvalue, char what) {
  String sValue;
  if (value != newvalue)  {
    value = newvalue;
    if ( lvalue / 10 != value / 10 ) {
      sValue = String(lvalue /10) + "\n" + String(value /10);
      switch (what) {
        case 'H': zoneClockH1.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, H1s, H1e);
          break;
        case 'M': zoneClockM1.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, M1s, M1e);
          break;
        case 'S': zoneClockS1.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, S1s, S1e);
          break;
      }
    }
    sValue = String(lvalue % 10) + "\n" + String(value % 10); 
    switch (what) {
      case 'H': zoneClockH0.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, H0s, H0e);
        break;
      case 'M': zoneClockM0.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, M0s, M0e);
        break;
      case 'S': zoneClockS0.setText(sValue, _SCROLL_UP_SMOOTH, _NONE_MOD, ClockAnimTick, S0s, S0e);
        break;
    }
    lvalue = value;
    return true;
  } else return false;
}

uint8_t IntensityMap(uint16_t sensor) {
   uint8_t Intensity;
   if (sensor < 80) Intensity = 0;
   else if (sensor < 110) Intensity = 1;
   else if (sensor < 140) Intensity = 2;
   else if (sensor < 170) Intensity = 3;
   else if (sensor < 200) Intensity = 4;
   else if (sensor < 250) Intensity = 5;
   else if (sensor < 300) Intensity = 6;
   else if (sensor < 350) Intensity = 7;
   else if (sensor < 400) Intensity = 8;
   else if (sensor < 450) Intensity = 9;
   else if (sensor < 500) Intensity = 10;
   else if (sensor < 550) Intensity = 11;
   else if (sensor < 600) Intensity = 12;
   else if (sensor < 650) Intensity = 13;
   else if (sensor < 700) Intensity = 14;
   else Intensity = 15;
   return Intensity;  
}

boolean checkTime(uint8_t hh, uint8_t mm, uint8_t ss, boolean exact) {
  if (exact) return (hh == hour() && mm == minute() && ss == second());
  else return (hh == hour() && mm == minute() && ss <= second());
}


uint8_t keyboard(ClockStates key_DIR_CW, ClockStates key_DIR_CCW, ClockStates key_SW_DOWN, ClockStates key_SW_UP) {
  uint8_t  key=DIR_NONE;

//  noInterrupts();
//  if (!Q.isEmpty()) {
//    Q.pop(&key);
//  }
//  interrupts();


  if ( xQueueReceive( xQueue, &key, 0 ) == pdPASS ) {
    switch(key) {
      case DIR_CW:
        if (key_DIR_CW != _Clock_none) ClockState = key_DIR_CW;
        break;
      case DIR_CCW:
        if (key_DIR_CCW != _Clock_none) ClockState = key_DIR_CCW;
        break;
      case SW_DOWN:
        if (key_SW_DOWN != _Clock_none) ClockState = key_SW_DOWN;
        break;
      case SW_UP:
        if (key_SW_UP != _Clock_none) ClockState = key_SW_UP;
        break;
    }
    #if DEBUG_ON
      if (key != DIR_NONE) {PRINT("New Closk State: ", ClockState); PRINTLN;}
    #endif
    return key;
  } else return DIR_NONE;
}

/* Snake Routines 
Start => */
boolean occupied(int ptrA, const int snakeLength, int *snakeX, int *snakeY) {
  for ( int ptrB = 0 ; ptrB < snakeLength; ptrB++ ) {
    if ( ptrA != ptrB ) {
      if ( equal(ptrA, ptrB, snakeX, snakeY) ) {
        return true;
      }
    }
  }
  return false;
}

int next(int ptr, const int snakeLength) {
  return (ptr + 1) % snakeLength;
}

boolean equal(int ptrA, int ptrB, int *snakeX, int *snakeY) {
  return snakeX[ptrA] == snakeX[ptrB] && snakeY[ptrA] == snakeY[ptrB];
}
/* <= End Snake Routines */

void loadPreferences(void) {
    preferences.begin("clock", false);
    //preferences.clear();
    DingOnOff = preferences.getBool("DingOnOff", true);
    preferences.end();
}

void savePreferences(void) {
    preferences.begin("clock", false);
    //preferences.clear();
    preferences.putBool("DingOnOff", DingOnOff);
    preferences.end();
}


void setup()
{
 //   #if  DEBUG_ON
      Serial.begin(115200);
      PRINTS("Clock started\n");
//    #endif 

    loadPreferences();

    // start serial interface for MP3 player
    DFPSerial.begin(9600, SERIAL_8N1, MP3RX, MP3TX);  // speed, type, RX, TX

    delay(250);    // Delay needed to initiate the serial interface(s)

    // SetUp Sensors
    Wire.begin(21,22, 400000);
    //  ADDR=0 => 0x23 and ADDR=1 => 0x5C.
    lightMeter.begin();
      

    pinMode(ledPin,  OUTPUT); // passing seconds LED
    pinMode(modePin, INPUT_PULLUP); // check setup
    pinMode(pirPin, INPUT); // input from PIR sensor
         
    // matrix.setIntensity(0); // Use a value between 0 and 15 for brightness
    matrix.setIntensity(IntensityMap(lightMeter.readLightLevel()));     
  
    matrix.setPosition(0, 7, 0); matrix.setRotation(0, 3);    
    matrix.setPosition(1, 6, 0); matrix.setRotation(1, 3);
    matrix.setPosition(2, 5, 0); matrix.setRotation(2, 3);
    matrix.setPosition(3, 4, 0); matrix.setRotation(3, 3);
    matrix.setPosition(4, 3, 0); matrix.setRotation(4, 3);
    matrix.setPosition(5, 2, 0); matrix.setRotation(5, 3);
    matrix.setPosition(6, 1, 0); matrix.setRotation(6, 3);
    matrix.setPosition(7, 0, 0); matrix.setRotation(7, 3);

    matrix.fillScreen(LOW);
    //matrix.setTextColor(HIGH);
    matrix.setTextColor(HIGH, LOW);
    matrix.setTextWrap(false);

    IntensityCheck.start();
    
    zoneInfo0.setText("   Starting Clock ...", _SCROLL_LEFT, _TO_FULL, InfoTick, I0s, I0e);
    zoneInfo0.Animate(true);

    if (!DFPlayer.begin(DFPSerial)) {  //Use softwareSerial to communicate with mp3.
      PRINTX("MP3 Player failed", DFPlayer.readType());
      zoneInfo0.setText("MP3 Player failed", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
      zoneInfo0.Animate(true);
    } else {
      DFPlayer.setTimeOut(500);
      DFPlayer.volume(30);  
      DFPlayer.EQ(DFPLAYER_EQ_BASS);
      DFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
    }    

    for (int wait = 10; wait >= 0; wait -=1) {
      for ( int x = 0; x < matrix.width() - 1; x++ ) {
        matrix.fillScreen(LOW);
        matrix.drawLine(x, 0, matrix.width() - 1 - x, matrix.height() - 1, HIGH);
        matrix.write(); // Send bitmap to display
        delay(wait);
      }
    }

    matrix.fillScreen(LOW);
    matrix.write();


    //WiFi.mode(WIFI_OFF);

    // Q.begin(); // start queue to collect button and rotery events
    xQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint8_t));

    pinMode(PIN_BUT, INPUT_PULLUP); // button to operate menu
    attachInterrupt(digitalPinToInterrupt(PIN_BUT), processButton, CHANGE);
     
    R.begin(); // start rotary decoder
    attachInterrupt(digitalPinToInterrupt(PIN_A), processEncoder, CHANGE);
    attachInterrupt(digitalPinToInterrupt(PIN_B), processEncoder, CHANGE);
    
    mySensor.settings.commInterface = I2C_MODE;
    mySensor.settings.I2CAddress = 0x76;
    mySensor.settings.tStandby = 0;
    mySensor.settings.runMode = 3; //Normal mode
    mySensor.settings.filter = 0;
    mySensor.settings.tempOverSample = 1;
    mySensor.settings.pressOverSample = 1;
    mySensor.settings.humidOverSample = 1;
    mySensor.begin();
    SensorUpdate.start(-1);

    // zero temperature table
    for (int i=0; i < NumberOfPoints; tempTable[i++]=0);
    for (int i=0; i < NumberOfPoints; humiTable[i++]=0);
    for (int i=0; i < NumberOfPoints; presTable[i++]=0);
    
    DFPlayer.playFolder(3, 101);

    
    if (digitalRead(modePin)) {
      SetupWiFi();
      SyncNTPError = StartSyncNTP();
      if (!SyncNTPError) {
        if (SetUpDST(month(), day(), weekday())) {
          DSTFlag = true;
          timeClient.setTimeOffset(SECS_PER_HOUR*CEST);
          adjustTime(+SECS_PER_HOUR*CEST);
        } else {
          DSTFlag = false;
          timeClient.setTimeOffset(SECS_PER_HOUR*CET);
          adjustTime(+SECS_PER_HOUR*CET);
          //adjustTime(+48*60);
        }    
        // setTime(1512093784+60*56+40); // Friday, December 1, 2017 2:03:04 AM
        PRINTS("             Local Time="); PRINTS(timeClient.getFormattedTime() ); PRINTLN;

        PRINTS(digitalClockString());

        correctByDST();
//        PRINT("Now: ", now()); PRINTLN;
//        PRINT("Hour: ", hour()); PRINTLN;
//        PRINT("Min: ", minute()); PRINTLN;
//        PRINT("Sec: ", second()); PRINTLN;
//        PRINT("hour()%12): ", (hour()%12)); PRINTLN;
//        PRINT("11-(hour()%12): ", 11-(hour()%12)); PRINTLN;
//        PRINT("(11-(hour()%12))*3600: ", (11-hour()%12)*SECS_PER_HOUR); PRINTLN;
//        PRINT("(60-minute())*60: ", (60-minute())*60); PRINTLN;
//        // sync every 12 hours at 12:05 or 24:05 
//        PRINT("Seconds to sync at 12:10: ", ((12-(hour()%12))*60 - minute())*60-second()+10*60 -NTPRESYNC); PRINTLN;
//        //PRINT("   Mins to sync at 12:10: ", (11-(hour()%12))*60+60-minute()+10 - NTPRESYNC); PRINTLN;
//        PRINT("   Secs to sync:at     H: ", (60-minute())*60-second() - 60*60); PRINTLN;
//        PRINT("   Secs to sync:at   x15: ", ((60-minute() -((int)(60-minute())/(int)15)*15))*60-second()-15*60); PRINTLN;
        
        NTPUpdateTask.start( ((12-(hour()%12))*60 - minute())*60-second()+10*60 -NTPRESYNC );
        
        zoneInfo0.setText("Sync OK", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
        zoneInfo0.Animate(true);
        delay(250);
      } else {
        PRINTS("Error at first NTP Sync\n");
        zoneInfo0.setText("1.NTP Sync ERROR", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
        zoneInfo0.Animate(true);        
      }
    } else {
      PRINTS("No WiFi by setup on GPIO27\n"); 
      zoneInfo0.setText("WiFi Off", _SCROLL_LEFT, _TO_LEFT, InfoTick1, I0s, I0e);
      zoneInfo0.Animate(true);
    }

    //mqtt.subscribe(&ledMQTT);
    // UpdateMQTT.start(-Time2UpdateMQTT);

    SecondsVA.start();
    ScreenSaver.start();

    ChimeQuarter.start( ((60-minute() -((int)(60-minute())/(int)15)*15))*60-second()-15*60 );
    ChimeHour.start( (60-minute())*60-second() - 60*60 );   
    
    
    matrix.setClip(0, matrix.width(), 0, matrix.height());
    matrix.fillScreen(LOW);
    matrix.write();    

    goBackState = _Clock_complete_info;
    ClockState = _Clock_complete_info_init;
    //ClockState = _Clock_simple_time_init;

    xTaskCreate(
    taskMQTT,           /* start regular  MQTT update task*/
    "taskMQTT",         /* name of task. */
    8000,               /* Stack size of task */
    NULL,               /* parameter of the task */
    1,                  /* priority of the task */
    NULL);                    /* Task handle to keep track of created task */

}

void loop()
{
    // current and last time values
    static int valueH = 0;
    static int valueM = 0;
    static int valueS = 0;
    static int lvalueH = -1;
    static int lvalueM = -1;
    static int lvalueS = -1;   
    
    static bool flasher = false;          // seconds passing flasher
    static uint8_t intensity = 0;         // brithness of the led matrix - all modules
    static uint8_t lintensity = 0;        // last brithness of the led matrix - all modules
    static boolean updateDisplay = false; // Any change => display needs to be updated

    static char DataStr[] = "xx:xx:xx Xxx xx Xxx xxxx                ";
                          // 0123456789012345678901234567891234567890
                          // 0         2         3         4        5
                          // 23:59:59 Sun 31 Oct 2016 100°C 1000HPa
                          
    static uint8_t DataMode = 0;
    uint8_t key;

    // index for tables with measurements
    static uint8_t dayNumber = 0 ; 
    static unsigned long measurementNumber = 1;

    static temp_t tempMin = +10000;
    static temp_t tempMax = -10000;
    static humi_t humiMin = +10000;
    static humi_t humiMax = -10000;
    static pres_t presMin = +10000;
    static pres_t presMax = -10000;
    
    temp_t tempValue;
    pres_t presValue;
    humi_t humiValue;
    int    intValue;
        
    static temp_t averageTemp=0;
    static humi_t averageHumi=0;
    static pres_t averagePres=0;
    
    textEffect_t textEffect;
    unsigned int infoTime;
    
//    int16_t  tx1, ty1;
//    uint16_t tw, th;    
//    boolean decPoint;

    // Snake variables
    static int snakeLength = 1;
    static int snakeX[MaxSnake], snakeY[MaxSnake];
    static int ptr, nextPtr;
    static int snakeRound = 0;
    static SnakeStates_t SnakeState;
    int attempt;
    boolean continueLoop = true;

    static byte VAValue;
  
    static String ParamS = DingOnOff? "On":"Off";


  if (SensorUpdate.check(MeasurementFreg)) {

    averageTemp = tempTable[dayNumber] + (mySensor.readTempC()-tempTable[dayNumber])/measurementNumber;
    tempTable[dayNumber] = averageTemp;

    averagePres = presTable[dayNumber] + (mySensor.readFloatPressure()/100-presTable[dayNumber])/measurementNumber;
    presTable[dayNumber] = averagePres;

    averageHumi = humiTable[dayNumber] + (mySensor.readFloatHumidity()-humiTable[dayNumber])/measurementNumber;
    humiTable[dayNumber] = averageHumi;
  
//    PRINT("Day: ", dayNumber);
//    PRINT("   Measuremeant: ", measurementNumber);
//    PRINT("  Aver humi: ", humiTable[dayNumber]);
//    PRINTLN;
    
    measurementNumber++;
    if (measurementNumber >= MaxMeasurements) {
      measurementNumber = 1;
      dayNumber++;
      if (ClockState==_Clock_Temp) ClockState=_Clock_Temp_init;
      if (dayNumber >= NumberOfPoints ) {
        for (int ii = 0; ii <= NumberOfPoints-2; ii++) {
          tempTable[ii] = tempTable[ii+1];
          presTable[ii] = presTable[ii+1];
          humiTable[ii] = humiTable[ii+1];
        }
        tempTable[NumberOfPoints-1] = 0;
        dayNumber = NumberOfPoints-1;
      }
    }
  }

  if (second()==59) {
    if (SecondsVA.check(VADelay)) {
      VAValue -= VADec;
      if (VAValue<MinV) VAValue = MinV;
      dacWrite(DACOut, VAValue);
    }
  } else {
    VAValue = MaxV;
    dacWrite(DACOut, map(second(), 0, 59, MinV, MaxV));   
  }
   
  // check the light to setup matrix intensivity after a final write
  if (IntensityCheck.check(IntensityWait)) {
    uint16_t lux = lightMeter.readLightLevel();
    intensity = IntensityMap(lux);
    //PRINT("Light: ",lux);
    //PRINT(" lx  MAP:", intensity);
    //PRINTLN;
    if (intensity != lintensity) {
      //matrix.setIntensity(intensity);
      lintensity = intensity;
    }

    // check and activate screen saver mode max7219 -> shutdown mode and (re)set screenSaverNotActive flag for matrix.write 
    if (digitalRead(pirPin)) {
      ScreenSaver.start();
      digitalWrite(ledPin, LOW);
      matrix.shutdown(false);
      screenSaverNotActive = true;
      lastTime = digitalClockString();
    } else {
      if (ScreenSaver.check(ScreenTimeOut)) {
        digitalWrite(ledPin, HIGH);
        matrix.shutdown(true);
        screenSaverNotActive = false;
      }
    }
  }

  // check if NTP sync is due?
  // If yes change clock status
  if (NTPUpdateTask.check(NTPRESYNC)) {
    ClockState  = _Clock_NTP_Sync;
  }

  // for all status except ... check if the time of DST has came and if yes change the time accordingly
  if ( ClockState != _Clock_init ) {
    correctByDST();
  }

  // check time dependant actions

  { int hourTemp = hour();
    if (ChimeQuarter.check(CHIMEQ)) {
     int fileNumber = minute() / 15;
     PRINT("Minute", minute()); 
     PRINT(" Play Qurter file number", fileNumber); PRINTLN;
     if (DingOnOff && hourTemp >= DingON && hourTemp < DingOFF ) DFPlayer.playFolder(2, fileNumber);
     ChimeWait.start();
    }
    if (ChimeWait.check(CHIMEW)) {
      if (ChimeHour.check(CHIMEH)) {
        int fileNumber = hour() % 12;
        PRINT("Hour", hour()); 
        PRINT(" Play hour file number", fileNumber); PRINTLN;
        if (DingOnOff && hourTemp >= DingON && hourTemp < DingOFF ) DFPlayer.playFolder(1, fileNumber);
      }
    }
  }

  // cehck the current clock / display status
  switch(ClockState)
  {
//      case _Clock_init:
//          ClockState = _Clock_simple_time_init;
//          break;
          
      case _Clock_NTP_Sync:
        PRINTS(digitalClockString());
        if (StartSyncNTP()) { // error by NTP sync
          zoneInfo0.setText("NTP Sync ERROR", _SCROLL_LEFT, _TO_FULL, InfoTick1, I0s, I0e);
          zoneInfo0.Animate(true);
          SyncNTPError = true;          
        } else {
          zoneInfo0.setText("NTP Sync OK", _SCROLL_LEFT, _TO_FULL, InfoTick1, I0s, I0e);
          zoneInfo0.Animate(true);
          PRINTS("NTP sync OK, Local="); PRINTS(timeClient.getFormattedTime() ); PRINTLN;
          SyncNTPError = false;
        }
        ClockState = goBackState;
        PRINT("NTP Resync Completed\nNew mode=", ClockState); PRINTLN;
        PRINTS(digitalClockString());
        break;

      case _Clock_Temp_init:
          clearScreen();
          StatTask.start();
          tempMin = tempTable[0];
          tempMax = tempTable[0];
          for (ptr=0; ptr < dayNumber; ptr++) {
            tempValue = tempTable[ptr];
          
//            PRINT("tempValue : ", tempValue);
//            PRINT("  min : ", tempMin);
//            PRINT("  max : ", tempMax);
//            PRINTLN;

            if (tempValue < tempMin) tempMin = tempValue;
            if (tempValue > tempMax) tempMax = tempValue;

            presValue = presTable[ptr];
            if (presValue < presMin) presMin = presValue;
            if (presValue > presMax) presMax = presValue;

            humiValue = humiTable[ptr];
            if (humiValue < humiMin) humiMin = humiValue;
            if (humiValue > humiMax) humiMax = humiValue;
          }
          if (tempMax-tempMin < 8) tempMax = tempMin+8;
          if (presMax-presMin < 8) presMax = presMin+8;
          if (humiMax-humiMin < 8) humiMax = humiMin+8;
          
          goBackState = _Clock_Temp_init;
          ClockState = _Clock_Temp;
          DataMode = 0;
          break;

      case _Clock_Temp:

          if (StatTask.check(DiagramDelay)) {
            clearScreen();            
            matrix.setCursor(0,0);
            switch (DataMode) {
              case 0:
                  matrix.print("T");
                  break;
              case 1:
                  matrix.print("P");
                  break;
              case 2:
                  matrix.print("H");
                break;
            }
            for (ptr = 0; ptr <= dayNumber; ptr++) {
                switch (DataMode) {
                  case 0:
                      intValue = map(tempTable[ptr], tempMin, tempMax, 0, 8);                
                      break;
                  case 1:
                      intValue = map(presTable[ptr], presMin, presMax, 0, 8);                
                      break;
                  case 2:
                      intValue = map(humiTable[ptr], humiMin, humiMax, 0, 8);                
                      break;
                }
                intValue = constrain(intValue, 0, 8);

//              PRINT("i: ", ptr);
//              PRINT("  Table: ", intValue);
//              PRINT("  min : ", tempMin);
//              PRINT("  max : ", tempMax);
//              PRINTLN;

              matrix.drawFastVLine(ptr+ShiftDiagram, 8-intValue, intValue, HIGH);
            }
            updateDisplay = true;
          }
          if (keyboard(_Clock_menu_init, _Clock_simple_time_init, _Clock_none, _Clock_none) == SW_UP) { 
            DataMode = (DataMode+1)%3;
            StatTask.check(-DiagramDelay);
          }
          break;
                             
      case _Clock_simple_time_init:

          clearScreen();

          valueH = hour();
          valueM = minute();
          valueS = second();
          lvalueH = valueH;
          lvalueM = valueM;
          lvalueS = valueS;

          matrix.drawChar(H1s,0, (char)('0' + valueH / 10), HIGH, LOW, 1);
          matrix.drawChar(H0s,0, (char)('0' + valueH % 10), HIGH, LOW, 1);
          matrix.drawChar(M1s,0, (char)('0' + valueM / 10), HIGH, LOW, 1);
          matrix.drawChar(M0s,0, (char)('0' + valueM % 10), HIGH, LOW, 1);
          matrix.drawChar(S1s,0, (char)('0' + valueS / 10), HIGH, LOW, 1);
          matrix.drawChar(S0s,0, (char)('0' + valueS % 10), HIGH, LOW, 1);
          matrix.drawPixel(M0e+1,0,HIGH);
          matrix.drawPixel(M0e+1,1,HIGH);
          matrix.drawPixel(H0e+1,2,HIGH);
          matrix.drawPixel(H0e+1,5,HIGH);
          matrix.drawPixel(H0e+1,7, SyncNTPError || !digitalRead(modePin)); // indicate if NTP sync error 
//          PRINT("SyncNTPError =",SyncNTPError);
//          PRINT("   Mode =",digitalRead(modePin));
//          PRINTLN;

          updateDisplay = true;

          //IntensityCheck.start();
          SnakeUpdate.start();
          randomSeed(analogRead(pinRandom)); // Initialize random generator
          SnakeState = _sInit;
          
          PRINTS("Simple Time Init closed\n");
          
          goBackState = ClockState;
          ClockState = _Clock_simple_time;
          break;

      case _Clock_simple_time:
          updateTtime(valueH, lvalueH, hour(), 'H');
          updateTtime(valueM, lvalueM, minute(), 'M');
          if ( updateTtime(valueS, lvalueS, second(), 'S') ) {
//            flasher = !flasher;
//            digitalWrite(ledPin, flasher);
            updateDisplay = true;

            PRINTS(digitalClockString());

//            matrix.setClip(H0e+1,H0e+2,0,8);
//            matrix.drawPixel(H0e+1,2,flasher);
//            matrix.drawPixel(H0e+1,5,flasher);
          }
          updateDisplay |= zoneClockH0.Animate(false);
          updateDisplay |= zoneClockH1.Animate(false);
          updateDisplay |= zoneClockM0.Animate(false);
          updateDisplay |= zoneClockM1.Animate(false);
          updateDisplay |= zoneClockS1.Animate(false); 
          updateDisplay |= zoneClockS0.Animate(false);

          // Snake animation
          if (SnakeUpdate.check(SnakeWait) || SnakeState ==_sRunA) {
              matrix.setClip(SNs,SNe,0,8);
              updateDisplay = true;
              switch (SnakeState) {
                case _sInit:
//                        PRINTS("\n Init");
                        matrix.fillScreen(LOW);
                        for ( ptr = 0; ptr < snakeLength; ptr++ ) {
                          snakeX[ptr] = SNs+(SNe-SNs) / 2;
                          snakeY[ptr] = matrix.height() / 2;
                        }
                        nextPtr = 0;
                        snakeLength = 1;
                        snakeRound = 0;              
          
                        SnakeState = _sRunA;
                        break;
          
                case _sRunA:              
//                        PRINTS("\n State A");
                        ptr = nextPtr;
                        nextPtr = next(ptr, snakeLength);
                        matrix.drawPixel(snakeX[ptr], snakeY[ptr], HIGH); // Draw the head of the snake
                        SnakeState = _sRunB;
                        break;      
                        
                case _sRunB:
//                        PRINTS("\n State B");
                        if ( !occupied(nextPtr, snakeLength, snakeX, snakeY) ) {
                          matrix.drawPixel(snakeX[nextPtr], snakeY[nextPtr], LOW); // Remove the tail of the snake
                        }
                      
                        continueLoop = true;
                        for ( attempt = 0; (attempt < SnakeAttempt) && continueLoop ; attempt++ ) {
                          // Jump at random one step up, down, left, or right
                          switch ( random(4) ) {
                            case 0: 
                                snakeX[nextPtr] = (snakeX[ptr] + 1 >= SNe) ? SNs : snakeX[ptr] + 1;
                                snakeY[nextPtr] = snakeY[ptr]; 
                                break;
                            case 1:
                                snakeX[nextPtr] = (snakeX[ptr] - 1 < SNs) ? SNe-1 : snakeX[ptr] - 1;      
                                snakeY[nextPtr] = snakeY[ptr]; 
                                break;
                            case 2: 
                                snakeY[nextPtr] = (snakeY[ptr] + 1 > matrix.height()-1)? 0 : snakeY[ptr] + 1;
                                snakeX[nextPtr] = snakeX[ptr]; 
                                break;
                            case 3:
                                snakeY[nextPtr] = (snakeY[ptr] - 1 < 0)? matrix.height()-1 : snakeY[ptr] - 1;
                                snakeX[nextPtr] = snakeX[ptr]; 
                                break;
                          }    
                          continueLoop = occupied(nextPtr, snakeLength, snakeX, snakeY);
                        }
                        if (attempt == SnakeAttempt) { 
                          matrix.fillScreen(HIGH);
                          SnakeState = _sFail;
                        } else {
                            snakeRound = (snakeRound +1) % SankeNextRound;
                            if (snakeRound == 0) snakeLength = snakeLength + 1;
                            if (snakeLength >= MaxSnake) {
                               matrix.fillScreen(HIGH);
                               SnakeState = _sFail;
                            }  else SnakeState = _sRunA;  
                        }
                        break;
                case _sFail:
//                        PRINTS("\n Fail");
                        SnakeState = _sInit;
                        break;
              }
          }

          key = keyboard(_Clock_Temp_init, _Clock_complete_info_init, _Clock_none, _Clock_none);
          break;     

      case _Clock_menu_init:
          clearScreen();
          zoneInfo0.setText("Menu:", _SCROLL_RIGHT, _TO_LEFT, InfoTick, I0s, I0e);
          zoneInfo0.Animate(true);
          updateDisplay = false;
          goBackState = _Clock_menu_init;
          ClockState = _Clock_menu;
          DataMode = 0;
          zoneInfo0.setText("Ding:", _PRINT, _NONE_MOD, InfoTick, MEs, MEe);
          zoneInfo1.setText(ParamS, _BLINK, _NONE_MOD, InfoSlow, PAs, PAe);
          break;
          
      case _Clock_menu:
          updateDisplay = zoneInfo1.Animate(false);
          if (keyboard(_Clock_complete_info_init, _Clock_Temp_init, _Clock_none, _Clock_none) == SW_UP) {
            DingOnOff = !(DingOnOff);
            ParamS = DingOnOff? "On":"Off";
            zoneInfo1.setText(ParamS, _BLINK, _NONE_MOD, InfoSlow, PAs, PAe);
            savePreferences();
            updateDisplay = true;
          }
          break;     
      
      case _Clock_complete_info_init:
       
          clearScreen();
          
          valueH = hour();
          valueM = minute();
          valueS = second();
          lvalueH = valueH;
          lvalueM = valueM;
          lvalueS = valueS;

          matrix.drawChar(H1s,0, (char)('0' + valueH / 10), HIGH, LOW, 1);
          matrix.drawChar(H0s,0, (char)('0' + valueH % 10), HIGH, LOW, 1);
          matrix.drawChar(M1s,0, (char)('0' + valueM / 10), HIGH, LOW, 1);
          matrix.drawChar(M0s,0, (char)('0' + valueM % 10), HIGH, LOW, 1);
          matrix.drawPixel(H0e+1,7, SyncNTPError || !digitalRead(modePin)); // indicate if NTP sync error 
//          PRINT("SyncNTPError =",SyncNTPError);
//          PRINT("   Mode =",digitalRead(modePin));
//          PRINTLN;
          
          updateDisplay = true;

          DataMode = 0;
          DataDisplayTask.start(-2000);
          
          //IntensityCheck.start();
          
          PRINTS("Complete Time Init closed\n");

          goBackState = ClockState;
          ClockState = _Clock_complete_info;
          break;

      case _Clock_complete_info:
     
          updateTtime(valueH, lvalueH, hour(), 'H');
          updateTtime(valueM, lvalueM, minute(), 'M');
          if ( updateTtime(valueS, lvalueS, second(), 'S') ) {
            flasher = !flasher;
            // digitalWrite(ledPin, flasher);
            updateDisplay = true;
#if DEBUG_ON            
            // digitalClockDisplay();
#endif            
            matrix.setClip(H0e+1,H0e+2,0,8);
            matrix.drawPixel(H0e+1,2,flasher);
            matrix.drawPixel(H0e+1,5,flasher);
          }
          updateDisplay |= zoneClockH0.Animate(false);
          updateDisplay |= zoneClockH1.Animate(false);
          updateDisplay |= zoneClockM0.Animate(false);
          updateDisplay |= zoneClockM1.Animate(false);

          if ( DataDisplayTask.check(FullInfoDelay) ) {
            switch (DataMode){
              case 0:
                 sprintf (DataStr, "%s%02d", monthShortStr(month()), day());
                 textEffect = _SCROLL_LEFT;
                 break;
              case 1:
                 sprintf (DataStr, "%s%02d", dayShortStr(weekday()), day());
                 textEffect = _SCROLL_LEFT;
                 break;
              case 99: 
                 sprintf (DataStr, "%s", monthStr(month()));
                 textEffect = _SCROLL_LEFT;
                 break;
              case 2: 
                 // sprintf (DataStr, "%c%d%c", 160, (int)(mySensor.readTempC()+0.5), 161);
                 tempValue = round(mySensor.readTempC());
                 sprintf (DataStr, "%d%c", (int)(tempValue), 161);                  
                 textEffect = tempValue > round(averageTemp) ? _SCROLL_UP : tempValue < round(averageTemp) ? _SCROLL_DOWN:_SCROLL_LEFT;
                 break;
              case 3: 
                 //sprintf (DataStr, "%c%.0f%c", 162, mySensor.readFloatPressure()/100, 163);

                 presValue = round(mySensor.readFloatPressure()/100);
                 sprintf (DataStr, "%.0f%c", presValue, 163);        
                 textEffect = presValue > round(averagePres) ? _SCROLL_UP : presValue < round(averagePres) ? _SCROLL_DOWN:_SCROLL_LEFT;
                 //textEffect = _SCROLL_LEFT;
                 break;                   
              case 4: 
                 //sprintf (DataStr, "%c%d%%", 166, (int)(mySensor.readFloatHumidity()+0.5));
                 humiValue = round(mySensor.readFloatHumidity());
                 sprintf (DataStr, "%d%%", (int)(humiValue));

//                 PRINT("Day:", dayNumber);
//                 PRINT("  Srednia:", round(averageHumi));
//                 PRINT("  Sensor: ", humiValue)
//                 PRINTLN;

                 textEffect = humiValue > round(averageHumi) ? _SCROLL_UP : humiValue < round(averageHumi) ? _SCROLL_DOWN:_SCROLL_LEFT;
                 //textEffect = _SCROLL_LEFT;
                 break;                   
              case 6: 
                 int measurement = hallRead();
                 PRINT("Hall sensor measurement: ", measurement);
                 sprintf (DataStr, "H:%02d", measurement);
                 textEffect = _SCROLL_LEFT;
                 break;                   
            }
            infoTime = textEffect == _SCROLL_LEFT? InfoQuick : InfoSlow;
            zoneInfo1.setText(DataStr, textEffect, _TO_LEFT, infoTime, I1s, I1e);
            DataMode = (DataMode+1) % 5;
          }
          updateDisplay |= zoneInfo1.Animate(false);
          if (keyboard(_Clock_simple_time_init, _Clock_menu_init, _Clock_none, _Clock_none) == SW_UP) {
            DataMode = (DataMode+1) % 5;
            DataDisplayTask.start(-FullInfoDelay);
          }
          break;          
          
//      case _Clock_idle:
//          break;

      default:;
  }

  if (updateDisplay) {
    if (screenSaverNotActive) {
      matrix.write();
      matrix.setIntensity(intensity);
    }
    updateDisplay = false;
  }

}

void processButton() {
   static volatile uint8_t SWPrev = 1;

   uint8_t state =  digitalRead(PIN_BUT);
   uint8_t x;
   if (state == 0) {
      if (SWPrev == 1) {
        SWPrev = 0;
        x = SW_DOWN;
        //Q.push((uint8_t *)&x);
        xQueueSendToBackFromISR( xQueue, (uint8_t *)&x, NULL );
        PRINTS("\nDown\n");
      } 
   } else { 
      if (SWPrev == 0) {
        SWPrev = 1;
        x = SW_UP;
        //Q.push((uint8_t *)&x);
        xQueueSendToBackFromISR( xQueue, (uint8_t *)&x, NULL );
        PRINTS("\nUp\n");
      } 
   }
}

void processEncoder () {
  uint8_t x = R.read();
  if (x) {
    PRINTS((x == DIR_CW ? "\n+1\n" : "\n-1\n"));
    //Q.push(&x);
    xQueueSendToBackFromISR( xQueue, (uint8_t *)&x, NULL );
  }
}


void taskMQTT( void * parameter ) {

  const TickType_t xTicksToWait = pdMS_TO_TICKS(Time2UpdateMQTT);

  UBaseType_t uxHighWaterMark;

  while (true) {
    uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
    // Serial.print("\nStack IN:"); Serial.println(uxHighWaterMark);
    vTaskDelay( xTicksToWait );
    PRINTS("MQTT publishing\n");
    if ( MQTT_connect() ) {
      tempMQTT.publish( mySensor.readTempC() );
      humiMQTT.publish( mySensor.readFloatHumidity() );
      presMQTT.publish( mySensor.readFloatPressure()/100 );
      brigMQTT.publish( lightMeter.readLightLevel() );
      dataMQTT.publish( lastTime.c_str() );
      onofMQTT.publish( screenSaverNotActive?1:0 );
    }
    uxHighWaterMark = uxTaskGetStackHighWaterMark( NULL );
    // Serial.print("\nStack OUT:"); Serial.println(uxHighWaterMark);
  }
}

// Function to connect and reconnect as necessary to the MQTT server.
// Should be called in the loop function and it will take care if connecting.
boolean MQTT_connect() {
  int8_t ret;

  // Stop if already connected.
  if (mqtt.connected()) {
    return true;
  }

  if (!WiFi.isConnected()) {
    PRINTS("Connecting to WIFI... ");
    WiFi.begin();
    delay(1000);
  }
  
  PRINTS("Connecting to MQTT... ");
  uint8_t retries = 3;
  while ( (ret = mqtt.connect()) != 0 && (retries > 0) ) { // connect will return 0 for connected
       PRINTS(mqtt.connectErrorString(ret));PRINTLN;
       PRINTS("Retrying MQTT connection in 5 seconds...\n");
       mqtt.disconnect();
       delay(5000);  // wait 5 seconds
       retries--;
  }
  if (ret == 0) {
    PRINTS("MQTT Connected!\n");
    return true;
  }
  else return false;
}

String digitalClockString() {
  // digital clock display of the time
  return String(hour())+printDigits(minute())+printDigits(second())+" "+String(day())+"."+String(month())+"."+String(year())+"\n";
}

String printDigits(int digits) {
  // utility function for digital clock display: prints preceding colon and leading 0
  return String(":" + (digits < 10 ? String("0") : String("")) + String(digits));
}