
// MQTT callback
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  //  Serial.print("MQTT payload arrived [");
  //  Serial.print(topic);
  //  Serial.print("] ");
  //  for (int i = 0; i < length; i++) {
  //    Serial.print((char)payload[i]);
  //  }
  //  Serial.println();

  if (strcmp(topic, mqtt_topic_in) == 0) { //if topic = mqtt_topic_in
    Serial.print("received MQTT ");
    Serial.println(mqtt_topic_in);

    int len;
    if (length < 101) len = length; // if input is bigger than dest buffer, cut
    else len = 100;

    for (int i = 0; i < len; i++) {
      cmdPayload[i] = (char)payload[i];
    }
    cmdPayload[len + 1] = '\0';
    //    Serial.print("cmdPayload:");
    //    Serial.println(cmdPayload);
    cmdInQueue = true; // payload is processed in "commands"
  }//if topic = mqtt_topic_in

  if (strcmp(topic, outTemp_topic_in) == 0) { //if topic = outTemp_topic_in
    int len;
    if (length < 6) len = length; // if input is bigger than dest buffer, cut
    else len = 5;
    for (int i = 0; i < len; i++) {
      outTemp_newValue[i] = (char)payload[i];
    }
    outTemp_newValue[len + 1] = '\0';
    outTemp_parseNewValue = true;
  }//if topic = outTemp_topic_in

  if (strcmp(topic, outHum_topic_in) == 0) { //if topic = outHum_topic_in
    int len;
    if (length < 4) len = length; // if input is bigger than dest buffer, cut
    else len = 3;
    for (int i = 0; i < len; i++) {
      outHum_newValue[i] = (char)payload[i];
    }
    outHum_newValue[len + 1] = '\0';
    outHum_parseNewValue = true;
  }//if topic = outHum_topic_in

  if (strcmp(topic, domoticz_out_topic) == 0) { //if topic = domoticz_out_topic
    Serial.print("received MQTT ");
    Serial.println(domoticz_out_topic);
    if ( !domoticzOutParserBusy ) {
      int len;
      if (length < 450) len = length; // if input is bigger than dest buffer, cut
      else len = 449;
      for (int i = 0; i < len; i++) {
        domoticzOutPayload[i] = (char)payload[i];
      }
      domoticzOutPayload[len + 1] = '\0';
      domoticzOutParseData = true; // parse domoticz data in the next loop()
    }
  }//if topic = domoticz_out_topic

}//mqttCallback


void mqttPrepareConnection() {
  Serial.print("MQTT connection with ");
  if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) == 0) {
    // user and password, no Last Will
    Serial.println("user and password, no Last Will");
    mqttMode = 2;
  }
  else if (strlen(mqtt_user) > 0 && strlen(mqtt_willTopic) > 0) {
    // user, password and Last Will
    Serial.println("user, password and Last Will");
    mqttMode = 3;
  }
  else if (strlen(mqtt_user) == 0 && strlen(mqtt_willTopic) > 0) {
    // Last Will but no user and password
    Serial.println("Last Will but no user and password");
    mqttMode = 4;
  }
  else {
    // no user, password and no Last Will
    Serial.println("no user, password and no Last Will");
    mqttMode = 1;
  }
}

