#include <main.hpp>

RemoteVariable pm25_in = RemoteVariable();
RemoteVariable pm25_out = RemoteVariable();
RemoteVariable temp_out = RemoteVariable();
RemoteVariable weather = RemoteVariable();
RemoteVariable pm_switch = RemoteVariable();
RemoteVariable pm_fan_speed = RemoteVariable();

const char *mode_names[] = {"off", "cool", "fan_only", "dry"};
const char *fan_speed_names[] = {"auto", "high", "medium", "low"};
uint8_t row = 4;
uint8_t column = 2;
const uint8_t light_array[4][2] = {
    {LIGHT_ROW1_COLUMN1, LIGHT_ROW1_COLUMN2},
    {LIGHT_ROW2_COLUMN1, LIGHT_ROW2_COLUMN2},
    {LIGHT_ROW3_COLUMN1, LIGHT_ROW3_COLUMN2},
    {LIGHT_ROW4_COLUMN1, LIGHT_ROW4_COLUMN2}};


AirConditioner ac = {
    .max_temperature = 32,
    .min_temperature = 16,
    .modes = 4,
    .mode_names = mode_names,
    .fan_speeds = 4,
    .fan_speed_names = fan_speed_names,
    .getInfraredCode = &getInfraredCode};
/***********************************************
 *            End Configuration                *
 ***********************************************/

ESPMegaPRO espmega = ESPMegaPRO();
ISEDisplay iseDisplay = ISEDisplay(&iseDisplayAdapter, &light_array[0][0], row, column);

ClimateCard climateCard = ClimateCard(AIR_CONDITIONER_IR_PIN, ac,
                                      AIR_CONDITIONER_SENSOR_TYPE, AIR_CONDITIONER_SENSOR_PIN,
                                      AIR_CONDITIONER_RMT_CHANNEL);

void handleMqttMessage(char *topic, char *payload)
{
}

void sendStopBytes()
{
  Serial.write(0xFF);
  Serial.write(0xFF);
  Serial.write(0xFF);
}

void sendExtStopBytes()
{
  iseDisplayAdapter.write(0xFF);
  iseDisplayAdapter.write(0xFF);
  iseDisplayAdapter.write(0xFF);
}

