#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include <Preferences.h> //Сохранение настроек хеша прошивки
#include <HTTPClient.h>
#include <ESP32httpUpdate.h> //Библиотека ОТА обновлений
#include "M5Atom.h"          //Библиотека атома для функции Led и Кнопки, можно упразднить и убрать
#include <WiFiUdp.h>         //Udp клиент
#include <NTPClient.h>       //NTP запрос времени
#include <TimeLib.h>         //Внутреннее время
#include <Wire.h>            //Библиотека дял I2C
#include <PubSubClient.h>    //Mtqq
#include <ArduinoJson.h>     //Упакова в JSon - удобная библиотека
#include <ESPmDNS.h>
#include "esp32-hal-ledc.h"

#define Fpin 22
#define Bpin 19
#define Resist 33

#define TRIGGER_PIN 39
unsigned int VersionSW = 4;

const int freq = 5000;
const int ledChannel1 = 0;
const int ledChannel2 = 1;
const int resolution = 8;

//3 - Первый код для анемостата

WiFiManager wm;             // обьект менеджера
Preferences OTApreferences; //Обьект хранения настроек хеша прошивки

String JsonData;
StaticJsonDocument<200> doc, docResult;

WiFiClient espClient;
PubSubClient MqttClient(espClient);
IPAddress IpMqtt;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "192.168.89.210", 0, 20000); //Собственно сервер времени смещение и частоат запроса, но он вручную
const PROGMEM char *willmess = "{\"conn\":\"err\"}";
const char *mqttHostName = "cctv.automation.art"; //Хостнейм брокера metrics.local   cctv.automation.art  //192.168.89.210
unsigned int mqttPort = 8889;                     //Порт брокера 1883 8889

String getMacAddress();
String macc = getMacAddress();
String topicTemp = "aastudio/act/" + macc + "/data/0";
String setTopic = "aastudio/act/" + macc + "/set/0";
String willTopicTemp = "aastudio/act/" + macc + "/status";
const PROGMEM char *mqttLogin = "AA_Lab", *mqttPass = "automation.art$";

byte setPercent = 0, setSpeedAnemo = 0;
int stateResistor = 0;

const char *mqttIPHost;                                                  //тут хранится IP хоста по хостнейму
unsigned long timingUpdate = 0, timingReqSensor = 0, timingSendMqtt = 0; //Таймеры для millis()
int PROGMEM nextM5Update = 450000, nextMqttSend = 10000;                 //каждые 7.5 минут запрос обновления с сервера

bool mqttSendFlag = false;
int reqCounter = 0;
long rssi = 0, minRes = 1450, maxRes = 2600;
char bufTopic[140], bufWillTopic[150], bufSetTopic[150];

int moveAnemo(byte speed, byte percent);
int setMotor(bool Finput, int Binput);
void callback(char *topic, byte *payload, unsigned int length);
int disconnectMQTT();

void ledset(byte color = 0)
{
  M5.dis.setBrightness(60); //Половина яркости
  switch (color)
  {
  case 2:
    M5.dis.drawpix(0, 0xf00000); //Зеленый
    break;
  case 1:
    M5.dis.drawpix(0, 0x00ff00); //Красный
    break;
  case 3:
    M5.dis.drawpix(0, 0x0000ff); //Синий
    break;
  case 4:
    M5.dis.drawpix(0, 0xffffff); //Белый
    break;
  case 5:
    M5.dis.drawpix(0, 0xffec00); //желтый
    break;

  default:
    M5.dis.clear();
    break;
  }
}

//Настройки

int getIpBlock(int index, String str)
{
  char separator = '.';
  int found = 0;
  int strIndex[] = {0, -1};
  int maxIndex = str.length() - 1;

  for (int i = 0; i <= maxIndex && found <= index; i++)
  {
    if (str.charAt(i) == separator || i == maxIndex)
    {
      found++;
      strIndex[0] = strIndex[1] + 1;
      strIndex[1] = (i == maxIndex) ? i + 1 : i;
    }
  }

  return found > index ? str.substring(strIndex[0], strIndex[1]).toInt() : 0;
}

