/* Wemos8266RelaysLedDisplay/main.cpp
 * (C) Dirk Jahnke <dirk.jahnke@mailbox.org>
 */

#define COMPDATE __DATE__ __TIME__
// Following defines are set by build environment using -D parameter
// #define APP_VERSION "0.2.18"
// #define FS_UPDATE_BASE_URL "http://ota.iotjunkie.org/fc_client"
// #define FS_UPDATE_AUTH_USER "fcclient"
// #define FS_UPDATE_AUTH_PASSWORD "63Gs59PWveT6uQSh"

#include <Arduino.h>
#include <IOTAppStory.h>
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <FS.h>
#include "WebServer.h"
#include <ESP8266HTTPClient.h>
#include "Relays.h"
#include "Clock.h"
#include "Display.h"
#include "FSUpdater.h"

// Button pin on the esp for selecting modes. 0 for Generic devices!
#define MODEBUTTON D3
//#define RELAY1_PIN D1
//#define RELAY2_PIN D2
#define DEBUG_RELAYS false
#define DEBUG_DISPLAY false
#define STARTUP1_ANIMATION_DURATION_ms 1000
#define STARTUP2_ANIMATION_DURATION_ms 33000

IOTAppStory IAS(COMPDATE, MODEBUTTON);
String deviceName = "wemosMatrixDisplay";
String chipId;

Clock realTime("real");
Clock modelTime("Fremo");

Relays R;
WebServer W(&R, &realTime, &modelTime);
Display D(&realTime, &modelTime);

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);
FSUpdater fsUpdater(FS_UPDATE_BASE_URL, APP_VERSION, FS_UPDATE_AUTH_USER, FS_UPDATE_AUTH_PASSWORD);

#define mkstring(x) #x

// Field default values
#define CLOCK_NAME_LEN 8
#define CLOCK_SPEED_LEN 4
#define RELAY_PIN_LEN 2
#define RELAY_TIMING_LEN 3
#define CLOCK_NAME_DISPLAY_TIMING_LEN 5
static char def_clockName[CLOCK_NAME_LEN+1] = "FREMO";
static char def_clockSpeed_modelMsPerRealSec_String[CLOCK_SPEED_LEN+1] = "250";
static char def_relay1Pin_String[RELAY_PIN_LEN+1] = mkstring(D1);
static char def_relay2Pin_String[RELAY_PIN_LEN+1] = mkstring(D2);
static int relay1Pin = D1, relay2Pin = D2;
static char def_relayHoldTime_ms_String[RELAY_TIMING_LEN+1] = "200";
static char def_relayMinOffTime_ms_String[RELAY_TIMING_LEN+1] = "100";
// Clock Display Config Parameter
static char def_displayClockNameEvery_ms_String[CLOCK_NAME_DISPLAY_TIMING_LEN+1] = "16000";
static char def_displayClockNameDuration_ms_String[CLOCK_NAME_DISPLAY_TIMING_LEN+1] = "1200";

static char *clockName = def_clockName;
static char *clockSpeed_modelMsPerRealSec_String = def_clockSpeed_modelMsPerRealSec_String;
static char *relay1Pin_String = def_relay1Pin_String;
static char *relay2Pin_String = def_relay2Pin_String;
static char *relayHoldTime_ms_String = def_relayHoldTime_ms_String;
static char *relayMinOffTime_ms_String = def_relayMinOffTime_ms_String;
static char *displayClockNameEvery_ms_String = def_displayClockNameEvery_ms_String;
static char *displayClockNameDuration_ms_String = def_displayClockNameDuration_ms_String;