bool mqttReconnect() {
  mqttclient.disconnect();
  delay(10);
  // Create a random MQTT client ID
  String mqttClientId = "ESP8266Client-";
  mqttClientId += String(random(0xffff), HEX);

  Serial.print("connecting to MQTT broker with ");

  bool connRes;

  switch (mqttMode) {
    case 1:
      Serial.print("no user, password and no Last Will");
      connRes = mqttclient.connect(mqttClientId.c_str());
      break;
    case 2:
      Serial.print("user and password, no Last Will");
      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass);
      break;
    case 3:
      Serial.print("user, password and Last Will");
      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_user, mqtt_pass, mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
      break;
    case 4:
      Serial.println("Last Will, no user and password");
      connRes = mqttclient.connect(mqttClientId.c_str(), mqtt_willTopic, mqtt_willQos, mqtt_willRetain, mqtt_willMsg);
      break;
  }

  Serial.print("... attempt: ");
  Serial.print(mqttReconnectAttempts);
  Serial.println();

  if (connRes) {
    Serial.print("MQTT connected. Reconnects: ");
    Serial.println(mqttReconnects);
    mqttReconnects++;

    // Once connected, publish an announcement...
    //    char outMsg[30];
    //    sprintf(outMsg, "connected, %d reconnects", mqttReconnects);
    //    mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
    publishStatus();
    //mqttclient.publish(mqtt_topic_out, "connected");
    // ... and resubscribe
    Serial.println("Subscribed to:");
    if (strlen(mqtt_topic_in) > 0) {
      if (mqttclient.subscribe(mqtt_topic_in)) {
        Serial.println(mqtt_topic_in);
        mqttInTopicSubscribed = true;
      }
    }
    if (strlen(outTemp_topic_in) > 0) {
      if (mqttclient.subscribe(outTemp_topic_in)) {
        Serial.println(outTemp_topic_in);
      }
    }
    if (strlen(outHum_topic_in) > 0) {
      if (mqttclient.subscribe(outHum_topic_in)) {
        Serial.println(outHum_topic_in);
      }
    }
    if (useDomoticz && strlen(domoticz_out_topic) > 0) {
      if (mqttclient.subscribe(domoticz_out_topic)) {
        Serial.println(domoticz_out_topic);
      }
    }
  }
  else {
    Serial.print("MQTT connect FAILED, rc=");
    Serial.println(mqttclient.state());
  }
  return mqttclient.connected();
} //mqttReconnect



void mqttClientInit() {
  mqttclient.setServer(mqtt_server, mqtt_port);
  mqttclient.setCallback(mqttCallback);
  mqttLastReconnectAttempt = 0;
  mqttReconnectAttempts = 0;
}

int lastWifiStatus;

void mqttHandleConnection() {
  int currWifiStatus = WiFi.status();
  if ( currWifiStatus != lastWifiStatus ) {
    lastWifiStatus = currWifiStatus;
    Serial.print("WiFi status changed to: ");
    Serial.println(currWifiStatus);
  }

  if ( currWifiStatus == WL_CONNECTED ) {
    // MQTT reconnect if not connected (nonblocking)
    bool doReconnect = false;
    if (!mqttclient.connected()) doReconnect = true;

    if (mqttInTopicSubscribed) { //  if mqtt_topic_in is subscribed
      if ( (millis() - mqttLastHeartbeat) > MQTT_HEARTBEAT_MAXAGE) { // also reconnect if no HEARTBEAT was received for some time
        doReconnect = true;
        mqttLastHeartbeat = millis();
        Serial.println("MQTT HEARTBEAT overdue. Force-Reconnecting MQTT...");
      }
    }

    if (doReconnect) {
      unsigned int mqttReconnectAttemptDelay;
      if (mqttReconnectAttempts < 3) mqttReconnectAttemptDelay = 15000; // if this is the 1-3rd attempt, try again in 15s
      else if (mqttReconnectAttempts < 10) mqttReconnectAttemptDelay = 60000; // if more than 3 attempts failed, try again every min
      else if (mqttReconnectAttempts < 20) mqttReconnectAttemptDelay = 300000; // if more than 10 attempts failed, try again every 5 min
      else if (mqttReconnectAttempts >= 20) {
        // if more than 20 attempts failed, restart the ESP
        delay(100);
        ESP.restart();
      }

      if ((millis() - mqttLastReconnectAttempt) > mqttReconnectAttemptDelay) {
        mqttLastReconnectAttempt = millis();
        mqttReconnectAttempts++;
        if (mqttReconnect()) { // Attempt to reconnect
          // attempt successful - reset mqttReconnectAttempts
          mqttReconnectAttempts = 0;
        }
      }
      mqttclient.loop();
    }
    else {
      mqttclient.loop();
    }
  }
}

void publishStatus() {
  char outMsg[60];
  long upTime = millis() / 1000;
  sprintf(outMsg, "connected, reconnects: %d, uptime: %d, free heap: %d", mqttReconnects - 1, upTime, ESP.getFreeHeap());
  mqttclient.publish(mqtt_topic_out, outMsg, mqtt_outRetain);
  yield();
}

