//========== Preprocesor ==========

//---- Required ----
#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
//------------------
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

#include "Tasker.h"
#include "OneWire.h"
#include "DallasTemperature.h"

#include "config.h"

//========== Create objects ==========

AsyncWebServer server(80);
Tasker tasker;
OneWire oneWire(DS18B20_PIN);
DallasTemperature sensor(&oneWire);

//========== Init variables ==========

float avgTemperature;
String metrics;
bool heating;
float reqTemp = 21.5;
float offset = 0.25;
float correction[6] = {CORRECTION1, CORRECTION2, CORRECTION3, CORRECTION4, CORRECTION5, CORRECTION6};

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


void setup() {
  //========== Wi-Fi setup ==========

  WiFi.mode(WIFI_STA);
  WiFi.begin(STASSID, STAPSK);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    //Connection Failed! Rebooting...
    delay(5000);
    ESP.restart();
  }

  //========== OTA setup ==========

  // Port defaults to 8266
  // ArduinoOTA.setPort(8266);

  // Hostname defaults to esp8266-[ChipID]
  // ArduinoOTA.setHostname("myesp8266");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");
  ArduinoOTA.begin();
  SPIFFS.begin();
  //========== Pin setup ==========

  pinMode(RELAY_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  //========== Sensor setup ==========

  sensor.begin();
  // do not block during temperature conversion
  sensor.setWaitForConversion(false);

  //========== Tasks init ==========

  startConversion(); // First temp read
  tasker.setInterval(startConversion, 15000); // read temperature every 15 seconds
  tasker.setInterval(otaHandle, 1000);

  if (THERMOSTAT) {
    tasker.setInterval(thermostatCheck, 1000);
  }

  //========== Web server setup ==========

  if (METRICS_EXPORT || API) {
    if (METRICS_EXPORT) {
      server.on("/metrics", HTTP_GET, [](AsyncWebServerRequest * request) {
        tasker.setTimeout(ledBlink, 10);
        request->send(200, "text/plain; charset=utf-8", metrics);
      });
    }
    if (API) {
      server.on("/set", HTTP_GET, [](AsyncWebServerRequest * request) {
        //nutno nastavit maxima a minima

        String repply;

        if (THERMOSTAT) {
          if (request->hasParam("increasereqtemp")) {
            reqTemp += request->getParam("increasereqtemp")->value().toFloat();
            repply = String(reqTemp);
          } else if (request->hasParam("decreasereqtemp")) {
            reqTemp -= request->getParam("decreasereqtemp")->value().toFloat();
            repply = String(reqTemp);
          } else if (request->hasParam("increaseoffset")) {
            offset += request->getParam("increaseoffset")->value().toFloat();
            repply = String(offset);
          } else if (request->hasParam("decreaseoffset")) {
            offset -= request->getParam("decreaseoffset")->value().toFloat();
            repply = String(offset);
          } else {
            repply = "unknown parameter";
          }
        } else {
          if (request->hasParam("relay")) {
            boolean state = request->getParam("relay")->value() == "0";
            digitalWrite(RELAY_PIN, state);
            repply = "success";
          } else {
            repply = "unknown parameter";
          }
        }

        request->send(200, "text/plain; charset=utf-8", repply);
      });
      server.on("/get", HTTP_GET, [](AsyncWebServerRequest * request) {
        //nutno vyřešit dotazy na více parametrů najednou
        String repply;

        if (request->hasParam("temp")) {
          repply = String(avgTemperature);
        } else if (request->hasParam("relay")) {
          repply = String(heating);
        } else if (THERMOSTAT && request->hasParam("reqtemp")) {
          repply = String(reqTemp);
        } else if (THERMOSTAT && request->hasParam("offset")) {
          repply = String(offset);
        } else {
          repply = "unknown parameter";
        }

        request->send(200, "text/plain; charset=utf-8", repply);
      });
    }
    if (API && WEBAPP) {
      server.serveStatic("/", SPIFFS, "/").setDefaultFile("index.html");
    }

    server.onNotFound(notFound);
    server.begin();
  }
  //=================================
}

void loop() {
  tasker.loop();
}

void otaHandle() {
  ArduinoOTA.handle();
}

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void thermostatCheck() {
  if (avgTemperature <= (reqTemp - offset)) {
    heating = 1;
    digitalWrite(RELAY_PIN, !heating);
  } else if (avgTemperature >= (reqTemp + offset)) {
    heating = 0;
    digitalWrite(RELAY_PIN, !heating);
  }
}

void readSensor() {
  uint8_t ds18Count = sensor.getDS18Count();
  float tempC[ds18Count];
  float temperature[ds18Count];
  int savedTemperatures = 0;
  avgTemperature = 0;

  for (int i = 0; i < ds18Count; i++) {
    tempC[i] = sensor.getTempCByIndex(i);
    if (tempC[i] != DEVICE_DISCONNECTED_C) {
      temperature[i] = tempC[i] + correction[i];
      avgTemperature += temperature[i];
      savedTemperatures++;
    }
  }

  avgTemperature = avgTemperature / savedTemperatures;

  metrics = getMetricsVar(ds18Count, temperature, tempC);

}

String getMetricsVar(uint8_t ds18Count, float temperature[], float tempC[]) {
  metrics = "temp{device=\"";
  for (uint8_t i = 0; i < ds18Count; i++) {
    if (i != 0) {
      metrics += "\ntemp{device=\"";
    }
    metrics += i;
    metrics += "\"} ";
    metrics += temperature[i];
  }
  for (uint8_t i = 0; i < ds18Count; i++) {
    metrics += "\nraw_temp{device=\"";
    metrics += i;
    metrics += "\"} ";
    metrics += tempC[i];
  }
  if (THERMOSTAT) {
    metrics += "\nheating ";
    metrics += heating;
    metrics += "\nreqTemp ";
    metrics += reqTemp;
    metrics += "\noffset ";
    metrics += offset;
  }

  return metrics;
}

void startConversion() {
  // start temperature conversion (does not block)
  sensor.requestTemperatures();
  // schedule reading the actual temperature in 750 milliseconds
  tasker.setTimeout(readSensor, 750);
}
void ledBlink() {
  digitalWrite(LED_PIN, LOW);
  delay(200);
  digitalWrite(LED_PIN, HIGH);
}
