/*
 * Copyright Paul Warren 2019 <pwarren@pwarren.id.au>
 * 
 * Released under the terms of the GPLv3 license.
 *
 *D1Mini: Colour  : UART Module
 *    D5: White   : DOUT
 *    D6: Green   : DIN
 *    D7: Yellow  : Wake Up
 *    D8: Blue    : Reset
 *
 *
 */
#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Timezone.h>
#include <ArduinoJson.h>

#include "epd.h"
#include "credential.h"


// BOM, see bom_rrd2.py
const String ds_url = "http://pwarren.id.au/darksky/bom.json";  

bool ds_done = false;

// Timezone bits

// Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660};    // UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600};    // UTC + 10 hours
Timezone ausET(aEDT, aEST);
TimeChangeRule *tcr;


// when to wake up the display and get going
int wakeup_seconds = 50;


// Network clients
WiFiClient wfclient;
WiFiUDP ntpUDP;
HTTPClient http;
NTPClient timeClient(ntpUDP, "pi.lan", 0, 60000); //use internal ntp server, update every 10 minutes, taking in to account the 9 seconds it takes to update the screen for the current time

//NTPClient timeClient(ntpUDP, "au.pool.ntp.org", 0, 60000); //use external ntp server, update every 10 minutes

// defined in "credentials.h"
const char* ssid      = ssid_name;      // The SSID (name) of your Wi-Fi network
const char* password  = ssid_pass;      // The password of the Wi-Fi network

const int led = LED_BUILTIN;

// Font width
int dwidth = 185;

// yes this seems backwards but it works...
void ledOn(void)  { digitalWrite(led, LOW);} 
void ledOff(void) { digitalWrite(led, HIGH);}
void wait(void)   { delay(1500);}


// globals for the temperature :(
const char* current_outside;
const char* current_bedroom;
const char* current_lounge;

void setup() {
  pinMode(led, OUTPUT);
  ledOff();

  wifi_set_sleep_type(LIGHT_SLEEP_T);

  epd_init();
  epd_wakeup();
  epd_set_memory(MEM_NAND);
  epd_set_color(BLACK, WHITE);
  epd_set_en_font(ASCII64);
  epd_clear();
  
  Serial.begin(115200);                     // Start serial communication
  Serial.setTimeout(2000);
  while (!Serial) { }
  Serial.println("\r\n\r\nSTARTING\r\n");
  
  wifi();
  timeClient.begin();
  ntpUpdate();
  /* weatherUpdate();
  printDateTime(1);

  epd_update();
  wait(); */
  ledOff();
}

void wifi() {
  WiFi.persistent(false);
  WiFi.mode(WIFI_OFF);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);                         // Access WiFi
   
  Serial.print("Connecting to ");
  Serial.print(ssid);
  Serial.print(" ...");

  while (WiFi.status() != WL_CONNECTED) {   // Wait for WiFi to connect
    delay(250);
    Serial.print(".");
  }

  Serial.println('\n');
  Serial.println("WiFi connection established");
  Serial.print("Device's IP address is ");
  Serial.println(WiFi.localIP());                        // Show device's IP address
}

void ntpUpdate() {
  if (timeClient.update()) {  
    setTime(timeClient.getEpochTime());
  }
}


void disp_digit(int digit, int xpos){
  // digit: digit to display
  // xpos: X digit position, indexed at 1

  int dpos = 0;
  int ypos = 0;
  dpos = ((xpos - 1) * dwidth);
  if (xpos > 2) {
    dpos += 60; // account for colon
  }  
    switch (digit) {
    case 0:
      epd_disp_bitmap("0.JPG", dpos, ypos);
      break;
    case 1:
      epd_disp_bitmap("1.JPG", dpos, ypos);
      break;
    case 2:
      epd_disp_bitmap("2.JPG", dpos, ypos);
      break;
    case 3:
      epd_disp_bitmap("3.JPG", dpos, ypos);
      break;
    case 4:
      epd_disp_bitmap("4.JPG", dpos, ypos);
      break;
    case 5:
      epd_disp_bitmap("5.JPG", dpos, ypos);
      break;
    case 6:
      epd_disp_bitmap("6.JPG", dpos, ypos);
      break;
    case 7:
      epd_disp_bitmap("7.JPG", dpos, ypos);
      break;
    case 8:
      epd_disp_bitmap("8.JPG", dpos, ypos);
      break;
    case 9:
      epd_disp_bitmap("9.JPG", dpos, ypos);
      break;
  }
   // wait();
}

void disp_time(int hours, int minutes) {
  
  // first digit of hours
  if (hours > 19) {
     disp_digit(2, 1);
     hours -= 20;
  } else if (hours > 9) {
    disp_digit(1, 1);
    hours -= 10;
  } else {
    disp_digit(0, 1);
  } 

  // second hours digit
  disp_digit(hours, 2);

  epd_disp_bitmap("COLO.JPG", 2 * dwidth, 0);
  delay(500);

  // first minutes digit
  if (minutes > 49) {
    disp_digit(5, 3);
    minutes -= 50;
  } else if (minutes > 39) {
    disp_digit(4, 3);
    minutes -= 40;
  } else if (minutes > 29) {
    disp_digit(3, 3);
    minutes -= 30;
  } else if (minutes > 19) {
    disp_digit(2, 3);
    minutes -= 20;
  } else if (minutes > 9) {
    disp_digit(1, 3);
    minutes -= 10;
  } else {
    disp_digit(0, 3);
  }

  // second minutes digit 
  disp_digit(minutes, 4);
  
}


