// #Inspired ad:
// * https://www.theelectronics.co.in/2022/03/ntp-client-simulation-using-esp32.html
// * https://github.com/satyamkr80/ESP32-Simulation-Codes
// * https://www.youtube.com/watch?v=bwaIEtLUVKQ
#include <WiFi.h>
#include <TimeLib.h>
#include <Wire.h>
 
#define NTP_SERVER     "pool.ntp.org"
#define UTC_OFFSET     0
#define UTC_OFFSET_DST 0

const int PIN_LEVEL_HIGH = 25;
const int PIN_LEVEL_LOW = 26;
const int PIN_IMPULSE = 27;

const int PIN_LED_HIGH = 23;
const int PIN_LED_LOW = 22;
const int PIN_LED_IMPULSE = 21;


// ========================================================
/*
Popis funcke 
 - po preruseni nacteme aktualni cas + prdame hodnotu highLevel/lowLevel 
   a vse zapiseme do Stringu pro odeslani do db
 - ve smycce kontrolujeme zda jsou nejaka data k odeslani, a pokud ano odesleme je vsechny naraz na server
   pricemz kontrolujeme odpoved - pokud je 200-299 akceptujeme jako ulozene a smazeme pole pro odeslani
   a vynulujeme pocitadlo radku zaznamu
   Nutno vyresit problem s prerusenim - pokud vezmu data a poslu na server, pote je smazu tak v tento maly okamzik 
   pokud by prislo preruseni a pridal by se radek tak by nebyl nikdy odelan a byl by hned smazan

  - mozny navrh prepracovani:
  -- preruseni ulozi pouze millis() do globalni promenne a tim preruseni konci
  -- ve smycce se kontroluze jestli je ulozen nejaky cas, pokud ano teprve nyni se 
     vygeneruje radek pro odeslani dat do DB + inkrementujeme pocitadlo radku/zaznamu
  -- nepredpoklada se preruseni vicekrat jak po cca 500ms (to by byl prikon 14kW!)
  -- ve smycce se nadale zkontroluje zda jsou nejaka data a pripadne odesle
  -- odesilani dat take opodminkujeme na max 1x za minutu
  -- pokud odeslani selze mazani dat nerovedeme a zkusime o minutu pozdeji
  -- pokud je ulozeno vice jak cca 300 radku ktere se nepodarilo odeslat, je nutne data zahodi
  --- pripadne ukladat nejaky souhrnny stav ktery se odesle (ukladat pocet impulzu ktere se neodeslali a kdy bylo posledni uspesne odeslani)

  -- NodeMcu ma 96kB RAM - lze ulozit cca 900 radku o delce 100 znaku
*/
// ========================================================

String dataToInfluxDb = "";
String messagesToSerial = "";
bool impulseInteruptProcessing = false;
unsigned long lastImpulseMilis = 0;

// ========================================================

unsigned long ntpTimestamp = 0;
unsigned long ntpTimestampMilis = 0;

unsigned long loadNtpTimestamp() {
  Serial.println("Loading NTP time . . .");

  
  struct tm timeinfo;

  if (!getLocalTime(&timeinfo)) {
    Serial.println("Cannot load NTP time - repeat after 10s");

    delay(10);
    return loadNtpTimestamp();
  }

  Serial.print("NTP time: ");
  Serial.println(&timeinfo, "%H:%M:%S %d.%m.%Y   %Z");
  

  time_t now;
  time(&now);

  

  ntpTimestamp = (unsigned long) now;
  ntpTimestampMilis = millis();

  Serial.print("NTP timestamp: ");
  Serial.println(now);

  Serial.print("NTP timestamp milis: ");
  Serial.println(ntpTimestampMilis);
  
  return now;
}

