//#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <IPAddress.h>
#include <DallasTemperature.h>

// My Libraries
#include <secrets.h>
#include <Global.h>
#include <Communicator.h>
#include <Device.h>

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
WiFiClient net;

byte glycol_ds18b20[8] = {0x28,0xFF,0x64,0x0E,0x7F,0x57,0x09,0x66};
byte fermA_ds18b20[8] =  {0x28,0xFF,0x64,0x0E,0x7F,0x55,0x1C,0x55};
byte fermB_ds18b20[8] =  {0x28,0xFF,0x64,0x0E,0x71,0x86,0xF1,0xB8};

ControlDevice DEVICE_LIST[3];
int TOTAL_DEVICES = 0;

auto chipid = String(ESP.getChipId(), HEX);

Communicator hass_comm = Communicator(net);

unsigned long lastMillis;
unsigned long currentMillis;

void mqttCallback(char *topic, byte *payload, unsigned int length) {
  payload[length] = '\0';

  for (int i=0;i<TOTAL_DEVICES;i++) {
    if (strcmp((char *)topic, (DEVICE_LIST[i].mode.CmdTopic).c_str() ) == 0){
      DEVICE_LIST[i].mode.Setting = (char*)payload;

      if (DEVICE_LIST[i].mode.Setting == "auto") {
        DEVICE_LIST[i].swing_state = "off";
        hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "on");
      } else {
        DEVICE_LIST[i].swing_state = "on";
        hass_comm.publish_data(DEVICE_LIST[i].swing_topic, "off");
      }

      hass_comm.publish_data(DEVICE_LIST[i].mode.StateTopic, DEVICE_LIST[i].mode.Setting);
      break;
    }

    int setpoint_count;
    bool  dual_mode = DEVICE_LIST[i].dual;
    if (dual_mode) {
      setpoint_count = 3;
    } else {
      setpoint_count = 1;
    }

    for (int j=0;j<setpoint_count;j++) {
      String thistopic = DEVICE_LIST[i].setpoints[j].CmdTopic;
      if (strcmp((char *)topic, thistopic.c_str()) == 0){
        DEVICE_LIST[i].setpoints[j].Setting = atoi((char *)payload);
        hass_comm.publish_data(DEVICE_LIST[i].setpoints[j].StateTopic, String(DEVICE_LIST[i].setpoints[j].Setting));
        break;
      }
    }
  }
}

void merge(JsonObject dest, JsonObjectConst src) {
   for (auto kvp : src) {
     dest[kvp.key()] = kvp.value();
   }
}

/* Setup MQTT discovery for chiller tank control.
 *
 * Using climate device.
 */