void setupIAS(void) {
  #if defined  ESP8266
    // creat a unique deviceName for classroom situations (deviceName-123)
    chipId      = String(ESP.getChipId());
    chipId      = "-"+chipId.substring(chipId.length()-3);
    deviceName += chipId;
  #endif

  // preset deviceName this is also your MDNS responder: http://deviceName.local
  IAS.preSetDeviceName(deviceName);
  IAS.preSetAppName(F("Wemos2RelaysMatrixDisplays"));
  IAS.preSetAppVersion(F(APP_VERSION));
  IAS.preSetAutoUpdate(true);

  // define fields
  IAS.addField(clockName, "Clock Name", CLOCK_NAME_LEN, 'T');
  IAS.addField(clockSpeed_modelMsPerRealSec_String, "Model MilliSec per Real Sec", CLOCK_SPEED_LEN, 'N');
  IAS.addField(displayClockNameEvery_ms_String, "Display clock name every (ms)", CLOCK_NAME_DISPLAY_TIMING_LEN, 'N');
  IAS.addField(displayClockNameDuration_ms_String, "Display clock name duration (ms)", CLOCK_NAME_DISPLAY_TIMING_LEN, 'N');
  IAS.addField(relay1Pin_String, "Pin Relay 1", RELAY_PIN_LEN, 'P');
  IAS.addField(relay2Pin_String, "Pin Relay 2", RELAY_PIN_LEN, 'P');
  IAS.addField(relayHoldTime_ms_String, "Relay hold time (ms)", RELAY_TIMING_LEN, 'N');
  IAS.addField(relayMinOffTime_ms_String, "Relay min off time (ms)", RELAY_TIMING_LEN, 'N');

  IAS.onModeButtonShortPress([]() {
    Serial.println(F(" If mode button is released, I will enter firmware update mode."));
    Serial.println(F("*----------------------------------------------------------------------*"));
    D.print("|updt");
  });

  IAS.onModeButtonLongPress([]() {
    Serial.println(F(" If mode button is released, I will enter configuration mode."));
    Serial.println(F("*----------------------------------------------------------------------*"));
    D.print("|cfg");
  });

  IAS.onFirstBoot([]() {
    Serial.println(F(" Manual reset necessary after serial upload!"));
    Serial.println(F("*----------------------------------------------------------------------*"));
    D.print("|rst");
    ESP.reset();
  });

  IAS.onConfigMode([]() {
    D.print(" WiFi");
    delay(400);
    D.print(":" + chipId);
    Serial.print(F("Entered config mode for Wifi, device=")); Serial.println(chipId);
  });

  IAS.onFirmwareUpdateCheck([]() {
    // D.print("chk upd");
    Serial.println(F("Firmware update check"));
  });

  IAS.onFirmwareUpdateDownload([]() {
    D.print("dl+instl");
    Serial.println(F("Download and install new firmware"));
  });

  IAS.onFirmwareUpdateError([]() {
    // D.print("Err fwu");
    Serial.println(F("Firmware update error"));
  });

  // Optional parameter: What to do with EEPROM on First boot of the app?
  // 'F' Fully erase | 'P' Partial erase(default) | 'L' Leave intact
  IAS.begin('L');
  delay(500);
  // Set to true to enable calling home frequently (disabled by default)
  IAS.setCallHome(true);
  // Call home interval in seconds, use 60s only for development.
  // Please change it to at least 2 hours in production
  IAS.setCallHomeInterval(120);
  // IAS.callHome(true /*SPIFFS-check*/);
  modelTime.setSpeed_modelMsPerRealSecond(atoi(clockSpeed_modelMsPerRealSec_String));
  relay1Pin = IAS.dPinConv(relay1Pin_String);
  relay2Pin = IAS.dPinConv(relay2Pin_String);
  R.setHoldTime_ms(atoi(relayHoldTime_ms_String));
  R.setMinOffTime_ms(atoi(relayMinOffTime_ms_String));
  D.setClockNameDisplayEvery_ms(atoi(displayClockNameEvery_ms_String));
  D.setClockNameDurationTime_ms(atoi(displayClockNameDuration_ms_String));

  Serial.println(F("Configuration used:"));
  Serial.print(F("Relay1 Pin: ")); Serial.println(relay1Pin);
  Serial.print(F("Relay2 Pin: ")); Serial.println(relay2Pin);
  Serial.print(F("Relay hold time (ms): ")); Serial.println(relayHoldTime_ms_String);
  Serial.print(F("Relay min off time (ms): ")); Serial.println(relayMinOffTime_ms_String);
  Serial.print(F("Clock speed (model ms per real time s): ")); Serial.println(clockSpeed_modelMsPerRealSec_String);
  Serial.print(F("Show clock name every (ms): ")); Serial.println(displayClockNameEvery_ms_String);
  Serial.print(F("Show clock name for (ms): ")); Serial.println(displayClockNameDuration_ms_String);
}


void setupFS() {
  if(!SPIFFS.begin()){
    Serial.println(F(" SPIFFS Mount Failed"));
    return;
  }
}

void setup(void)
{
  Serial.println(F("setup():"));
  D.begin();
  setupFS();
  setupIAS();
  W.begin();
  W.setIAS(&IAS);
  delay(100);
  R.begin(relay1Pin, relay2Pin);
  timeClient.begin();
  Serial.println(F("setup() finished"));
}


static bool timeClientInitialized = false;
static unsigned long lastTimeOutput_ms = 0;
#define TIME_BETWEEN_REALTIME_UPDATE_ms 60000


void loop(void)
{
  static unsigned int lastMinutes = 0;
  static unsigned long lastIASLoop_ts = 0;
  #define CALL_IAS_EVERY_ms 100

  static unsigned long firstLoop_ts = 0;
  if (firstLoop_ts == 0) firstLoop_ts = millis();

  if (!timeClientInitialized && WiFi.status() == WL_CONNECTED) {
    timeClient.begin();
    timeClientInitialized = true;
    timeClient.update();
    Serial.println(timeClient.getFormattedTime());
    lastTimeOutput_ms = millis();
  }
  if (millis() - lastIASLoop_ts > CALL_IAS_EVERY_ms) {
    IAS.loop();
    lastIASLoop_ts = millis();
  }

  if (timeClientInitialized && millis()-lastTimeOutput_ms > TIME_BETWEEN_REALTIME_UPDATE_ms) {
    timeClient.update();
    Serial.print("t-update: ");
    Serial.println(timeClient.getFormattedTime());
    lastTimeOutput_ms = millis();
  }

  if (millis() < firstLoop_ts + STARTUP1_ANIMATION_DURATION_ms) {
    // Startup phase 1: Constant display of content, created during setup()
    return;
  }
  if (millis() < firstLoop_ts + STARTUP1_ANIMATION_DURATION_ms + STARTUP2_ANIMATION_DURATION_ms) {
    D.loopEyeAnimation();
    return;
  }

  if (timeClientInitialized) {
    realTime.setTime(timeClient.getHours(), timeClient.getMinutes(), timeClient.getSeconds());
  } else {
    realTime.setTime((millis() / 60 * 60 * 1000) % 24, (millis() / 60 * 1000) % 60, (millis() / 1000) % 60);
  }

  D.loop();

  // toggle relays on minute change
  if (lastMinutes != realTime.getMinutes()) {
    R.toggle();
    lastMinutes = realTime.getMinutes();
  }

  R.loop();
}
