//MULTICORE TESTING
#include <WiFi.h>
#include <Wire.h>
#include "RTClib.h"
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <Preferences.h>
#include <LiquidCrystal_I2C.h>

#define upDown 19
#define lockPin 18
#define triggerUp 5
#define triggerDown 25

LiquidCrystal_I2C LCD = LiquidCrystal_I2C(0x27, 20, 4);

Preferences pref;
bool wifiConnected = false;
String wifiPassword;
String wifiUsername; // Username for WPA2-Enterprise
String wifiIdentity; // Identity for WPA2-Enterprise
String wifiSSID;

RTC_DS1307 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

#define NTP_SERVER     "pool.ntp.org"
#define UTC_OFFSET     0
#define UTC_OFFSET_DST 0

DateTime now;
DateTime shutTime;
DateTime openTime;
DateTime lastResync;
int timeSinceLastResync;


String syncedTime;
String jsonOpenTime;
String jsonShutTime;

SemaphoreHandle_t baton;

const String duskTimeApi = "https://api.sunrisesunset.io/json?lat=-35.06042984189702&lng=138.85387414368216&timezone=UTC&date=today";

TaskHandle_t TaskHandle_0;
TaskHandle_t Task2;
int btnState;
int btnState2;

enum doorstate
{
  OPEN,
  CLOSED,
  CALIBRATING,
  BROKEN,
  UP,
  DOWN,
};
doorstate dstate;

byte chicken[] = {
  B00000,
  B00000,
  B00110,
  B00111,
  B01100,
  B11100,
  B01000,
  B01000
};

byte open1[] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00001,
  B00010,
  B00100,
  B01000
};

byte open2[] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B00000,
  B00000,
  B00000
};

byte open3[] = {
  B10000,
  B01000,
  B00100,
  B00010,
  B00001,
  B00000,
  B00000,
  B00000
};

byte open4[] = {
  B00001,
  B00001,
  B00001,
  B00001,
  B00001,
  B00000,
  B00000,
  B00000
};

uint8_t dot[] = {
  B00000,
  B00100,
  B01110,
  B11111,
  B11111,
  B10001,
  B10001,
  B11111
};



void setup() 
{
  LCD.init();
  LCD.backlight();
  LCD.setCursor(0, 0);
  Serial.begin(115200);
  pinMode(triggerUp, INPUT_PULLUP);
  pinMode(triggerDown, INPUT_PULLUP);
  pinMode(upDown, OUTPUT);
  pinMode(lockPin, OUTPUT);

  pref.begin("savedWifiCredentials", false);

  if (!rtc.begin()) {
    Serial.println("Couldn't find RTC");
    abort();
  }
    
    xTaskCreate(
      dostuff, // Function to implement the task 
      "otherThings", // Name of the task 
      10000,  // Stack size in words 
      NULL,  // Task input parameter 
      1,  // Priority of the task 
      &TaskHandle_0  // Task handle. 
      );
      
    xTaskCreate(
      startUp, /* Function to implement the task */
      "Connect To Internet", /* Name of the task */
      10000,  /* Stack size in words */
      NULL,  /* Task input parameter */
      2,  /* Priority of the task */
      &TaskHandle_0  /* Task handle. */
      );
}


void openingAnimation()
{
  LCD.createChar(1, chicken);
  LCD.createChar(2, dot);
  LCD.setCursor(2, 0);
  LCD.print("Door opening");
  LCD.setCursor(1, 1);
  LCD.print("Rise and shine!");
  
  for (int i = 3; i < 16; i++) {
    LCD.setCursor(i, 3);
    LCD.print("\1");
    for (int j = i + 1; j < 16; j++) {
      LCD.setCursor(j, 3);
      LCD.print("\2");
    }
    delay(200);
    LCD.setCursor(i, 3);
    LCD.print(" ");
  }
  for (int i = 3; i < 16; i++) 
  {
  LCD.setCursor(i, 3);
  LCD.print("\2");
  delay(50);
  }
  delay(1000);
}

