#define DEBUGLOG_DEFAULT_LOG_LEVEL_DEBUG

#include <DebugLog.h>
#include <DebugLogEnable.h>

#include <Arduino.h>
#include <PubSubClient.h>

#include <config.h>
#include <PCF85063_Driver.h>
#include <R4DCB08_Driver.h>
#include <S50140_Driver.h>
#include <BUZZER_Driver.h>
#include <LED_Driver.h>
#include <ETH_Driver.h>

#include <digitalIO.h>

#include "utils.h"

/////////////// GLOBALS ///////////////
Config& conf = Config::getInstance();
/////////////// GLOBALS ///////////////

void callback(char *topic, uint8_t *payload, unsigned int length)
{
  std::string pl;
  pl.resize(length);
  std::snprintf(pl.data(), length, "%s", payload);
  LOG_INFO("Message: Topic [", topic, "], Payload [", pl.c_str(), "]");
}

void myTask(void *mqtt)
{
  auto client = (PubSubClient *)(mqtt);
  while (client->connected())
  {
    client->loop();
    vTaskDelay(pdMS_TO_TICKS(100));
  }
  LOG_ERROR("Mqtt Loop Ended, client disconnected");
  vTaskDelete(NULL); // delete the current task
};

void setup()
{
  Serial.begin(9600);
  LOG_ATTACH_SERIAL(Serial);
  conf.init();
}

void loop()
{
  const uint8_t baseRegister(0x00);
  uint16_t k(0);
  uint8_t sensors(0);
  bool buzzing(false);

  //////////////// DEVICES ////////////////
  // Declared here to keep devices local to the main loop otherwise the kernel crashes //
  auto i2c = drivers::I2C();
  auto bus = drivers::MODBUS(9600, SERIAL_8N1);
  auto rtc = drivers::PCF85063(i2c, PCF85063_ADDRESS);
  auto eth = drivers::Ethernet(conf.m_ethHostname);
  auto tmp = drivers::R4DCB08(bus, conf.m_modbusTemperatureAddr);
  delay(100);
  auto io = digitalIO(i2c, bus, {conf.m_modbusRelayAddr});
  delay(100);
  auto seneca = drivers::S50140(bus, conf.m_modbusSenecaAddr);
  auto buzzer = drivers::Buzzer();
  auto led = drivers::Led();
  //////////////// DEVICES ////////////////
  // Initialize temperature sensors
  sensors = tmp.getNum();
  LOG_INFO("Temperature sensors connected ->", sensors);

  //////////////// NETWORK ////////////////
  // MQTT Test //
  NetworkClient tcp;
  PubSubClient mqtt(tcp);
  mqtt.setServer(conf.m_mqttHost.c_str(), conf.m_mqttPort);
  mqtt.setCallback(callback);
  //////////////// NETWORK ////////////////
  
    //////////////// NETWORK ////////////////
    /////////////// CALLBACK ////////////////
  Network.onEvent(
      [&eth, &rtc, &mqtt, &buzzer, &led](arduino_event_id_t event, arduino_event_info_t info) -> void
      {
        eth.onEvent(event, info); // Arduino Ethernet event handler
        if (!eth.isConnected())
          return;
        // Get RTC time at ethernet connection
        time_t ntpTime;
        uint8_t timeRetries(0);
        uint8_t mqttRetries(0);
        while (timeRetries++ < conf.m_ntpRetries)
        {
          if (eth.getNtpTime(ntpTime) && rtc.setDatetime(drivers::PCF85063::fromEpoch(ntpTime)))
          {
            buzzer.beep(250, NOTE_F);
            led.setColor({255, 255, 0});
            const drivers::PCF85063::datetime_t dt(drivers::PCF85063::fromEpoch(ntpTime));
            LOG_INFO("NTP Time: ", drivers::PCF85063::datetime2str(dt).c_str());
            delay(100);
          }
          break;
        }
        while (mqttRetries++ < conf.m_mqttRetries)
        {
          if (!mqtt.connected() && mqtt.connect(conf.m_mqttClientName.c_str()))
          {
            mqtt.subscribe("test/esp32-in");
            xTaskCreatePinnedToCore(myTask, "mqttLoop", 4096, &mqtt, 2, NULL, 1);
            break;
          }
          delay(100);
        }
      });

  ////////////////////////////////////////
  ///////// MAIN LOOP INSIDE LOOP ////////
  ////////////////////////////////////////

  while (true)
  {
    LOG_INFO("[", k++, "] Loop");

    const std::string timeStr(rtc.getTimeStr());
    LOG_INFO("Current Datetime", timeStr.c_str());
    mqtt.publish("test/esp32-out", ("[" + std::to_string(k) + "] -> " + timeStr).c_str());

    uint8_t i(0);
    for (auto v : tmp.getTempAll())
    {
      LOG_INFO("Temperature channel", i++, "->", v);
    }
    i = 0;
    delay(10);
    for (auto v : tmp.getCorrection())
    {
      LOG_INFO("Temperature correction channel", i++, "tc", v);
    }

    delay(100);
    drivers::S50140::powerinfo_t pinfo = seneca.getAll();
    LOG_INFO("Power Info ==> V:", pinfo.v, "- A:", pinfo.a, "- W:", pinfo.pAct, "- F:", pinfo.f, "- Wh_t:", pinfo.whTot, "- Wh_p:", pinfo.whPar);

    if (io.digitalIORead(0)) // rosso
    {
      uint8_t regset(seneca.getRegset());
      uint16_t countStat(seneca.getCounterStatus());
      LOG_INFO("Register Set: ", regset);
      LOG_INFO("Counter Status: ", countStat);
      seneca.resetPartialCounters();
    }
    delay(100);
    if (io.digitalIORead(8)) // blu
    {
      if (!buzzing)
      {
        buzzing = true;
        buzzer.beepRepeat(100, 1000, NOTE_C);
        led.blinkColor(100, 500, {255, 0, 255});
      }
      else
      {
        buzzer.beepStop();
        led.blinkAlternate(500, 500, {255, 255, 0}, {0, 255, 255});
        buzzing = false;
      }
      LOG_INFO("Buzzing -> ", buzzing ? "True" : "False");
    }

    if(io.digitalIORead(9)) { // verde
      conf.resetConfig();
    }

    if(io.digitalIORead(10)) { // giallo 
      esp_restart();
    }

    delay(conf.m_globalLoopDelay);
  }

  ////////////////////////////////////////
  ///////// MAIN LOOP INSIDE LOOP ////////
  ////////////////////////////////////////
}