unsigned long getCurrentTimestamp(unsigned long curMillis = 0)
{
  if(curMillis == 0) {
    curMillis = millis();
  }

  return ntpTimestamp + (int) ((curMillis - ntpTimestampMilis) / 1000);
}
// ========================================================


 
void setup() {
  Serial.begin(115200);
  Serial.println("");
  Serial.println("Serial startup");

  // WI-FI
  WiFi.begin("Wokwi-GUEST", "", 6);
  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
  }

  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
 
  // NTP time
  configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
  loadNtpTimestamp();

  // Setup buttons
  pinMode(PIN_LEVEL_HIGH, INPUT);
  pinMode(PIN_LEVEL_LOW, INPUT);
  pinMode(PIN_IMPULSE, INPUT);

  pinMode(PIN_LED_HIGH, OUTPUT);
  pinMode(PIN_LED_LOW, OUTPUT);
  pinMode(PIN_LED_IMPULSE, OUTPUT);

  digitalWrite(PIN_LED_HIGH, LOW);
  digitalWrite(PIN_LED_LOW, LOW);
  digitalWrite(PIN_LED_IMPULSE, LOW);

  attachInterrupt(digitalPinToInterrupt(PIN_IMPULSE), interuptImpulse, FALLING);

  Serial.println("Start loop()");
  Serial.println("====================");
}
 
void loop() {

  if(messagesToSerial != "") {
    Serial.println(messagesToSerial);
    messagesToSerial = "";
  }

  digitalWrite(PIN_LED_HIGH, !digitalRead(PIN_LEVEL_HIGH));
  digitalWrite(PIN_LED_LOW, !digitalRead(PIN_LEVEL_LOW));
  digitalWrite(PIN_LED_IMPULSE, !digitalRead(PIN_IMPULSE));

  if(dataToInfluxDb != ""){
    Serial.println("*** Influx DB ***");
    Serial.println(dataToInfluxDb);
    Serial.println("----------");

    dataToInfluxDb = "";
  }


  /*
  delay(1100);

  Serial.print(getCurrentTimestamp());
  Serial.println(
    String(" - Status ")
    + String(" H:") + String(digitalRead(PIN_LEVEL_HIGH) ? 'Y' : 'N')
    + String(" L:") + String(digitalRead(PIN_LEVEL_LOW) ? 'Y' : 'N')
    + String(" I:") + String(digitalRead(PIN_IMPULSE) ? 'Y' : 'N')
    );
    */

}


void interuptImpulse() {

  if(impulseInteruptProcessing) {
    // Already processing
    return;
  }

  impulseInteruptProcessing = true;

  unsigned long curMillis = millis();
  unsigned long curTime = getCurrentTimestamp(curMillis);
  

  if(lastImpulseMilis == 0) {
    // Frst impulse only store
    lastImpulseMilis = curMillis;

    messagesToSerial += String(curTime) + String(" - First impulse\n");
  } else {
    messagesToSerial += String(curTime) + String(" - Next impulse\n");
    unsigned long impulseDiff = curMillis - lastImpulseMilis;
    
    if(impulseDiff < 100) {
      // Lower then 10ms - fake - ignore
      impulseInteruptProcessing = false;
      return; 
    } else {
      lastImpulseMilis = curMillis;
    }

    int power = (int) (7200 / (impulseDiff / 1000));
    bool levelHigh = !digitalRead(PIN_LEVEL_HIGH);
    bool levelLow = !digitalRead(PIN_LEVEL_LOW);

    messagesToSerial +=  String(curTime) 
      + String(" - Impulse: ")
      + String("milis: ")
      + String(curMillis) 
      + String(" / diff: ") 
      + String(impulseDiff) 
      + String("ms / power: ")
      + String(power)
      + String("W \n");
/*
    dataToInfluxDb += String("wattmetter power=") 
    + String(power)
    + String(",levelHigh=")
    + String(levelHigh?"1":0)
    + String(",levelLow=")
    + String(levelLow?"1":0) 
    + String(" ") 
    + String(curTime)
    + String(".")
    + String(curMillis % 1000)
    + String("\n");)
*/

    impulseInteruptProcessing = false;
  }
}