void closingAnimation()
{
    LCD.createChar(1, open1);
  LCD.createChar(2, open2);
  LCD.createChar(3, open3);
  LCD.createChar(4, open4);
  LCD.setCursor(2, 0);
  LCD.print("Door closing");
  LCD.setCursor(1, 1);
  LCD.print("Mind your feathers!");
  LCD.setCursor(0, 2);
  printLine("\1");
  delay(1000);
  printLine("\2");
  delay(1000);
  printLine("\3");
  delay(1000);
  printLine("\4");
  delay(1000);
}

void printLine(String txt)
{
  for(int i=0; i!=20; i++)
  {
    LCD.setCursor(i, 2);    
    LCD.print(txt);
    LCD.setCursor(i, 3);  
    LCD.print(txt);  
  }
}


void dostuff(void * pvParameters)
{

 Serial.print("time: ");
  Serial.println(xPortGetCoreID());
  
  while(true){
  if(now.hour() >= shutTime.hour() && now.minute() >= shutTime.minute())
  {
    dstate = CLOSED;
  }

  else if(now.hour() >= openTime.hour() && now.minute() >= openTime.minute())
  {
   dstate = OPEN;
  }

  timeSinceLastResync = now.minute() - lastResync.minute();
  //Serial.println(timeSinceLastResync);

  if(timeSinceLastResync == 2)
  {
    resyncClocks();
    dstate = CALIBRATING;
  }

  /*
  Serial.print("Now valid ");
  Serial.println(now.isValid());
  Serial.print("shuttime valid ");
  Serial.println(shutTime.isValid());
  Serial.print("hours match ");
  Serial.println(now.hour() >= shutTime.hour());
  Serial.print("Minutes match ");
  Serial.println( now.minute() >= shutTime.minute());
  Serial.println(shutTime.minute());
  Serial.println(now.minute());
  */

  delay(500);
  }
}
  

void syncOpenShutTime() {
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Fetching duskTime...");
    //connects to internet
    WiFiClientSecure client;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, duskTimeApi);

    //connects to api website
    int httpResponseCode = http.GET();
    if (httpResponseCode > 0) {
      //if everything goes right it'll proceed
      Serial.print("HTTP ");
      Serial.print(httpResponseCode);
      String payload = http.getString();
      Serial.println();
      Serial.println(payload);

      //converts json data into string
      //if errors deserializing check if size is too low
      DynamicJsonDocument doc(2048);
      DeserializationError error = deserializeJson(doc, payload);

      if (error) {
        Serial.print("deserializeJson() failed: ");
        Serial.println(error.c_str());
        ESP.restart();
      }

      //first indicates object the times are inside of
      JsonObject results = doc["results"];
      //then sets variables as variable name
      jsonShutTime = results["dusk"].as<String>();
      Serial.println(jsonShutTime);
      jsonOpenTime = results["sunrise"].as<String>();
      Serial.println(jsonOpenTime);


      shutTime = convertToDateTime(jsonShutTime.c_str(), now);
      openTime = convertToDateTime(jsonOpenTime.c_str(), now);
    }
    else
    {
      Serial.println("FATAL ERR OCCURRED");
      switch(httpResponseCode)
      {
        case 400:
        Serial.println("Bad request");
        break;
        case 401:
        Serial.println("Unauthorized");
        break;
      }
    }
  } else {
    Serial.print("Connection error!");
    Serial.println("Attempting to reconnect...");
  }
    if(now.hour() < openTime.hour() && now.minute() < openTime.minute())
  {
   dstate = OPEN;
  }
}