void climateDevice(String name, byte ds18b20[], bool multimode=false) {
  int setpoint_count = 0;
  String name_slug = slugify(name);

  String config_topic = "homeassistant/climate/" + name_slug + "_" + chipid + "/config";
  String topic_root = "brewhouse/" + name_slug + "/";

  ControlDevice device;
  device.dual = false;
  device.name = name;
  device.topic_root = "brewhouse/" + slugify(name) + "/";

  for (int i=0;i<8;i++) {
    device.temp_sensor[i] = ds18b20[i];
  }

  StaticJsonDocument<1536> entity;
  entity["uniq_id"] = chipid + "_" + name_slug;
  entity["name"] =  name;
  entity["value_template"] = "{{ value }}";
  entity["temp_unit"] =  "F";
  entity["max_temp"] =  100;
  entity["min_temp"] =  0;
  entity["swing_mode_stat_t"] = topic_root + SWING_STATE;
  device.swing_topic = topic_root + SWING_STATE;

  entity["act_t"] = topic_root + ACTION_TPC;
  entity["act_tpl"] = "{{ value }}";
  device.action_topic = topic_root + ACTION_TPC;

  // Mode setup
  entity["mode_cmd_t"] = topic_root + MODE_SET;
  Mode mode = {topic_root + MODE_SET, topic_root + MODE_STATE, "off"};
  device.mode = mode;

  entity["mode_cmd_tpl"] = "{{ value }}";
  entity["mode_stat_t"] = topic_root + MODE_STATE;
  JsonArray modes = entity.createNestedArray("modes");
  modes.add("off");
  modes.add("cool");

  entity["temp_cmd_t"] = topic_root + TEMP_SET;
  SetPoint temp = {topic_root + TEMP_SET,topic_root + TEMP_STATE,70};
  device.setpoints[setpoint_count] = temp;
  setpoint_count++;
  entity["temp_cmd_tpl"] = "{{ value }}";
  entity["temp_stat_t"] = topic_root + TEMP_STATE;
  entity["curr_temp_t"] = topic_root + TEMP_CURRENT;
  entity["curr_temp_tpl"] = "{{ value }}";
  device.current_temp_topic= topic_root + TEMP_CURRENT;

  if (multimode == true) {
    device.dual = true;
    entity["temp_hi_cmd_t"] = topic_root + TEMP_HI_SET;
    SetPoint temp_hi = {topic_root + TEMP_HI_SET, topic_root + TEMP_HI_STATE, 70};
    device.setpoints[setpoint_count] = temp_hi;
    setpoint_count++;
    entity["temp_hi_cmd_tpl"] = "{{ value }}";
    entity["temp_hi_stat_t"] = topic_root + TEMP_HI_STATE;

    entity["temp_lo_cmd_t"] = topic_root + TEMP_LO_SET;
    SetPoint temp_lo = {topic_root + TEMP_LO_SET, topic_root + TEMP_LO_STATE, 70};
    device.setpoints[setpoint_count] = temp_lo;
    setpoint_count++;
    entity["temp_lo_cmd_tpl"] = "{{ value }}";
    entity["temp_lo_stat_t"] = topic_root + TEMP_LO_STATE;
    modes.add("heat");
    modes.add("auto");
  }
  JsonObject dev = entity.createNestedObject("dev");
  dev["name"] = DEVICE_NAME;
  dev["mdl"] = DEVICE_MDL;
  dev["sw"] = DEVICE_SW;
  dev["mf"] = DEVICE_MF;
  JsonArray ids = dev.createNestedArray("ids");
  ids.add(chipid);
  //dev["ids"] = "[\"" + chipid +"\"]";

  DEVICE_LIST[TOTAL_DEVICES] = device;
  TOTAL_DEVICES++;
  hass_comm.mqtt_discovery(config_topic, entity);
}

void SendUpdate(ControlDevice device) {
  int setpoint_count = 0;
  float device_temp;
  device_temp = UpdateTemperature(&sensors, device.temp_sensor);
  hass_comm.publish_data(device.current_temp_topic, String(device_temp));
  hass_comm.publish_data(device.mode.StateTopic, device.mode.Setting);
  hass_comm.publish_data(device.action_topic, device.action_state);
  bool dual_mode = device.dual;

  if (dual_mode) {
    setpoint_count = 3;
  } else {
    setpoint_count = 1;
  }

  for (int i=0;i<setpoint_count;i++) {
    hass_comm.publish_data(device.setpoints[i].StateTopic, String(device.setpoints[i].Setting));
  }
}

void setup() {
  hass_comm.connectCallback(&mqttCallback);

  const char* ssid = WIFI_SSID;
  const char* password = WIFI_PASSWORD;
  sensors.begin();
  sensors.setResolution(glycol_ds18b20, 9);

  Serial.begin(115200);
  WiFi.begin(ssid, password);

  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Failed!");
    return;
  }
  Serial.println();
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  climateDevice("Coolant Tank", glycol_ds18b20);
  climateDevice("Fermenter 1", fermA_ds18b20,true);
  climateDevice("Fermenter 2", fermB_ds18b20,true);

  SendUpdate(DEVICE_LIST[0]);
  SendUpdate(DEVICE_LIST[1]);
  SendUpdate(DEVICE_LIST[2]);
  lastMillis = millis();
}

void loop() {
  currentMillis = millis();



  if (currentMillis - lastMillis >= 10000) {
    if (DeviceLoop(DEVICE_LIST[0], &sensors)) {
      Serial.println("Ouput state changed.");
      SendUpdate(DEVICE_LIST[0]);
    }
    if (DeviceLoop(DEVICE_LIST[1], &sensors)) {
      Serial.println("Ouput state changed.");
      SendUpdate(DEVICE_LIST[1]);
    }
    if (DeviceLoop(DEVICE_LIST[2], &sensors)) {
      Serial.println("Ouput state changed.");
      SendUpdate(DEVICE_LIST[2]);
    }
    sensors.requestTemperatures();
    SendUpdate(DEVICE_LIST[0]);
    SendUpdate(DEVICE_LIST[1]);
    SendUpdate(DEVICE_LIST[2]);
    lastMillis = currentMillis;
  }

  delay(100);
  hass_comm.loop();
}