IPAddress str2IP(String str)
{
  IPAddress ret(getIpBlock(0, str), getIpBlock(1, str), getIpBlock(2, str), getIpBlock(3, str));
  return ret;
}

int setMqttServer(const char *mqttHostNameF, unsigned int mqttPortF)
{
  mdns_init();

  IPAddress ipaddr;
  OTApreferences.begin("ota-config");

  OTApreferences.putString("mqttip", "0");
  delay(1000);
  String keyIpMqtt = OTApreferences.getString("mqttip", "0");
  Serial.print("keyIpMqtt: ");
  Serial.println(keyIpMqtt);

  if (keyIpMqtt == "0")
  {
    //Запрос по хостнейму .local
    ipaddr = MDNS.queryHost(mqttHostNameF, 4000);
    //Таймаут 4 секунды
    //Если не получили по хостнейму МДНС
    Serial.print("mdns: ");
    Serial.println(ipaddr);

    if (ipaddr.toString() == "0.0.0.0")
    {
      //Запрос к ДНС по адресу сайта
      int err = WiFi.hostByName(mqttHostNameF, ipaddr);
      ////real internal timeout in lwip library is 14[s]
      //Если получили по хостнейму
      if (err == 1)
      {
        Serial.print("hostByName: ");
        Serial.println(ipaddr);
        MqttClient.setServer(ipaddr, mqttPortF);
        OTApreferences.putString("mqttip", ipaddr.toString());
      }
      else
      {
        Serial.print("All method not FOUND IP");
      }
    }
    else
    { //Если все хорошо
      OTApreferences.putString("mqttip", ipaddr.toString());
      MqttClient.setServer(ipaddr, mqttPortF);
    }
  }
  else
  {
    MqttClient.setServer(str2IP(keyIpMqtt), mqttPortF);
  }

  MqttClient.setCallback(callback);
  OTApreferences.end();
  return 0;
}

//Запрос времени NTP и установка локлаьного времени
int reqNtpTime()
{
  if (timeClient.update())
  {
    uint32_t timeEpoch = timeClient.getEpochTime();
    if (timeEpoch < 1600000000)
    {
      Serial.println("Error reqNtpTime() Time");
      return 1;
    }
    setTime(timeEpoch);
    // Serial.print(timeEpoch);
    // Serial.print(" <=ntp==  ==device=> ");
    // Serial.println(now());
  }
  return 0;
}

//{"percent":50,"speed":20}
void callback(char *topic, byte *payload, unsigned int length)
{
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");

  char jsonResult[250];

  for (int i = 0; i < length; i++)
  {
    Serial.print((char)payload[i]);
    jsonResult[i] = (char)payload[i];
  }

  DeserializationError error = deserializeJson(docResult, jsonResult);
  if (error)
  {
    Serial.print(F("deserializeJson() failed: "));
    return;
  }

  setPercent = (byte)docResult["percent"];  //0-100
  setSpeedAnemo = (byte)docResult["speed"]; //speed

  moveAnemo(setSpeedAnemo, setPercent);
}

int nowPercent(int analogStatus)
{

  Serial.print("Percent NOW: ");
  int percentNow = map(analogStatus, minRes, maxRes, 0, 100);
  Serial.print(percentNow);
  Serial.println(" %");

  return percentNow;
}

int nowResist()
{

  Serial.print("NOW: ");
  int analogStatus = analogRead(Resist);
  Serial.print(analogStatus);

  return analogStatus;
}