DateTime convertToDateTime(const char* timeStr, DateTime now) {
/*
  **************
  ****SYNTAX****
  **************

  strdup = duplicate string
  strtok = sets token variable to everything before the semicolon
  atoi = conversion from char to int
  free = free up unused memory
*/


  // Split the time string up into hours and minutes
  char* timeCopy = strdup(timeStr);
  char* token = strtok(timeCopy, " :");
  Serial.println(timeCopy);
  int hour = atoi(token);
  token = strtok(NULL, " :");
  int minute = atoi(token);
  token = strtok(NULL, " :");
  //important so it properly converts to 24 hour
  char* ampmToken = strtok(NULL, " :");

  // Trim leading and trailing spaces from ampmToken
  char* trimmedAmpm = strtok(ampmToken, " ");
  
  // Handle AM/PM
  bool isPM = (strcasecmp(trimmedAmpm, "PM") == 0);
  
  if (isPM && hour != 12) {
    hour += 12;
  } else if (!isPM && hour == 12) {
    hour = 0;
  }

  DateTime timeConverted(now.year(), now.month(), now.day(), hour, minute, 0); // Assuming seconds are always 0
  free(timeCopy);
  return timeConverted;
}

void connectToInternet()
{
  if(pref.isKey("ssid") == true)
  {
    Serial.println("saved SSID found!");
    wifiSSID = pref.getString("ssid");
    Serial.println(wifiSSID);
  }

  if(pref.isKey("password") == true)
  {
    Serial.println("saved Password found!");
    wifiPassword = pref.getString("password");
  }
  
  else
  {
    Serial.println("Details not found...");
    scanNetworks();
  }
}

void resyncClocks()
{
  configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
  //uses UTP time server to sync time
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Connection Err");
    abort();
  }

  String AMorPM;
  if (timeinfo.tm_hour > 12) {
    AMorPM = "PM";
  } else {
    AMorPM = "AM";
  }

  //adjusts dateTime so time is always synced up properly
  rtc.adjust(DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec));
  lastResync = rtc.now();
}

void startUp(void * pvParameters)
{
  Serial.println("connecting to internet:");
  connectToInternet();
  Serial.println("Done!");
  Serial.println("Updating time...");
  resyncClocks();
  Serial.println("Done!");
  syncOpenShutTime();
  vTaskDelete(NULL);
}

void scanNetworks() {
  Serial.println("Scanning for WiFi networks...");

  int networkCount = WiFi.scanNetworks();
  Serial.println("Scan done!");

  if (networkCount == 0) {
    Serial.println("No networks found.");
    return;
  }

  Serial.print(networkCount);
  Serial.println(" networks found:");
  for (int i = 0; i < networkCount; ++i) {
    Serial.print(i + 1);
    Serial.print(": ");
    Serial.print(WiFi.SSID(i));
    Serial.print(" (");
    Serial.print(WiFi.RSSI(i));
    Serial.print(") ");

    switch (WiFi.encryptionType(i)) {
        case WIFI_AUTH_OPEN:            Serial.print("open"); break;
        case WIFI_AUTH_WEP:             Serial.print("WEP"); break;
        case WIFI_AUTH_WPA_PSK:         Serial.print("WPA"); break;
        case WIFI_AUTH_WPA2_PSK:        Serial.print("WPA2"); break;
        case WIFI_AUTH_WPA_WPA2_PSK:    Serial.print("WPA+WPA2"); break;
        case WIFI_AUTH_WPA2_ENTERPRISE: Serial.print("WPA2-EAP"); break;
        case WIFI_AUTH_WPA3_PSK:        Serial.print("WPA3"); break;
        case WIFI_AUTH_WPA2_WPA3_PSK:   Serial.print("WPA2+WPA3"); break;
        case WIFI_AUTH_WAPI_PSK:        Serial.print("WAPI"); break;
        default:                        Serial.print("unknown");
      }

    Serial.println();
    delay(10);
  }
  selectNetwork(networkCount);
}