void setup()
{
  gpio_config_t gpio_2_conf;
  gpio_2_conf.intr_type = GPIO_INTR_DISABLE;
  gpio_2_conf.mode = GPIO_MODE_INPUT;
  gpio_2_conf.pull_up_en = GPIO_PULLUP_ENABLE;
  gpio_config(&gpio_2_conf);
  // If GPIO 2 is pulled low, clear the FRAM then reboot (Reset the device to factory defaults)
  bool clear_fram = !gpio_get_level(GPIO_NUM_2);
  Serial.begin(115200);
  iseDisplayAdapter.begin(ISE_DISPLAY_BAUD_RATE, SERIAL_8N1, ISE_DISPLAY_RX_PIN, ISE_DISPLAY_TX_PIN);
  sendExtStopBytes();
  iseDisplayAdapter.print("rest");
  sendExtStopBytes();
  sendStopBytes();
  Serial.print("rest");
  sendStopBytes();
  Serial.print("boot_state.txt=\"Core Initializing . . .\"");
  sendStopBytes();
  espmega.begin();
  // if (clear_fram)
  // {
  //   Serial.print("boot_state.txt=\"Factory Resetting . . .\"");
  //   sendStopBytes();
  //   for (uint16_t i = 0; i < 32768; i++)
  //   {
  //     espmega.fram.write8(i, 0);
  //   }
  //   esp_restart();
  // }
  Serial.print("boot_state.txt=\"IoT Initializing . . .\"");
  sendStopBytes();
  espmega.enableIotModule();
  ETH.begin();
  espmega.iot->bindEthernetInterface(&ETH);
  Serial.print("boot_state.txt=\"Network Initializing . . .\"");
  sendStopBytes();
  espmega.iot->loadNetworkConfig();
  espmega.iot->connectNetwork();
  Serial.print("boot_state.txt=\"MQTT Initializing . . .\"");
  sendStopBytes();
  espmega.iot->loadMqttConfig();
  espmega.iot->connectToMqtt();
  Serial.print("boot_state.txt=\"Display Initializing . . .\"");
  espmega.enableInternalDisplay(&Serial);
  espmega.enableWebServer(80);
  espmega.inputs.registerCallback(on_pin_change);
  espmega.outputs.setAutoSaveToFRAM(true);
  // Set value of pin 0-12 to 4095
  for (uint8_t i = 0; i < 13; i++)
  {
    espmega.outputs.setValue(i, 4095);
  }
  espmega.installCard(2, &climateCard);
  climateCard.bindFRAM(&espmega.fram, 5000);
  climateCard.loadStateFromFRAM();
  climateCard.setFRAMAutoSave(true);
  espmega.display->bindClimateCard(&climateCard);
  // auto binded_display_update_on_pm25_out = std::bind(&display_update,0,std::placeholders::_1);
  // auto binded_display_update_on_pm25_in = std::bind(&display_update,1,std::placeholders::_1);
  // auto binded_display_update_on_temp_out = std::bind(&display_update,2,std::placeholders::_1);
  // auto binded_display_update_on_weather = std::bind(&display_update,3,std::placeholders::_1);
  // auto binded_display_update_on_pm_switch = std::bind(&display_update,4,std::placeholders::_1);
  // auto binded_display_update_on_pm_fan_speed = std::bind(&display_update,5,std::placeholders::_1);

  // pm25_out.registerCallback(std::function<void (char*)>(binded_display_update_on_pm25_out));
  // pm25_in.registerCallback(std::function<void (char*)>(binded_display_update_on_pm25_in));
  // temp_out.registerCallback(std::function<void (char*)>(binded_display_update_on_temp_out));
  // weather.registerCallback(std::function<void (char*)>(binded_display_update_on_weather));
  // pm_switch.registerCallback(std::function<void (char*)>(binded_display_update_on_pm_switch));
  // pm_fan_speed.registerCallback(std::function<void (char*)>(binded_display_update_on_pm_fan_speed));

  pm25_out.registerCallback(&pm25outupdatedisplay);
  pm25_in.registerCallback(&pm25inupdatedisplay);
  temp_out.registerCallback(&tempoutupdatedisplay);
  weather.registerCallback(&weatherupdatedisplay);
  pm_switch.registerCallback(&pmswitchupdatedisplay);
  pm_fan_speed.registerCallback(&pmfanspeedupdatedisplay);



  // placeholder
  // PM2.5 PPM Remote Variable
  // 12 bytes remote variable, 11 characters + null terminator
  // Enable value request at /iqair/pm25_request
  pm25_out.begin(6, "/aqi/value", espmega.iot, true, "/aqi/request_value");
  // Temperature Remote Variable
  temp_out.begin(6, "/temp/value", espmega.iot, true, "/temp/request_value");
  // Weather Remote Variable
  weather.begin(45, "/weather/value", espmega.iot, true, "/weather/request_value");
  // PM2.5 PPM Remote Variable
  pm25_in.begin(6, "/pm/value", espmega.iot, true, "/pm/request_value");
  // Air Purifier Switch Remote Variable
  pm_switch.begin(6, "/pm/switch_state", espmega.iot, true, "/pm/request_switch_state");
  pm_switch.enableSetValue("/pm/set_switch_state");
  // Air Purifier Fan Speed Remote Variable
  pm_fan_speed.begin(6, "/pm/fan_speed", espmega.iot, true, "/pm/request_fan_speed");
  pm_fan_speed.enableSetValue("/pm/set_fan_speed");
  espmega.iot->registerCard(0); // Register the Input Card
  espmega.iot->registerCard(1); // Register the Output Card
  espmega.iot->registerCard(2); // Register the Climate Card
  auto bindedGetTime = std::bind(&ESPMegaPRO::getTime, &espmega);
  iseDisplay.begin(&espmega.inputs, &espmega.outputs, &climateCard, &pm_switch, &pm_fan_speed);
  espmega.iot->registerRelativeMqttCallback(&handleMqttMessage);
  iseDisplay.registerPageChangeCallback(&handlePageChange);

  
}