void sendStatus(char* payload) {
  char buf[101];
  strlcpy(buf, payload, 101);
  Serial.println(buf);
  mqttclient.publish(mqtt_topic_out, buf, mqtt_outRetain);
  yield();
}

void publishCurrentThermostatValues() {
  char tmp_topic_out[50];

  float curr_setTemp;
  if (heatingMode == 1) curr_setTemp = setTemp;
  else if (heatingMode == 2) curr_setTemp = setTempLow;
  else if (heatingMode == 3) curr_setTemp = setTempLow2;

  char setTemp_chararr[6];
  char currSetTemp_chararr[6];

  dtostrf(setTemp, 1, 1, setTemp_chararr );
  dtostrf(curr_setTemp, 1, 1, currSetTemp_chararr );

  Serial.print("heatingMode: '");
  Serial.print(heatingMode);
  Serial.println("'");
  Serial.print("set temp: '");
  Serial.print(setTemp_chararr);
  Serial.println("'");
  Serial.print("current set temp: '");
  Serial.print(currSetTemp_chararr);
  Serial.println("'");

  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "setTemp");
  mqttclient.publish(tmp_topic_out, setTemp_chararr);
  yield();

  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "currSetTemp");
  mqttclient.publish(tmp_topic_out, currSetTemp_chararr);
  yield();

  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingMode");
  char heatingMode_chararr[3];
  sprintf(heatingMode_chararr, "%d", heatingMode);
  mqttclient.publish(tmp_topic_out, heatingMode_chararr);
  yield();

  char turnHeatingOn_char[5];
  if (turnHeatingOn) strcpy(turnHeatingOn_char, "on");
  else strcpy(turnHeatingOn_char, "off");
  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heating");
  mqttclient.publish(tmp_topic_out, turnHeatingOn_char);
  yield();

  char buf[101];
  sprintf(buf, "%d", heatingOnTime);
  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOnTime");
  mqttclient.publish(tmp_topic_out, buf);
  yield();

  sprintf(buf, "%d", heatingOffTime);
  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "heatingOffTime");
  mqttclient.publish(tmp_topic_out, buf);
  yield();
}

void publishCurrentSensorValues() {
  if ( lastTempUpdate != 0 && (millis() - lastTempUpdate) < 120000 ) {
    char tmp_topic_out[50];

    char temp_chararr[6];
    char hum_chararr[4];
    dtostrf(currTemp, 1, 1, temp_chararr );
    sprintf(hum_chararr, "%2i", currHum);

    Serial.print("temp: '");
    Serial.print(temp_chararr);
    Serial.println("'");
    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "temp");
    mqttclient.publish(tmp_topic_out, temp_chararr);
    yield();

    Serial.print("hum: '");
    Serial.print(currHum);
    Serial.println("'");
    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "hum");
    mqttclient.publish(tmp_topic_out, hum_chararr);
    yield();

    dtostrf(currTemp_raw, 1, 1, temp_chararr );
    sprintf(hum_chararr, "%2i", currHum_raw);

    Serial.print("temp_raw: '");
    Serial.print(temp_chararr);
    Serial.println("'");
    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "temp_raw");
    mqttclient.publish(tmp_topic_out, temp_chararr);
    yield();

    Serial.print("hum_raw: '");
    Serial.print(currHum_raw);
    Serial.println("'");
    sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "hum_raw");
    mqttclient.publish(tmp_topic_out, hum_chararr);
    yield();
  }
}

void publishCurrentPIRValue() {
  char tmp_topic_out[50];
  char PIRStatus[4];

  sprintf(tmp_topic_out, "%s/%s", mqtt_topic_out, "PIR");

  if (PIRSensorOn) {
    strcpy(PIRStatus, "on");
  }
  else {
    strcpy(PIRStatus, "off");
  }
  mqttclient.publish(tmp_topic_out, PIRStatus);
}

void mqttPublishHeartbeat() {
  mqttclient.publish(mqtt_topic_in, "HEARTBEAT"); // publishes to IN-topic. MQTT will force-reconnect if it does not get a HEARTBEAT for 2 min
}


