/*
  Idea by Schuppeste@googlemail.com
  Sources: https://github.com/schuppeste/Sofaleds
  Space: http://www.hackaday.io/Schuppeste
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include "FS.h"
#include "Time.h"
#include "TimeLib.h"
#include <SoftwareSerial.h>
#include <Wire.h>
#include "RTClib.h"

RTC_DS3231 rtc;
int SS_TXPin = D5;
int BWM_Pin = D7;

SoftwareSerial Serial1t(SW_SERIAL_UNUSED_PIN, SS_TXPin);
ESP8266WebServer server(80);

int citycount = 0;
int myintervall = 60;
int mymethod = 0;
const char signMessage[] PROGMEM = { };
long mainconfig[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
String citys[20] = { "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
                     "", "", "", "", ""
                   };
long gmt[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
int dst[20] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
String ssid = "";
String key = "";
String ip = "";
int waiting = 1000;
String NTPurl = "";
String setRow1 = "";
String setRow2 = "";
String olddate = "";
int col = 0;
int steps = 0;
int cursor1 = -1;
int cursor2 = -2;
int cursor3 = -3;

boolean updated = false;
boolean startanim = false;
unsigned long lastupdate = millis();

byte helper1[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
                   0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13
                 };
byte helper2[] = { 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
                   0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27
                 };
IPAddress local_IP(192, 168, 4, 22);
IPAddress gateway(192, 168, 4, 9);
IPAddress subnet(255, 255, 255, 0);
boolean startAP = false;
int tzoffset = 0;

void setup() {
  pinMode(D7, INPUT);
  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1)
      ;
  }

  int apstatus = HIGH;
  Serial.begin(9600);
  if (!SPIFFS.begin()) {
    Serial.println("Failed to mount file system");
  } else if (loadConfig()) {
  }
  WiFi.hostname("weltuhr");
  Serial.println(ssid + key);
  if (ssid == "" || key == "" || apstatus == LOW) {
    WiFi.softAP("weltuhr", "weltuhr"); //Create Access Point
    WiFi.softAPConfig(local_IP, gateway, subnet);
  } else {
    WiFi.begin((const char*) ssid.c_str(), (const char*) key.c_str());
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }
  }
  server.on("/", handleRoot);
  server.on("/index.html", handleRoot);
  server.serveStatic("/config.json", SPIFFS, "/config.json");
  server.serveStatic("/stylesheet.css", SPIFFS, "/stylesheet.css");
  server.begin(); //Start the server
  startAP = true;
  waiting = mainconfig[0];
  myintervall = mainconfig[1];

  Serial1t.begin(9600);
  Serial1t.write(0x1B);
  Serial1t.write(0x05);
  Serial1t.write(0x1B);
  Serial1t.write(0x0A);
  Serial1t.write(0x1B);
  Serial1t.write(0x17);
  Serial1t.write(mainconfig[6] + 1);
  //WiFi.mode(WIFI_OFF);
  if (myintervall == 0)
    myintervall = 60 * 1000;
  setSyncProvider (rtcsync);
  setSyncInterval(500);
}

boolean first = true;
boolean waitprogress = false;
unsigned long lastwaitprogress = 0;
boolean updatewaitprogress = false;
unsigned long lastupdatewaitprogress = 0;
boolean cyclewaitprogress = false;
unsigned long lastcyclewaitprogress = 0;
boolean screensaver = false;
unsigned long lastscreensaver = 0;
int cycles = 0;
int cityscount = 0;
unsigned long speedupdate = 200;
boolean bwm_active = false;
void loop() {
  unsigned long current  Millis = millis();
  int tempbwm = digitalRead(D7);
  if ((tempbwm == HIGH && screensaver ) || (tempbwm == HIGH && cyclewaitprogress)) {
    lastscreensaver = currentMillis;
    screensaver = false;
    cyclewaitprogress = false;
     Serial1t.write(0x1B);
      Serial1t.write(0x05);
    Serial.println("BWM");
  }
  //Between Cities
  if (currentMillis - lastwaitprogress >= (myintervall * 1000) || first) {
    lastwaitprogress = currentMillis;
    waitprogress = false;
  }
  //update
  if (currentMillis - lastupdatewaitprogress >= speedupdate) {
    lastupdatewaitprogress = currentMillis;
    updatewaitprogress = false;
  }
  //Between Cycle Wait
  if (currentMillis - lastcyclewaitprogress >= mainconfig[0] * 1000) {
    lastcyclewaitprogress = currentMillis;
    cyclewaitprogress = false;
    //Serial1t.write(0x1B); Serial1t.write(0x05);
  }
  if (currentMillis - lastscreensaver >= mainconfig[5] * 1000
      && cyclewaitprogress && mainconfig[3] == 1) {
    lastscreensaver = currentMillis;
    screensaver = true;
    Serial1t.write(0x1B);
    Serial1t.write(0x0B);
     Serial1t.write(0x1B);
  Serial1t.write(0x17);
  Serial1t.write(mainconfig[6] + 1);
  }
  //---------------------------------------------------------Animation-----------------------------------------------------------------
  if (!screensaver)
    if (cycles < mainconfig[4]) {

      if (!cyclewaitprogress) {
        lastcyclewaitprogress = currentMillis;

        if (!startanim && !waitprogress) {
          cityscount++;
          if (citys[cityscount] == "") {
            cityscount = 0;
            cycles++;
          }

          if (mainconfig[2] == 0)
            tzoffset = 0;
          else if (mainconfig[2] == 1)
            tzoffset = dst[cityscount] * 3600; //manually add +1 for Germany (too many different choices to make Options)
          else if (mainconfig[2] == 2)
            if (summertime_EU)
              tzoffset = dst[cityscount] * 3600; //automatically add +1 for Germany (too many different choices to make Options)
          //Check viewed Clock on Wait to update without Animation
          olddate = centerString(
                      createDateTime(now(), tzoffset + gmt[cityscount]));
          prepareAnimation(centerString(citys[cityscount]), olddate);
          waitprogress = true;
          //Power on first Cycle
          if (first)
            first = false;
        }
      }
    } else {
      cycles = 0;
      cyclewaitprogress = true;
      Serial1t.write(0x1B);
      Serial1t.write(0x06);
    }
  if (startanim && !updatewaitprogress) {
    updatewaitprogress = true;
    updateAnimation();
    lastcyclewaitprogress = currentMillis;
  }
  String testdate = createDateTime(now(), tzoffset + gmt[cityscount]);
  if (olddate != testdate && !startanim) {
    replaceonlytime(tzoffset + gmt[cityscount] + now());
    olddate = testdate;
  }
  server.handleClient();
  delay(20);          //Handling of incoming requests

}
void handleRoot() {
  if (server.arg("setzen") != "") {   //Parameter not found
    rtc.adjust(server.arg("unixtimeset").toInt());
    Serial.println("Setzen");
    File indexFile = SPIFFS.open("/index.html", "r");
    if (!indexFile) {
      //Serial.println("Failed to open config file");
    }
    size_t sent = server.streamFile(indexFile, "text/html");
    indexFile.close();
  } else if (server.arg("network") != "") {   //Parameter not found
    ssid = const_cast<char*>(server.arg("ssid").c_str());
    key = const_cast<char*>(server.arg("key").c_str());
    ip = const_cast<char*>(server.arg("ip").c_str());
    saveConfig();
  } else if (server.arg("speichern") != "") {
    mainconfig[0] = server.arg("waiting").toInt();
    mainconfig[1] = server.arg("myintervall").toInt();
    mainconfig[2] = server.arg("mod").toInt();
    server.arg("bwm") == "on" ? mainconfig[3] = 1 : mainconfig[3] = 0;
    mainconfig[4] = server.arg("cycles").toInt();
    mainconfig[5] = server.arg("screensaver").toInt();
    mainconfig[6] = server.arg("brightness").toInt();
    citys[0] = server.arg("s0");
    citys[1] = server.arg("s1");
    citys[2] = server.arg("s2");
    citys[3] = server.arg("s3");
    citys[4] = server.arg("s4");
    citys[5] = server.arg("s5");
    citys[6] = server.arg("s6");
    citys[7] = server.arg("s7");
    citys[8] = server.arg("s8");
    citys[9] = server.arg("s9");
    citys[10] = server.arg("s10");
    citys[11] = server.arg("s11");
    citys[12] = server.arg("s12");
    citys[13] = server.arg("s13");
    citys[14] = server.arg("s14");
    citys[15] = server.arg("s15");
    citys[16] = server.arg("s16");
    citys[17] = server.arg("s17");
    citys[18] = server.arg("s18");
    citys[19] = server.arg("s19");

    gmt[0] = server.arg("z0").toInt();
    gmt[1] = server.arg("z1").toInt();
    gmt[2] = server.arg("z2").toInt();
    gmt[3] = server.arg("z3").toInt();
    gmt[4] = server.arg("z4").toInt();
    gmt[5] = server.arg("z5").toInt();
    gmt[6] = server.arg("z6").toInt();
    gmt[7] = server.arg("z7").toInt();
    gmt[8] = server.arg("z8").toInt();
    gmt[9] = server.arg("z9").toInt();
    gmt[10] = server.arg("z10").toInt();
    gmt[11] = server.arg("z11").toInt();
    gmt[12] = server.arg("z12").toInt();
    gmt[13] = server.arg("z13").toInt();
    gmt[14] = server.arg("z14").toInt();
    gmt[15] = server.arg("z15").toInt();
    gmt[16] = server.arg("z16").toInt();
    gmt[17] = server.arg("z17").toInt();
    gmt[18] = server.arg("z18").toInt();
    gmt[19] = server.arg("z19").toInt();

    server.arg("dst0") == "on" ? dst[0] = 1 : dst[0] = 1;
    server.arg("dst1") == "on" ? dst[1] = 1 : dst[1] = 0;
    server.arg("dst2") == "on" ? dst[2] = 1 : dst[2] = 0;
    server.arg("dst3") == "on" ? dst[3] = 1 : dst[3] = 0;
    server.arg("dst4") == "on" ? dst[4] = 1 : dst[4] = 0;
    server.arg("dst5") == "on" ? dst[5] = 1 : dst[5] = 0;
    server.arg("dst6") == "on" ? dst[6] = 1 : dst[6] = 0;
    server.arg("dst7") == "on" ? dst[7] = 1 : dst[7] = 0;
    server.arg("dst8") == "on" ? dst[8] = 1 : dst[8] = 0;
    server.arg("dst9") == "on" ? dst[9] = 1 : dst[9] = 0;
    server.arg("dst10") == "on" ? dst[10] = 1 : dst[10] = 0;
    server.arg("dst11") == "on" ? dst[11] = 1 : dst[11] = 0;
    server.arg("dst12") == "on" ? dst[12] = 1 : dst[12] = 0;
    server.arg("dst13") == "on" ? dst[13] = 1 : dst[13] = 0;
    server.arg("dst14") == "on" ? dst[14] = 1 : dst[14] = 0;
    server.arg("dst15") == "on" ? dst[15] = 1 : dst[15] = 0;
    server.arg("dst16") == "on" ? dst[16] = 1 : dst[16] = 0;
    server.arg("dst17") == "on" ? dst[17] = 1 : dst[17] = 0;
    server.arg("dst18") == "on" ? dst[18] = 1 : dst[18] = 0;
    server.arg("dst19") == "on" ? dst[19] = 1 : dst[19] = 0;
    Serial1t.write(0x1B);
    Serial1t.write(0x17);
    Serial1t.write(mainconfig[6] + 1);
    waiting = mainconfig[0];
    myintervall = mainconfig[1];
    mymethod = mainconfig[2];
    saveConfig();
    File configFile = SPIFFS.open("/index.html", "r");
    if (!configFile) {
      //Serial.println("Failed to open config file");
    }
    size_t sent = server.streamFile(configFile, "text/html");
    configFile.close();

  } else {
    File indexFile = SPIFFS.open("/index.html", "r");
    if (!indexFile) {
      Serial.println("Failed to open config file");
    }
    size_t sent = server.streamFile(indexFile, "text/html");
    indexFile.close();
  }
}

bool loadConfig() {
  File configFile = SPIFFS.open("/config.json", "r");
  if (!configFile) {
    //Serial.println("Failed to open config file");
    return false;
  }
  size_t size = configFile.size();
  if (size > 3000) {
    //Serial.println("Config file size is too large");
    return false;
  }
  std::unique_ptr<char[]> buf(new char[size]);
  configFile.readBytes(buf.get(), size);
  StaticJsonBuffer<3000> jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(buf.get());
  if (!json.success()) {
    Serial.println("Failed to parse config file");
    return false;
  }
  const char *string_table[20];
  json["citys"].as<JsonArray>().copyTo(string_table);
  for (int i = 0; i < 19; i++) {
    citys[i] = String(string_table[i]);
  }
  json["gmt"].as<JsonArray>().copyTo(gmt);
  json["dst"].as<JsonArray>().copyTo(dst);
  json["mainconfig"].as<JsonArray>().copyTo(mainconfig);
  ssid = json["ssid"].as<String>();
  key = json["key"].as<String>();
  ip = json["ip"].as<String>();
  NTPurl = json["ntpurl"].as<String>();
  configFile.close();
  return true;
}

bool saveConfig() {
  StaticJsonBuffer<2000> jsonBuffer;
  JsonObject& root = jsonBuffer.createObject();
  JsonArray& array4 = root.createNestedArray("mainconfig");
  array4.copyFrom(mainconfig);
  JsonArray& array1 = root.createNestedArray("citys");
  array1.copyFrom(citys);
  JsonArray& array2 = root.createNestedArray("gmt");
  array2.copyFrom(gmt);
  JsonArray& array3 = root.createNestedArray("dst");
  array3.copyFrom(dst);
  root["ssid"] = ssid;
  root["key"] = key;
  root["ip"] = ip;
  root["ntpurl"] = NTPurl;
  File configFile = SPIFFS.open("/config.json", "w");
  if (!configFile) {
    //Serial.println("Failed to open config file for writing");
    return false;
  }
  root.printTo(configFile);
  configFile.close();
  return true;
}

void prepareAnimation(String newRow1, String newRow2) {
  setRow1 = newRow1;
  setRow2 = newRow2;
  startanim = true;
}

void updateAnimation() {
  //Serial1t
  if (startanim) {
    if (cursor1 >= 0 && cursor1 <= 19) {
      setCharAtvfd(1, cursor1, 0xB2);
      setCharAtvfd(2, cursor1, 0xB2);
    }
    if (cursor2 >= 0 && cursor2 <= 19) {
      setCharAtvfd(1, cursor2, 0xB1);
      setCharAtvfd(2, cursor2, 0xB1);
    }
    if (cursor3 >= 0 && cursor3 <= 19) {
      setCharAtvfd(1, cursor3, 0xB0);
      setCharAtvfd(2, cursor3, 0xB0);
    }
    cursor1++;
    cursor2++;
    cursor3++;
    if (cursor3 > 1) {
      setCharAtvfd(1, steps, setRow1.charAt(steps));
      setCharAtvfd(2, steps, setRow2.charAt(steps));
      steps++;
    }
  }
  if (steps > 19)   //20
  {
    steps = 0;
    startanim = false;
    cursor1 = -1;
    cursor2 = -2;
    cursor3 = -3;
  }
}

String createDateTime(time_t tt, int offset) {
  time_t t = offset + tt;
  String t_str = " ";
  if (day(t) < 10)
    t_str += "0";
  t_str += day(t);
  t_str += ".";
  if (month(t) < 10)
    t_str += "0";
  t_str += month(t);
  t_str += ".";
  t_str += year(t);
  t_str += "   ";
  if (hour(t) < 10)
    t_str += "0";
  t_str += hour(t);
  t_str += ":";
  if (minute(t) < 10)
    t_str += "0";
  t_str += minute(t);
  return t_str;
}

void setCharAtvfd(int row, int pos, int setchar) {
  byte setcellcommand = 0;
  if (row == 1) {
    setcellcommand = helper1[pos];
  } else {
    setcellcommand = helper2[pos];
  }
  Serial1t.write(0x1B);
  Serial1t.write(0x13);
  Serial1t.write(setcellcommand);
  Serial1t.write(setchar);
}

String centerString(String text) {
  String returns = "";
  int lengths = text.length();
  int rest = 20 - lengths;
  float siderest = rest / 2;
  siderest = (int) floor(siderest);
  for (int u = 0; u < siderest; u++)
    returns += " ";
  returns += text;
  for (int u = returns.length(); u < 20; u++)
    returns += " ";
  return returns;
}

time_t rtcsync() {
  return rtc.now().unixtime();
}

void replaceonlytime(time_t t) {
  String t_str = "";
  int myhour = hour(t);
  int myminute = minute(t);
  if (myhour < 10)
    t_str += "0";
  t_str += myhour;
  t_str += ":";
  if (myminute < 10)
    t_str += "0";
  t_str += myminute;
  int myset = 0;
  for (int s = 14; s < 19; s++) {
    Serial1t.write(0x1B);
    Serial1t.write(0x13);
    Serial1t.write(helper2[s]);
    Serial1t.write(t_str.charAt(s - 14));
    myset++;
  }
}

static int x1, x2, lastyear; // Zur Beschleunigung des Codes ein Cache für einige statische Variablen
static byte lasttzHours;
int x3;
String inString = "";
boolean complete = false;
unsigned long latest = 0;

boolean summertime_EU() {
  if (month() < 3 || month() > 10)
    return false; // keine Sommerzeit in Jan, Feb, Nov, Dez
  if (month() > 3 && month() < 10)
    return true; // Sommerzeit in Apr, Mai, Jun, Jul, Aug, Sep
  if (year() != lastyear || 0 != lasttzHours) { // Umstellungsbeginn und -ende
    x1 = 1 + 0 + 24 * (31 - (5 * year() / 4 + 4) % 7);
    x2 = 1 + 0 + 24 * (31 - (5 * year() / 4 + 1) % 7);
    lastyear = year();
    lasttzHours = 0;
  }
  x3 = hour() + 24 * day();
  if (month() == 3 && x3 >= x1 || month() == 10 && x3 < x2)
    return true;
  else
    return false;
}