// void update_display(uint8_t type, char *value){
//   switch (type)
//   {
//   case 0:
//     iseDisplay.updatePMoutside(get_pm25_out());
//     break;
//   case 1:
//     iseDisplay.updatePMinside(get_pm25_in());
//     break;
//   case 2:
//     iseDisplay.updateTempOutside(get_temp_out());
//     break;
//   case 3:
//     iseDisplay.updateWeather(weather.getValue());
//     break;
//   case 4:
//     iseDisplay.updateAirPurifierState();
//     break;
//   case 5:
//     iseDisplay.updateAirPurifierState();
//     break;
//   default:
//     break;
//   }
// }
void pm25outupdatedisplay(char* value){
  iseDisplay.updatePMoutside(get_pm25_out());
}
void pm25inupdatedisplay(char* value){
  iseDisplay.updatePMinside(get_pm25_in());
}
void tempoutupdatedisplay(char* value){
  iseDisplay.updateTempOutside(get_temp_out());
}
void weatherupdatedisplay(char* value){
  iseDisplay.updateWeather(weather.getValue());
}
void pmfanspeedupdatedisplay(char* value){
  iseDisplay.updateAirPurifierState();
}
void pmswitchupdatedisplay(char* value){
  ESP_LOGI("PM switch", "getting PM switch state from MQTT: %d", pm_switch.getValue());
  iseDisplay.updateAirPurifierState();
  ESP_LOGI("PM switch", "toggling PM switch state from: %d to %d", pm_switch.getValue(), !pm_switch.getValue());
}


void loop()
{
  espmega.loop();
  iseDisplay.loop();

  // Update the time every 15 seconds
  // static bool run_when_init = false; // No static ? , this shit will reset to 0 every loop
  static uint32_t last_time_updated = 0;
  // rtctime_t time = espmega.getTime();
  // when init update once then update every 15 seconds
  //  You don't need this, when it go from page 0 to 1 it call handlePageChange
  //  if(!run_when_init){
  //    time = espmega.getTime();
  //    iseDisplay.updateDateTimeText(time);
  //    last_time_updated = millis();
  //    run_when_init = true;
  //  }

  if (millis() - last_time_updated > 15000)
  {
    rtctime_t time = espmega.getTime();
    // log time
    ESP_LOGI("time", "Time: %d:%d:%d", time.hours, time.minutes, time.seconds);
    ESP_LOGI("Date", "Date: %d/%d/%d", time.day, time.month, time.year);
    iseDisplay.updateDateTimeText(time);
    last_time_updated = millis();
  }
  // // Update the PM2.5 PPM value every 15 seconds
  // static uint32_t last_pm25_out_update = 0;
  // if (millis() - last_pm25_out_update > 15000)
  // {
  //   uint16_t pm25_out_value = get_pm25_out();
  //   // ESP_LOGI("loopPM2.5","updating PM2.5 from MQTT inside loop: %d", pm25_out_value);
  //   iseDisplay.updatePMoutside(pm25_out_value);
  //   last_pm25_out_update = millis();
  // }
  // // Update the PM2.5 PPM value every 15 seconds
  // static uint32_t last_pm25_in_update = 0;
  // if (millis() - last_pm25_in_update > 15000)
  // {
  //   uint8_t pm25_in_value = get_pm25_in();
  //   iseDisplay.updatePMinside(pm25_in_value);
  //   last_pm25_in_update = millis();
  // }
  // Update the temperature value every 15 seconds
  // static uint32_t last_temp_out_update = 0;
  // if (millis() - last_temp_out_update > 15000)
  // {
  //   float temp_out_value = get_temp_out();
  //   iseDisplay.updateTempOutside(temp_out_value);
  //   last_temp_out_update = millis();
  // }
  // // Update the weather value every 15 seconds
  // static uint32_t last_weather_update = 0;
  // if (millis() - last_weather_update > 15000)
  // {
  //   char *weather_value = weather.getValue();
  //   iseDisplay.updateWeather(weather_value);
  //   last_weather_update = millis();
  // }
  // // Update the PM  state every 1 seconds
  // static uint32_t last_pm_switch_update = 0;
  // if (millis() - last_pm_switch_update > 500)
  // {
  //   iseDisplay.updateAirPurifierState();
  //   last_pm_switch_update = millis();
  // }
}