void selectNetwork(int networkCount) {
  Serial.println("Enter the number of the network you want to connect to:");

  while (true) {
    if (Serial.available() > 0) {
      int networkIndex = Serial.parseInt();
      if (networkIndex > 0 && networkIndex <= networkCount) {
        wifiSSID = WiFi.SSID(networkIndex - 1);
        Serial.print("Selected network: ");
        Serial.println(wifiSSID);
        
        Serial.println("Enter the password:");

        while (Serial.available()) { Serial.read(); }

        while (!Serial.available());

        wifiPassword = Serial.readStringUntil('\n');
        Serial.print("Connecting to ");
        Serial.println(wifiSSID);
        connectToNetwork(wifiSSID, wifiPassword);
      }
      break;
    }
  }
}

void connectToNetwork(String ssid, String password) {
  WiFi.begin(ssid.c_str(), password.c_str());

  Serial.print("Connecting");
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 40) { // 10 seconds timeout
    delay(250);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() == WL_CONNECTED) {
    Serial.println();
    Serial.println("Connected successfully!");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    saveNetworkDetails(ssid, password, "", "");
  } else {
    Serial.println();
    Serial.println("Connection failed. Check your password and try again.");
    scanNetworks();
  }
}


void connectToEnterpriseNetwork(String ssid, String username, String identity, String password) {
  //TODO  
}


void saveNetworkDetails(String ssid, String password, String username, String identity) {
  pref.putString("savedWifiCredentials", ssid);
  pref.putString("savedWifiCredentials", password);
  Serial.println("Network details saved.");
}


void moveDoor(doorstate dstate)
{
  Serial.print("move: ");
  Serial.println(xPortGetCoreID());
  btnState = digitalRead(triggerUp);
  btnState2 = digitalRead(triggerDown);
  digitalWrite(lockPin, HIGH);

  switch(dstate){
  case UP:
  Serial.println("UP");
  for(btnState; btnState != LOW; btnState = digitalRead(triggerUp)){
    digitalWrite(upDown, HIGH);
  }
  break;

  case DOWN:
  Serial.println("DOWN");
  for(btnState2; btnState2 != LOW; btnState2 = digitalRead(triggerDown))
  {
    digitalWrite(upDown, LOW);
  }
  digitalWrite(lockPin, LOW);
  break;

  //case default: break;
  }
}

void loop() 
{

  LCD.setCursor(0,3);
  switch(dstate)
  {
    case OPEN:
    LCD.print("Door Open");
    break;

    case CLOSED:
    LCD.print("Door closed");
    break;

    case CALIBRATING:
    LCD.print("Calibrating");
    break;

    case UP:
    openingAnimation();
    break;

    case DOWN:
    closingAnimation();
    break;

  }

  now = rtc.now();
LCD.setCursor(0, 0);
if(now.hour() < 10){
  LCD.print('0');
  }
  LCD.print(now.hour(), DEC);
  LCD.print(':');
  if(now.minute() < 10){
  LCD.print('0');
  }
  LCD.print(now.minute(), DEC);
  LCD.print(':');
    if(now.second() < 10){
  LCD.print('0');
  }
  LCD.print(now.second(), DEC);
  /*
  LCD.print(" ");
  LCD.print(now.hour() % shutTime.hour(), DEC);
  LCD.print(':');
  LCD.print(now.minute() % shutTime.minute(), DEC);
  */
  LCD.setCursor(0, 1);
  LCD.print("Shuts at: ");
  LCD.print(shutTime.hour(), DEC);
  LCD.print(':');
  if(shutTime.minute() < 10){
  LCD.print('0');
  }
  LCD.print(shutTime.minute(), DEC);
  LCD.print(':');
  LCD.print(shutTime.second(), DEC);

  LCD.setCursor(0, 2);
  LCD.print("Opens at: ");
  LCD.print(openTime.hour(), DEC);
  LCD.print(':');
  LCD.print(openTime.minute(), DEC);
  LCD.print(':');
  LCD.print(openTime.second(), DEC);

  delay(10);
}
GND5VSDASCLSQWRTCDS1307+
NOCOMNCVCCGNDINLED1PWRRelay Module