int moveAnemo(byte speed, byte percent)
{

  Serial.print("Set: ");
  Serial.print(speed);
  Serial.print(" ");
  Serial.println(percent);

  int setResist = map(percent, 0, 100, minRes, maxRes);

  Serial.println(" -------------------- ");

  if (setResist <= stateResistor)
  {
    setMotor(false, true);
    while (setResist <= stateResistor)
    {
      stateResistor = analogRead(Resist);
      Serial.print("Now: ");
      Serial.print(map(stateResistor, minRes, maxRes, 0, 100));
      Serial.println(" %");
      delay(200);
    }
  }
  else
  {
    setMotor(true, false);
    while (setResist >= stateResistor)
    {
      stateResistor = analogRead(Resist);
      Serial.print("Now: ");
      Serial.print(map(stateResistor, minRes, maxRes, 0, 100));
      Serial.println(" %");
      delay(200);
    }
  }

  setMotor(false, false);

  // 0 - 1460
  // 10 - 1535 - 1547
  // 20 - 1656 - 1668
  // 30 - 1777 - 1789
  // 40 - 1898 - 1910
  // 50 - 2019 - 2031
  // 60 - 2140 - 2152
  // 70 - 2261 - 2273
  // 80 - 2382 - 2394
  // 90 - 2503 - 2515
  // 100 -2624 - 2640

  //1420 - закрыто
  //2630 - открыто

  return 0;
}

//Нажатие кнопки для сброса
void checkButton()
{
  Serial.println("Button RESET Pressed");
  disconnectMQTT();
  delay(100);
  OTApreferences.begin("ota-config");
  OTApreferences.putString("mqttip", "0");
  OTApreferences.end();
  wm.resetSettings();
  ESP.restart();
}

//Обновление прошивки, происходит проверка и загрузка
//Делается Get запрос на хостинг проверяется хеш, если хеш
void OTAUpdate()
{
  ////Serial.\println("OTAUpdate()");
  bool flagOTA = false;
  String keyOTA;
  String payload;
  OTApreferences.begin("ota-config");
  if (WiFi.status() == WL_CONNECTED)
  {

    HTTPClient http;
    String serverPath = "http://meteosence.s-host.net/airqa/airquality.php?meteopas=sfddsg38r327kjsdgf8932r&mac = " + macc + " &meteodata = gethash";
    http.begin(serverPath.c_str());
    int httpResponseCode = http.GET();

    if (httpResponseCode > 0)
    {
      Serial.print("HTTP Response code: ");
      Serial.println(httpResponseCode);
      payload = http.getString();
      Serial.println(payload);

      if (payload != "errno" || payload != "errfi")
      {

        keyOTA = OTApreferences.getString("md5HashOTA");

        if (keyOTA.length() <= 0)
        {
          OTApreferences.putString("md5HashOTA", "undifined");
        }

        keyOTA = OTApreferences.getString("md5HashOTA");

        if (payload != keyOTA)
        {
          flagOTA = true;
          OTApreferences.putString("md5HashOTA", payload);
          //Serial.println("flagOTA = true;");
        }
      }
    }
    else
    {

      Serial.print("Error code HTTP: ");
      Serial.println(httpResponseCode);
    }
    // Free resources
    http.end();
  }
  else
  {

    Serial.println("WiFi Disconnected");
  }

  if (flagOTA == true)
  {
    flagOTA = false;
    //Serial.println("flagOTA = false;");

    t_httpUpdate_return ret = ESPhttpUpdate.update("http://meteosence.s-host.net/airqa/co2/co2.bin");

    //После update ничего не происходит, такая вот особенность.
    //Если все прошло хорошо, перезагрузка на новую прошивку

    //Serial.print("ret ");
    //Serial.println(ret);

    switch (ret)
    {

    case HTTP_UPDATE_FAILED:
      //Serial.printf("HTTP_UPDATE_FAILD Error (%d): %s", ESPhttpUpdate.getLastError(), ESPhttpUpdate.getLastErrorString().c_str());
      //Serial.println(ESPhttpUpdate.getLastError());
      //Serial.println(ESPhttpUpdate.getLastErrorString().c_str());
      //Serial.println("HTTP_UPDATE_FAILD Error");

      ESP.restart();
      break;

    case HTTP_UPDATE_NO_UPDATES:
      //Serial.println("HTTP_UPDATE_NO_UPDATES");

      ESP.restart();
      break;

    case HTTP_UPDATE_OK:
      //Serial.println("HTTP_UPDATE_OK");

      ESP.restart();
      break;
    }
  }
  OTApreferences.end();
}

