#include <DHT.h>
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>
#include <avr/wdt.h>
#include <EEPROM.h>

#define LED_BUILTIN 8

const char OKheader[] = "HTTP/1.1 200 Okay";

float temp = -999;
float hum = -999;
int leak = 9999;
bool sound = true;
uint32_t dht_errors = 0;

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x69, 0x69 };
byte ip[] = { 192, 168, 178, 128 };

DHT dht(6, DHT11);

#define WEBBUF 64
EthernetServer server(80);

#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else  // __ARM__
extern char *__brkval;
#endif  // __arm__

int freeMemory() {
  char top;
#ifdef __arm__
  return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
  return &top - __brkval;
#else  // __arm__
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif  // __arm__
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  pinMode(5, OUTPUT);

  //Leak Sensor
  pinMode(A0, INPUT_PULLUP);

  //Virtual power
  pinMode(7, OUTPUT);
  digitalWrite(7, LOW);
  pinMode(3, OUTPUT);
  digitalWrite(3, LOW);

  dht.begin();

  Ethernet.init(10);
  Ethernet.begin(mac, ip);
  Ethernet.setRetransmissionCount(20);
  Ethernet.setRetransmissionTimeout(100);
  if (Ethernet.hardwareStatus() == EthernetNoHardware) {
    Serial.println(F("Ethernet error!"));
    wdt_enable(WDTO_8S);
    while (true) {
      digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
      delay(500);
    }
  }
  while (Ethernet.linkStatus() == LinkOFF) {
    Serial.println(F("No Link!"));
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    delay(1000);
  }
  digitalWrite(LED_BUILTIN, HIGH);

  // print your local IP address:
  Serial.print(F("IP address: "));
  Serial.println(Ethernet.localIP());

  sound = EEPROM.read(43);

  server.begin();

  digitalWrite(LED_BUILTIN, LOW);
}
void readSensors() {
  temp = dht.readTemperature();
  hum = dht.readHumidity();

  uint8_t dontCrash = 0;
  dontCrash = 0;
  while (isnan(temp) || isnan(hum)) {
    dht_errors++;
    delay(100);
    temp = dht.readTemperature();
    hum = dht.readHumidity();
    dontCrash++;
    if (dontCrash > 3) {
      temp = -999;
      hum = -999;
      break;
    }
  }

  uint16_t sum = 0;
  for (uint8_t i = 0; i < 10; i++) {
    sum += analogRead(A0);
    delay(5);
  }
  leak = sum / 10;

  Serial.println(temp);
  Serial.println(hum);
  Serial.println(leak);
}

