#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <NTPClient.h>    // for calling a time server on the Internet - the microcontroller does not have a clock
#include <FastLED.h>      // for controlling the LED strip
#include <TimeLib.h>
#include "ClockDisplay.h"

// for OTA (to update the microcontroller over the air)
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

#define NUMPIXELS 58 // Number of LEDs in strip 7*2*4+2

// define pins for the LED strip
#define DATAPIN    4
#define CLOCKPIN   5

#define BRIGHTNESS_DAY 80   // the brightness of the display in daytime
#define BRIGHTNESS_NIGHT 1  // the brightness of the display at nighttime

#define HOSTNAME "ALARMCLOCK"

const char* ssid = "<YOUR SSID HERE>";
const char* password = "<YOUR PASSWORD HERE>";

// for time server
WiFiUDP Udp;
// By default 'pool.ntp.org' is used with 60 seconds update interval and
// no offset
//NTPClient timeClient(ntpUDP);
// You can specify the time server pool and the offset, (in seconds)
// additionally you can specify the update interval (in milliseconds).
// NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 3600, 60000);


#define TIME_OFFSET_WINTER 3600 // 3600 = central european time, normal time
#define TIME_OFFSET_DST 7200 // central european time, daylight savings time
NTPClient timeClient(Udp, "europe.pool.ntp.org", TIME_OFFSET_WINTER, 60000);
bool dstChecked = false;

int gFramesPerSecond = 20;

CRGB leds[NUMPIXELS];

ClockDisplay clockDisplay = ClockDisplay(); // defined in ClockDisplay.cpp/.h

void setup() {
  Serial.begin(9600);
  FastLED.addLeds<DOTSTAR, DATAPIN, CLOCKPIN, BGR>(leds, 0, NUMPIXELS);
  delay(50);

  setupWifi();
  timeClient.begin(); // start the time checking service
}

void setupWifi() {
  bool notConnected = true;
  WiFi.persistent(false);   // <-- "prevents flash wearing"    

  WiFi.hostname(HOSTNAME);
//    WiFi.config(ip, dns, gateway, subnet); // if not using DHCP
  WiFi.setSleepMode(WIFI_NONE_SLEEP);
  WiFi.mode(WIFI_STA);    
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.begin(ssid, password);
  }

  Serial.println("");
  Serial.println("WL_CONNECTED:" + String(WL_CONNECTED));
  Serial.println("WL_DISCONNECTED:" + String(WL_DISCONNECTED));

  int wifiTries = 30;
  while (WiFi.status() != WL_CONNECTED && wifiTries > 0)
  {
    delay(500);
    Serial.print(WiFi.status());
    int tryCounter = 30 - wifiTries;
    wifiTries--;
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    Serial.print("Gateway: ");
    Serial.println(WiFi.gatewayIP());
    Serial.print("Subnet: ");
    Serial.println(WiFi.subnetMask());
    Serial.print("MAC: ");
    Serial.println(WiFi.macAddress());
    
    notConnected = false;
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    //-------------OTA
    ArduinoOTA.setHostname(HOSTNAME);
    ArduinoOTA.onStart([]() {
      Serial.println("Start");
    });
    ArduinoOTA.onEnd([]() {
      Serial.println("\nEnd");
    });
    ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    });
    ArduinoOTA.onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });
    ArduinoOTA.begin();
    //---------------OTA end      
  }
}


// Display the time
void updateTime() { 
  time_t t = timeClient.getEpochTime();

  if (!dstChecked) { // check daylight savings time once... maybe should check at 2 am or 3 am each night - now we'll have to restart the clock when the daylight savings time starts or ends
    if (isDst(day(t), month(t), weekday(t))) {
      timeClient.setTimeOffset(TIME_OFFSET_DST); // if daylight savings time is active - set time new offset
    }
    dstChecked = true;
  }
  Serial.println(timeClient.getFormattedTime());
  if (hour(t) >= 23 || hour(t) < 7) { // if the time is between 11 at night and 7 in the morning - use very low brightness
    FastLED.setBrightness(BRIGHTNESS_NIGHT);
  }
  else {
    FastLED.setBrightness(BRIGHTNESS_DAY);
  }
  clockDisplay.ShowTime(t);
}

// Checking if daylight savings time (EU-rules)
bool isDst(int day, int month, int weekday) {
    if (month < 3 || month > 10)  return false; 
    if (month > 3 && month < 10)  return true; 

    int lastSunday = day - weekday;

    if (month == 3) return lastSunday >= 25;
    if (month == 10) return lastSunday < 25;

    return false; // this line is never gonna happen
}

void displayTime() {
  for (int i = 0; i < NUMPIXELS; i++) {    
    if (clockDisplay.PixelOn(i))
      leds[i] = CRGB::Green;
    else
      leds[i] = CRGB::Black;
  }
}

void setColor(CRGB color) {
  for (int i = 0; i < NUMPIXELS; i++) {    
    if (clockDisplay.PixelOn(i))
      leds[i] = color;
    else
      leds[i] = CRGB::Black;
  }
}

void loop() {
  EVERY_N_SECONDS(5) {ArduinoOTA.handle(); delay(10);} // checks for OTA (over-the-air update) once every 6 seconds

  timeClient.update(); // körs var n:e millisekund (som angivet i konstruktorn)
  EVERY_N_MILLISECONDS(500) { updateTime(); } // update the clock twice per second

  displayTime(); // updating the 'leds' array
  FastLED.show();  // send the 'leds' array out to the actual LED strip  
  FastLED.delay(1000/gFramesPerSecond); // insert a delay to keep the framerate modest    
}
