#include <Arduino.h>
#include <RH_ASK.h>

#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include "WiFiManager.h" //https://github.com/tzapu/WiFiManager

#include <PubSubClient.h> //MQTT
#include <ArduinoJson.h>

/**
 * Baud Rate for the 433 Mhz connection.
 * Caution: Must be the same for sender & receiver!
 **/
#define RH_SPEED 1000

/**
 * Pin for the receiver
 **/
#if defined(USE_ARDUINO)
#define RH_RX_PIN 11
#elif defined(USE_ESPRESSIF)
#define RH_RX_PIN D8
#endif

#define RH_BUF_LEN 11
uint8_t rh_buf[RH_BUF_LEN];

#define RH_ASK_MAX_MESSAGE_LEN RH_BUF_LEN


/**
 * If this file does not exist create it from the config.h.example file
 * Place project specific settings here. It should not be checked in.
 * */
#include "config.h"

// const int AirValue = 800;   //replace the value with value when placed in air
// const int WaterValue = 345; //replace the value with value when placed in water

volatile boolean new_value = false;



ESP8266WebServer server(80);
WiFiClient espClient;
PubSubClient mqttClient(MQTT_BROKER, 1883, espClient);

RH_ASK driver(RH_SPEED, RH_RX_PIN);

// Message Headers:
#define MSG_START 0x10
#define MSG_BAT 0x20
#define MSG_TEMPHUM 0x30
#define MSG_ERR 0x40
#define MSG_HUM 0x50

char buff[20];

float Temperature = 0;
float Humidity = 0;
int soil = 0;
float SoilHumidity = 0;
long battery = 0;

long lastMillis = 0;

void handleRoot(); // function prototypes for HTTP handlers
void handleNotFound();
void reconnect();
String SendHTML();
void publishData(float temp, float humid, float soil, long battery);
void callback(char *inTopic, byte *payload, unsigned int length);

void configModeCallback(WiFiManager *myWiFiManager)
{
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  //if you used auto generated SSID, print it
  Serial.println(myWiFiManager->getConfigPortalSSID());
}

void setup()
{

  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  if (!driver.init())
  {
    Serial.println("init failed");
  }
  else
  {
    Serial.println("init done");
  }

  //WiFiManager
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;
  //reset settings - for testing
  //wifiManager.resetSettings();

  //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
  wifiManager.setAPCallback(configModeCallback);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name
  //here  "AutoConnectAP"
  //and goes into a blocking loop awaiting configuration
  if (!wifiManager.autoConnect())
  {
    Serial.println("failed to connect and hit timeout");
    //reset and try again, or maybe put it to deep sleep
    ESP.reset();
    delay(1000);
  }

  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");

  mqttClient.setCallback(callback);

  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");

  // switch build in led OFF (with HIGH);
  digitalWrite(LED_BUILTIN, HIGH);
}