//Получение мак адреса
String getMacAddress()
{
  uint8_t baseMac[6];
  esp_read_mac(baseMac, ESP_MAC_WIFI_STA);
  char baseMacChr[18] = {0};
  sprintf(baseMacChr, "%02X%02X%02X%02X%02X%02X", baseMac[0], baseMac[1], baseMac[2], baseMac[3], baseMac[4], baseMac[5]);
  return String(baseMacChr);
}

//Отправка данных по MQTT
int SendMqttReq(bool sendVal = true, bool sendStatus = true, byte statusConn = 1)
{
  if (mqttSendFlag == true)
  {

    char resultString[200];
    String JsonData = "";
    rssi = WiFi.RSSI();
    unsigned long timeNow = now();

    doc["analog"] = nowResist();
    doc["percent"] = nowPercent(nowResist());
    doc["co2"] = "";

    doc["ts"] = timeNow;

    serializeJson(doc, JsonData);
    doc.remove("ts");
    doc.remove("analog");
    doc.remove("percent");
    doc.clear();
    doc.garbageCollect();

    Serial.println(JsonData); //Вывод JSON строки в консоль
    JsonData.toCharArray(resultString, JsonData.length() + 1);
    MqttClient.publish(bufTopic, resultString, true);

    if (sendStatus == true)
    {
      const char *conn;
      switch (statusConn)
      {
      case 1:
        conn = "on";
        break;

      case 2:
        conn = "off";
        break;

      case 3:
        conn = "slp";
        break;

      case 4:
        conn = "upd";
        break;

      default:
        break;
      }

      doc["conn"] = conn;
      doc["rssi"] = rssi;
      doc["bsid"] = WiFi.BSSIDstr();
      doc["ts"] = timeNow;
      doc["exp"] = timeNow + 11000;
      doc["sv"] = VersionSW;

      JsonData = "";
      serializeJson(doc, JsonData);
      Serial.println(JsonData); //Вывод JSON строки в консоль

      doc.clear();
      doc.garbageCollect();

      JsonData.toCharArray(resultString, JsonData.length() + 1);
      MqttClient.publish(bufWillTopic, resultString, true);
    }
  }
  return 0;
}