void on_pin_change(uint8_t pin, uint8_t value)
{
  // For input pin 0-3, map it with output pin 0-3
  // For input pin 4-7, map it with output pin 8-11
  // For input pin 12, map it with output pin 12
  // When Input pin change, Toggle the output pin
  if (pin < 4)
  {
    bool new_value = !espmega.outputs.getState(pin);
    espmega.outputs.setState(pin, new_value);
  } 
  else if (pin < 8)
  {
    bool new_value = !espmega.outputs.getState(pin + 4);
    espmega.outputs.setState(pin + 4, new_value);
  }
  else if (pin == 12)
  {
    bool new_value = !espmega.outputs.getState(12);
    espmega.outputs.setState(12, new_value);
  }
}

uint16_t get_pm25_out()
{
  uint16_t pm25_out_value = 0;
  // Read PM2.5 PPM from sensor
  pm25_out_value = atoi(pm25_out.getValue());
  ESP_LOGI("PM2.5", "getting PM2.5 PPM from MQTT: %d", pm25_out_value);
  return pm25_out_value;
}

uint16_t get_pm25_in()
{
  uint16_t pm25_in_value = 0;
  // Read PM2.5 PPM from sensor
  pm25_in_value = atoi(pm25_in.getValue());
  ESP_LOGI("PM2.5", "getting PM2.5 PPM from MQTT: %d", pm25_in_value);
  return pm25_in_value;
}

float get_temp_out()
{
  float temp_out_value = 0;
  // Read temperature from sensor
  temp_out_value = atof(temp_out.getValue());
  ESP_LOGI("Temperature", "getting Temperature from MQTT: %f", temp_out_value);
  return temp_out_value;
}

uint8_t get_pm_fanspeed()
{
  uint8_t pm_fan_speed_value = 0;
  // Read PM2.5 fan speed from sensor
  pm_fan_speed_value = (int) atof(pm_fan_speed.getValue());
  ESP_LOGI("PM fan speed", "getting PM2.5 PPM from MQTT: %d", pm_fan_speed_value);
  return pm_fan_speed_value;
}

bool get_pm_switch()
{
  ESP_LOGI("PM switch", "getting PM switch state from MQTT: %d", pm_switch.getValue());
  bool is_pm_switch_on = strcmp(pm_switch.getValue(),"on")== 0;
  return is_pm_switch_on;
}
void toggle_pm_switch()
{
  bool is_pm_switch_on = get_pm_switch();
  ESP_LOGI("PM switch", "toggling PM switch state from: %d to %d", is_pm_switch_on, !is_pm_switch_on);
  pm_switch.setValue(is_pm_switch_on ? "0" : "1");
}
void set_pm_fanspeed(uint8_t speed)
{
  ESP_LOGI("PM fan speed", "setting PM fan speed to: %d", speed);
  char buffer[4];
  itoa(speed, buffer, DEC);
  pm_fan_speed.setValue(buffer);
}

void handlePageChange(uint8_t page)
{

  // Which already send the time
  // printESP_LOGI the page have changed
  ESP_LOGI("Page", "Page change to: %d", page);
  rtctime_t time = espmega.getTime();
  iseDisplay.updateDateTimeText(time);
  switch (page)
  {
  case PAGE_STANDBY:
    iseDisplay.updateLightGroupStatePageStandby();
    iseDisplay.updateAirPurifierStateStandby();
    break;
  case PAGE_DASHBOARD:
    iseDisplay.updateLightGroupStatePageDashboard();
    iseDisplay.updateAirPurifierState();
    break;
  default:
    break;
  }

  // iseDisplay.updatePMinside();
  iseDisplay.updatePMoutside(get_pm25_out());
  iseDisplay.updatePMinside(get_pm25_in());
  iseDisplay.updateWeather(weather.getValue());
  iseDisplay.updateTempOutside(get_temp_out());

  /* iseDisplay.updateACState();
  iseDisplay.updateAirPurifierState();
  iseDisplay.updateLightGroupStatePageStandby();
  iseDisplay.updateLightGroupStatePageDashboard();
  iseDisplay.updateuserACmode();
  iseDisplay.updateAirPurifierStateStandby(); */
}