#include <UIPEthernet.h>
#include <EEPROM.h>

EthernetClient client;
EthernetServer server = EthernetServer(1337);

const byte mac[6] = { 0x46, 0x72, 0x69, 0x74, 0x7A, 0x21 }; // Fritz! (in hexadecimal)

const PROGMEM char dataPost[]        = "POST /igdupnp/control/WANCommonIFC1 HTTP/1.1\n";
const PROGMEM char dataHost[]        = "Host: fritz.box:49000\n";
const PROGMEM char dataConnection[]  = "Connection: keep-alive\n";
const PROGMEM char dataUserAgent[]   = "User-agent: FritzbackDevice/1.0\n";
const PROGMEM char dataContentType[] = "Content-Type: text/xml; charset=utf-8\n";
const PROGMEM char dataAction[]      = "SOAPAction: \"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1#GetAddonInfos\"\n";
const PROGMEM char dataLength[]      = "Content-Length: 426\n";

const PROGMEM char dataPacketA[]     = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope ";
const PROGMEM char dataPacketB[]     = "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" ";
const PROGMEM char dataPacketC[]     = "xmlns:ns1=\"urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\" ";
const PROGMEM char dataPacketD[]     = "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
const PROGMEM char dataPacketE[]     = "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" ";
const PROGMEM char dataPacketF[]     = "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">";
const PROGMEM char dataPacketG[]     = "<SOAP-ENV:Body><ns1:GetAddonInfos/></SOAP-ENV:Body></SOAP-ENV:Envelope>\n";

char sendBuffer[100];

long curRates[2] = { 0L, 0L };
long maxRates[2] = { 0L, 0L };

const int PIN_PUMP[2] = { 6, 5 };

const int ROM_PUMPS = 0;
const int ROM_MAXRATES[2] = { 2, 6 };

byte cfgPumps = 0;

void setup() {
  // Pin Modes
  pinMode(PIN_PUMP[0], OUTPUT);
  pinMode(PIN_PUMP[1], OUTPUT);
  // Ethernet
  Ethernet.begin(mac);
  server.begin();
  // EEPROM
  maxRates[0] = EEPROM_ReadLong(ROM_MAXRATES[0]);
  maxRates[1] = EEPROM_ReadLong(ROM_MAXRATES[1]);
  cfgPumps = EEPROM.read(ROM_PUMPS);
}

void loop() {
  if (client.connect(Ethernet.gatewayIP(), 49000)) {
    while (client.connected()) {
      client.print(getString(dataPost));
      client.print(getString(dataHost));
      client.print(getString(dataConnection));
      client.print(getString(dataUserAgent));
      client.print(getString(dataContentType));
      client.print(getString(dataAction));
      client.print(getString(dataLength));
      client.print("\n");
      client.print(getString(dataPacketA));
      client.print(getString(dataPacketB));
      client.print(getString(dataPacketC));
      client.print(getString(dataPacketD));
      client.print(getString(dataPacketE));
      client.print(getString(dataPacketF));
      client.print(getString(dataPacketG));
      client.print("\n");
      //
      unsigned long timeout = millis() + 5000;
      while (client.available() == 0) {
        if (millis() > timeout) {
          break;
        }
        delayLoop(1);
      }
      char limiter[] = "<>";
      timeout = timeout = millis() + 5000;
      while (true) {
        char dataBuffer[255] = { 0 };
        client.readBytesUntil('\n', dataBuffer, 255);
        setRate("NewByteReceiveRate", 18, dataBuffer, 0);
        setRate("NewByteSendRate", 15, dataBuffer, 1);
        char *fin = strtok(dataBuffer, "<>");
        if ((strncmp(fin, "/s:Envelope", 11) == 0) || millis() > timeout) {
          break;
        }
      }
      delayLoop(250);
    }
  }
  delayLoop(5000);
}

void delayLoop(int wait) {
  unsigned long timer = millis() + wait;
  while (millis() < timer) {
    if (EthernetClient control = server.available()) {
      while (server.available() > 0) {
        int cmd = control.read();
        switch (cmd) {
          case 'R':
            maxRates[0] = 0L;
            maxRates[1] = 0L;
            EEPROM_WriteLong(ROM_MAXRATES[0], 0L);
            EEPROM_WriteLong(ROM_MAXRATES[1], 0L);
          break;
          case 'P':
            cfgPumps = 1;
            EEPROM.update(ROM_PUMPS, 1);
          break;
          case 'p':
            cfgPumps = 0;
            EEPROM.update(ROM_PUMPS, 0);
            analogWrite(PIN_PUMP[0], 0);
            analogWrite(PIN_PUMP[1], 0);
          break;
        }
        control.println(curRates[0]);
        control.println(curRates[1]);
        control.println(maxRates[0]);
        control.println(maxRates[1]);
        control.println(cfgPumps);
        control.println();
        control.stop();
      }
    }
  }
}

void setRate(const char* value, int vsize, char* origBuffer, int mr) {
  char dataBuffer[255];
  strcpy(dataBuffer, origBuffer);
  char* tag = strtok(dataBuffer, "<>");
  if (strncmp(tag, value, vsize) == 0) {
    long data = atol(strtok(NULL, "<>"));
    curRates[mr] = data;
    if (data > maxRates[mr]) {
      EEPROM_WriteLong(ROM_MAXRATES[mr], data);
      maxRates[mr] = data;
    }
    if (cfgPumps == 1) {
      if (map(data, 0, maxRates[mr], 0, 100) > 3) {
        analogWrite(PIN_PUMP[mr], map(constrain(data, 0, maxRates[mr] * 0.7), 0, maxRates[mr] * 0.7, 100, 255));
      } else {
        analogWrite(PIN_PUMP[mr], 0);
      }
    }
  }
}

char* getString(const char* str) {
  strcpy_P(sendBuffer, (char*) str);
  return sendBuffer;
}

void EEPROM_WriteLong(int address, long value) {
  byte four = (value & 0xFF);
  byte three = ((value >> 8) & 0xFF);
  byte two = ((value >> 16) & 0xFF);
  byte one = ((value >> 24) & 0xFF);
  EEPROM.update(address, four);
  EEPROM.update(address + 1, three);
  EEPROM.update(address + 2, two);
  EEPROM.update(address + 3, one);
}

long EEPROM_ReadLong(int address) {
  long four = EEPROM.read(address);
  long three = EEPROM.read(address + 1);
  long two = EEPROM.read(address + 2);
  long one = EEPROM.read(address + 3);
  return ((four << 0) & 0xFF) + ((three << 8) & 0xFFFF) + ((two << 16) & 0xFFFFFF) + ((one << 24) & 0xFFFFFFFF);
}