//Переподключение при петери связи с MQTT
//7 раз проверили и вернулись в общий цикл что бы вдруг что втянуть обновления
void reconnectMqtt()
{
  byte circle = 0;
  while (!MqttClient.connected())
  {
    circle++;
    if (circle == 5)
    {
      break;
    }

    String maccrandom = macc + String(random(0xffff), HEX);
    const char *clientId = maccrandom.c_str();

    if (MqttClient.connect(clientId, mqttLogin, mqttPass, bufWillTopic, 2, true, willmess))
    {
      Serial.println("Mqttconnect - OK");
      mqttSendFlag = true;
      MqttClient.subscribe(bufSetTopic);
      SendMqttReq(false, true, 1);
    }
    else
    {
      Serial.print("Mqtt.state() = ");
      Serial.println(MqttClient.state());
      /*              
            -4 : MQTT_CONNECTION_TIMEOUT - the server didn't respond within the keepalive time
            -3 : MQTT_CONNECTION_LOST - the network connection was broken
            -2 : MQTT_CONNECT_FAILED - the network connection failed
            -1 : MQTT_DISCONNECTED - the client is disconnected cleanly
            0 : MQTT_CONNECTED - the client is connected
            1 : MQTT_CONNECT_BAD_PROTOCOL - the server doesn't support the requested version of MQTT
            2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier
            3 : MQTT_CONNECT_UNAVAILABLE - the server was unable to accept the connection
            4 : MQTT_CONNECT_BAD_CREDENTIALS - the username/password were rejected
            5 : MQTT_CONNECT_UNAUTHORIZED - the client was not authorized to connect
      */
      mqttSendFlag = false;
      delay(500);
    }
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  M5.begin(true, false, true);

  delay(50);
  pinMode(TRIGGER_PIN, INPUT);

  pinMode(Fpin, OUTPUT);
  pinMode(Bpin, OUTPUT);
  ledcSetup(ledChannel1, freq, resolution);
  ledcSetup(ledChannel2, freq, resolution);

  ledcAttachPin(Fpin, ledChannel1);
  ledcAttachPin(Bpin, ledChannel2);


  setMotor(false, false);

  WiFi.mode(WIFI_STA);

  delay(100);

  Serial.println(setTopic);
  Serial.println(topicTemp);
  Serial.println(willTopicTemp);

  setTopic.toCharArray(bufSetTopic, setTopic.length() + 1);
  topicTemp.toCharArray(bufTopic, topicTemp.length() + 1);
  willTopicTemp.toCharArray(bufWillTopic, willTopicTemp.length() + 1);

  // wm.resetSettings(); // wipe settings

  std::vector<const char *> menu = {"wifi", "info", "param", "sep", "restart", "exit"};
  wm.setMenu(menu);
  // set dark theme
  wm.setClass("invert");
  //set static ip
  // wm.setSTAStaticIPConfig(IPAddress(10,0,1,99), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); // set static ip,gw,sn
  // wm.setShowStaticFields(true); // force show static ip fields
  // wm.setShowDnsFields(true);    // force show dns field always
  // wm.setConnectTimeout(20); // how long to try to connect for before continuing
  wm.setConfigPortalTimeout(30);  // auto close configportal after n seconds
  wm.setMinimumSignalQuality(10); // set min RSSI (percentage) to show in scans, null = 8%
  wm.setScanDispPerc(true);       // show RSSI as percentage not graph icons
  wm.setWiFiAutoReconnect(true);  // if true, enable autoreconnecting

  bool res;

  res = wm.autoConnect("AnemostatM5Portal", "12345678"); // Подключение к анонимной точке доступа
  if (!res)
  {
    Serial.println("Failed to connect or hit timeout");
    ESP.restart();
  }

  if (reqNtpTime() == 1)
  {
    reqNtpTime();
  }

  setMqttServer(mqttHostName, mqttPort);
  nowPercent(nowResist());
}

void loop()
{
  M5.update();

  if (WiFi.status() != WL_CONNECTED)
  {
    Serial.println(WiFi.status());
    ESP.restart();
  }

  if (!MqttClient.connected())
  {
    reconnectMqtt();
  }

  stateResistor = analogRead(Resist);

  // if (millis() - timingSendMqtt > nextMqttSend)
  // {
  //   SendMqttReq();
  //   timingSendMqtt = millis();
  // }

  // 40 секунд и происходит сброс настроек WIFI
  if (M5.Btn.wasReleasefor(40000))
  {
    checkButton();
  }

  //По таймеру запруск обновления прошивки
  if (millis() - timingUpdate > nextM5Update)
  {
    reqNtpTime();
    SendMqttReq(false, true, 4);
    OTAUpdate();
    SendMqttReq(false, true, 1);
    timingUpdate = millis();
  }

  //Таймер отправки данных в брокер
  MqttClient.loop();
}

int disconnectMQTT()
{
  MqttClient.disconnect();
  return 0;
}

int setMotor(bool Finput, int Binput)
{

  if (Finput == true && Binput == false)
  {

    // digitalWrite(Fpin, HIGH);

    ledcWrite(ledChannel1, 200);
    digitalWrite(Bpin, LOW);
  }

  if (Finput == false && Binput == true)
  {
    digitalWrite(Fpin, LOW);

    ledcWrite(ledChannel2, 200);
    //  digitalWrite(Bpin, HIGH);
  }

  //stop
  if (Finput == false && Binput == false)
  {
    digitalWrite(Fpin, LOW);
    digitalWrite(Bpin, LOW);
  }

  return 0;
}

//Закрыто
// 1422
// 1418
// 1423
// 1421
// 1423
// 1422
// 1424
// 1424
// 1421
// 1424
// 1424
// 1421
// 1423
// 1424
// 1421
// 1419
// 1421
// 1423
// 1423
// 1424

//Открыто
// 2631
// 2635
// 2631
// 2637
// 2634
// 2635
// 2637
// 2635
// 2635
// 2634
// 2637
// 2637
// 2635
// 2631
// 2638