void printDateTime(int update_epd)
{
  char buf[64];
  
  time_t t;    
  t = ausET.toLocal(now()+60, &tcr);

  char m[4];    // temporary storage for month string (DateStrings.cpp uses shared buffer)
  strncpy(m, monthShortStr(month(t)), 4);
  sprintf(buf, "%.2d:%.2d:%.2d", hour(t), minute(t), second(t));
  Serial.print(buf); Serial.print(" ");
  
  if (update_epd > 0) {
    Serial.println("Adding Time");
    disp_time(hour(t), minute(t));
  }
  
  sprintf(buf, "%s %.2d %s %d",
	  dayShortStr(weekday(t)),
	  day(t),
	  m,
	  year(t));
	  
  Serial.println(buf);
  
  if (update_epd > 0) {
    Serial.println("Adding Date");
    epd_set_en_font(ASCII64);
    epd_disp_string(buf, 50, 225);
  }  
}

void weatherUpdate(void) {
  char buf[64];
  const size_t capacity = 4*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(4) + 150;
  DynamicJsonDocument jsonBuffer(capacity);
  
  http.begin(wfclient, ds_url);
  int httpCode = http.GET();
  
  if (httpCode > 0) {
    auto error = deserializeJson(jsonBuffer, http.getString());
    if (error) {
      Serial.print("Failed to parse json");
      return;
    } 
    http.end();
    
    const char* today_day = jsonBuffer["today"]["day"]; // "Wednesday"
    const char* today_icon = jsonBuffer["today"]["icon"]; // "sunny.jpg"
    int today_max = jsonBuffer["today"]["max"]; // "27"
    
    const char* tomorrow_day = jsonBuffer["tomorrow"]["day"]; // "Thursday"
    const char* tomorrow_icon = jsonBuffer["tomorrow"]["icon"]; // "sunny.jpg"
    int tomorrow_max = jsonBuffer["tomorrow"]["max"]; // "31"
    
    const char* day_after_day = jsonBuffer["day_after"]["day"]; // "Friday"
    const char* day_after_icon = jsonBuffer["day_after"]["icon"]; // "pcloud.jpg"
    int day_after_max = jsonBuffer["day_after"]["max"]; // "33"
    
    int current_outside = jsonBuffer["current"]["outside"];
    int current_bedroom = jsonBuffer["current"]["bedroom"];
    int current_lounge = jsonBuffer["current"]["lounge"];
    
    Serial.println("Updating EPD with weather");
    
    epd_set_en_font(ASCII64);
    
    //Today
    epd_disp_bitmap(today_icon, 0, 290);
    epd_disp_string(today_day, 0, 536);
    sprintf(buf, "%d", today_max);
    epd_disp_string(buf, 75, 460);

    //Tomorrow
    epd_disp_bitmap(tomorrow_icon, 267, 290); 
    epd_disp_string(tomorrow_day, 260, 536);
    sprintf(buf, "%d", tomorrow_max);
    epd_disp_string(buf, 352, 460);
    
    //Day After
    epd_disp_bitmap(day_after_icon, 533, 290); 
    epd_disp_string(day_after_day, 533, 536);
    sprintf(buf, "%d", day_after_max);
    epd_disp_string(buf, 615, 460);
    
    //Serial.println("Adding Temps");
    sprintf(buf, "E: %d   I: %d",
	    current_outside,
	    current_lounge);
    //Serial.println(buf);
    epd_disp_string(buf, 500, 225);
    
  } else {
    Serial.print("Error on HTTP Request: ");
    Serial.println(httpCode);
  }
  
}

void loop() {
  int sleep_seconds = 0;

  Serial.println("Back from sleep!");

  if (WiFi.status() != WL_CONNECTED) {
    wifi();
  }

  ntpUpdate();
  
  if (second(now()) >= wakeup_seconds) {
    epd_wakeup();
    epd_clear();
    weatherUpdate();
    Serial.print("Weather details sent: ");
    Serial.println(second(now()));
    printDateTime(1);    
    Serial.println("Waiting on minute to change");
    while (second(now()) != 0) {
      delay(500);
    }
   
  }
  Serial.println("EPD Update");
  epd_update();
  delay(4000);
  Serial.println("EPD Stop");
  epd_enter_stopmode();

  sleep_seconds = wakeup_seconds - second(now());

  if (sleep_seconds < 0) {
    sleep_seconds = 10;
  }
  
  Serial.print("Sleeping for: ");
  Serial.println(sleep_seconds);

  delay(sleep_seconds * 1000);
  
}