void handleServer() {
  char clientline[WEBBUF];
  int index = 0;

  EthernetClient client = server.available();
  if (client) {
    digitalWrite(LED_BUILTIN, HIGH);
    // an http request ends with a blank line
    boolean current_line_is_blank = true;

    // reset the input buffer
    index = 0;

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();

        // If it isn't a new line, add the character to the buffer
        if (c != '\n' && c != '\r') {
          clientline[index] = c;
          index++;
          // are we too big for the buffer? start tossing out data
          if (index >= WEBBUF)
            index = WEBBUF - 1;

          // continue to read more data!
          continue;
        }

        // got a \n or \r new line, which means the string is done
        clientline[index] = 0;

        // Print it out for debugging
        Serial.println(clientline);

        char *filename;

        filename = clientline + 4; // look after the "GET " (4 chars)
        // a little trick, look for the " HTTP/1.1" string and
        // turn the first character of the substring into a 0 to clear it out.
        (strstr(clientline, " HTTP"))[0] = 0;

        // print the file we want
        Serial.println(filename);

        if (String(filename) == F("/reboot")) {
          client.println(OKheader);
          client.println(F("Content-Type: text/plain"));
          client.println(F("Refresh: 5; url=/"));
          client.println();
          client.println(F("rebooting..."));
          client.stop();
          wdt_enable(WDTO_15MS);
          while (1);
        }
        else if (String(filename) == F("/")) {
          client.println(OKheader);
          client.println(F("Content-Type: text/html; charset=utf-8"));
          client.println(F("Refresh: 15"));
          client.println();
          client.print(F("<!DOCTYPE html>\n<html>\n<head>\n<title>Keller Sensor</title>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<style>body {background-color: #111; color: #EEE; font-family: monospace;}\nul {background-color: #222; max-width: max-content;}\na {background-color: #EEE; color: #222;}\nhr {color: #333;}\n.an {color: lime;}\n.aus{color: red;}\n.version{font-size: 8pt;}</style>\n</head>\n<body>\n<h1>Keller Sensor</h1>\n<ul>\n<li>Temperatur: "));
          client.print(temp);
          client.print(F("°C</li>\n<li>Feuchte: "));
          client.print(hum);
          client.print(F("%</li>\n<li>Leck Rohwert: "));
          client.print(leak);
          client.print(F("/1023</li>\nDer Leck Rowert geht von 0 bis 1023, wobei 1023 trocken, und 0 extrem nass ist.\n</ul>\n<hr>\n<p>Ton "));
          client.print(sound ? F("<span class=\"an\">AN</span>") : F("<span class=\"aus\">AUS</span>"));
          client.print(F(" <a href=\"/toggleSound\">[Ändern]</a></p>\n<p>Freier Speicher: "));
          client.print(freeMemory());
          client.print(F("</p>\n<p class=\"version\"><small>"));
          //client.print(__FILE__);
          client.print(F("Kompiliert am "));
          client.print(__DATE__);
          client.print(F(" um "));
          client.print(__TIME__);
          client.print(F("</small></p>\n</body>\n</html>"));

          break;
        }

        else if (String(filename) == F("/data")) {
          client.println(OKheader);
          client.println(F("Content-Type: application/json"));
          client.println(F("Refresh: 15"));
          DynamicJsonDocument doc(128);
          doc["temp"] = temp;
          doc["hum"] = hum;
          doc["leak_raw"] = leak;
          doc["leak_percent"] = map(leak, 1024, 0, 0, 100);
          doc["leak_bool"] = leak < 512;
          doc["sound"] = sound;
          doc["dht_errors"] = dht_errors;
          char output[128];
          client.print(F("Content-Length: "));
          client.println(serializeJson(doc, output));
          client.println();
          client.print(output);
          break;
        }

        else if (String(filename) == F("/toggleSound")) {
          client.println(OKheader);
          client.println(F("Content-Type: text/plain"));
          client.println(F("Refresh: 1; url=/"));
          client.println();
          sound = !sound;
          EEPROM.write(43, sound);
          client.print(F("Sound "));
          client.print(sound ? F("ON") : F("OFF"));
          break;
        }

        else {
          client.println(F("HTTP/1.1 404 Not Found"));
          client.println(F("Content-Type: text/plain"));
          client.println();
          client.println(F("404 Not Found"));
          break;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
    digitalWrite(LED_BUILTIN, LOW);
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  handleServer();
  //Ethernet.maintain(); //only needed with DHCP
  //ntp.update(); //crashes server after 2 minutes

  static uint32_t sensMillis = 0;
  if ((millis() - sensMillis) > 2000) {
    readSensors();
    sensMillis = millis();
  }

  //Scheduled Reboot after 14 days (just to be sure)
  if (millis() > 1209600000) {
    wdt_enable(WDTO_15MS);
    while (1);
  }

  if (leak < 512) {
    if (uint8_t(millis()) > 127) {
      if (sound) {
        tone(5, 1000);
      }
      digitalWrite(4, LOW);
    }
    else {
      noTone(5);
      digitalWrite(4, HIGH);
    }
  }
  else {
    noTone(5);
    digitalWrite(4, LOW);
  }

  delay(1);
}