void loop()
{

  uint8_t len = sizeof(rh_buf);

  long ms = millis();

  if (driver.recv(rh_buf, &len))
  {
    Serial.println("Message received.");
    uint8_t header = rh_buf[0] & 0xf0;
    uint8_t device_id = rh_buf[0] &0x0f;
    Serial.print("Device ID: ");
    Serial.println(device_id);
    switch (header)
    {
    case MSG_START:
      // start message
      Serial.println("0x00 start byte, first message from sensor");
      break;
    case MSG_TEMPHUM:
      // DHT Temperature
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("DHT Temp");
      memcpy(&Temperature, &rh_buf[1], 4);
      Serial.print(Temperature);
      Serial.println(" °C");
      // DHT Humidity:
      Serial.println("DHT Humidity");
      memcpy(&Humidity, &rh_buf[5], 4);
      Serial.print(Humidity);
      Serial.println(" %");
      Serial.println("Soil Humidity");
      memcpy(&soil, &rh_buf[9], 2);
      Serial.println(soil);
      SoilHumidity = (float)(map(soil, AirValue, WaterValue, 0, 100));
      Serial.println(SoilHumidity);
      new_value = true;
      break;
    case MSG_HUM:
      Serial.println("Soil Humidity");
      memcpy(&soil, &rh_buf[1], 2);
      Serial.println(soil);
      SoilHumidity = (float)(map(soil, AirValue, WaterValue, 0, 100));
      Serial.println(SoilHumidity);
      new_value = true;
      break;
    case MSG_BAT:
      // battery data
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("Battery");
      memcpy(&battery, &rh_buf[1], 4);
      Serial.print(battery);
      Serial.println(" mV");
      new_value = true;
      break;
    case MSG_ERR:
      // error
      Serial.println("0xEE error");
      break;
    default:
      // should never happen
      break;
    }
  }

  server.handleClient();
  if (!mqttClient.connected())
  {
    reconnect();
  }

  if (ms - lastMillis > 5000 && new_value)
  {
    publishData(Temperature, Humidity, SoilHumidity, battery);
    lastMillis = ms;
    new_value = false;
  }

  mqttClient.loop();

  digitalWrite(LED_BUILTIN, HIGH);

}

void reconnect()
{
  // Loop until we're reconnected
  while (!mqttClient.connected())
  {
    Serial.println("INFO: Attempting MQTT connection...");
    // Attempt to connect
    
    // Should work without authentication, if credentials are NULL
    if (mqttClient.connect(MQTT_CLIENT_ID, MQTT_CLIENT_USER, MQTT_CLIENT_PW))
    {
      Serial.println("INFO: connected");
    }
    else
    {
      Serial.print("ERROR: failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println("DEBUG: try again in 5 seconds");
      // Wait 5 seconds before retrying
      //delay(5000);
      return;
    }
  }
}

void callback(char *inTopic, byte *payload, unsigned int length)
{
  Serial.println("Got RF data");
  Serial.write(payload, length);
  Serial.println();

  // convert the byte non terminated payload to string:
  char pltext[length + 1];
  strncpy(pltext, (char *)payload, length);
  pltext[length] = '\0';
  // convert payload string to int:
  int num = atoi(pltext);
  Serial.println(pltext);
  Serial.println("RF data sent");
}

void handleRoot()
{
  server.send(200, "text/html", SendHTML());
}

void handleNotFound()
{
  server.send(404, "text/plain", "Not found");
}

// function called to publish the temperature and the humidity
void publishData(float temp, float humid, float soil, long battery)
{
  // create a JSON object
  // doc : https://github.com/bblanchon/ArduinoJson/wiki/API%20Reference
  DynamicJsonDocument doc(1024);

  doc["temperature"] = (String)temp;
  doc["humidity"] = (String)humid;
  doc["soilhumidity"] = (String)soil;
  doc["battery"] = (String)battery;
  
  serializeJson(doc, Serial);
  Serial.println("");
  char data[200];
  serializeJson(doc, data);
  mqttClient.publish(MQTT_SENSOR_TOPIC, data, true);
  yield();
}

String SendHTML()
{
  String ptr = "<!DOCTYPE html> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>Wiesendamm Wetter Report</title>\n";
  ptr += "<meta http-equiv=\"refresh\" content=\"2\" >";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr += "p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<div id=\"webpage\">\n";
  ptr += "<h1>Wiesendamm Wetter Report</h1>\n";

  ptr += "<p>Temperature: ";
  ptr += (float)Temperature;
  ptr += " C</p>";
  ptr += "<p>Humidity: ";
  ptr += (float)Humidity;
  ptr += " %</p>";
  ptr += "<p>Soil Humidity: ";
  ptr += (float)SoilHumidity;
  ptr += " %</p>";
  ptr += "<p>Battery: ";
  ptr += (float)battery;
  ptr += " mV</p>";

  ptr += "</div>\n";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}