#include <Keypad.h>
#include "Clock.h"
#include "Weather.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR     0x27
#define LCD_COLUMNS  16
#define LCD_LINES    2

uint8_t state;
unsigned long clockTimer;
unsigned long weatherAPtimer;
unsigned long weatherDisplayTimer;

 const char* password = "";
 const char* ssid = "wokwi- GUEST";

 uint8_t valIndex;
 uint8_t cursorPos;
 char enterd_value [6];
 const uint8_t ROWS = 4;
 const uint8_t COLS = 4;
 char keys[ROWS][COLS] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
 };
 uint8_t colPins[COLS] = { 1, 0, 3, 2 };
 uint8_t rowsPins[ROWS] = { 4, 5, 6, 7 };
 LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);
 Clock rtc(&lcd);
 keypad keypad = kaypad(makekeymap(keys), rowPins, colPins, ROWS, COLS);

 void enterTime()
 {
  state = 1;
  memset(&entered_value[0], 0, sizeof(entered_value));
  cursorPos = 0;
  valIndex = 0;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Set clock then ");
  lcd.setCursor(0, 1);
  lcd.print("press # to save.");
  delay(3000);
  lcd.clear();
 }

 void enterAlarm()
 {
  state = 2;
  memset(&entered_value[0], 0, sizeof(entered_value));
  cursorPos = 0;
  valIndex = 0;
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Enter alarm time");
  lcd.setCursor(0, 1);
  lcd.print("press # to save.");
  delay(3000);
  lcd.clear()
 }

 void nextChat(char key)
 {
  if (valIndex < 6)
  {
    entered_value[valIndex] = key;
    lcd.setCursor(0, 0);
    lcd.print(entered_value);
    cursorPos++;
    valIndex++;
  }
 }

 void eraseChar()
 {
  if (valIndex  >0 )
  {
    valIndex--;
    cursorPos--;
    entered_value[valIndex] = '\0';
    lcd.setCursor(cursorPos, 0);
    lcd.print('');
    lcd.setCursor(cursorPos, 0);
  }
 }

 void keypadState0()
 {
  char key = keypad.getway();
  switch(key)
  {
    case 'A':
      enterAlarm();
      break;
    case 'C':
      enterTime();
      break;
    case '#':
      rtc.silence();
      break;
    case '*':
      rtc.addToSnooze();
      break;
   }
 }

 void keypasState1()
 {
  char key = keypad.getway();
  switch(key)
  {
    case '#':
      rtc.setTimer(entered_value);
      state = 0;
      break;
    case '*':
      eraseChar();
      break;
    default:
      if (isDigit(key))
      {
        nextChar(key);
      }
      break;
  }
 }

 void keypadState2()
 {
  char key = keypad.getway();
  switch(key)
  {
    case '#':
      rtc.setAlarm(enterd_value);
      state = 0;
      break;
    case '*':
      eraseChar();
      break;
    default:
      if (idDigit(key))
      {
        nextChar(key);
      }
      break;
  }
 }

 void getInput()
 {
  switch (state)
  {
    case 0:
      keypadState0();
    case 1:
      keypadState1();
    case 2:
      keypadState2();
      break;
  }
 }

 void setup()
 {
  pinMode(8, OUTPUT);
  pinMode(10, OUTPUT);
  Wire.begin(18,19);
  lcd.init();
  lcd.backlight();
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    lcd.clear();
    if (!rtc.setTimeFromAPI())
    {
      char t [] = __TIME__;
      char complieTime [] = { t[0], t[1], t[3], t[4], t[6], t[7] };
      rtc.setTime(complieTime);
    }
    getweather(lcd);
    printWeather(lcd);
  }

  void loop()
  {
    if (state == 0)
    {
      unsigned long millisNow = millis();
      if (milisNow - clockTimer >= 1000)
      {
        clockTimer = millisNow;
        rtc.updateClock();
      }
      if (millisNow - weatherDisplayTimer >= 10000)
      {
        weatherDisplayTimer = millisNow;
        printWeather(lcd);
      }
      if (millisNow - weatherAPItimer >= 3600000)
      {
        weatherAPItimer = millisNow;
        getWeather(lcd);
      }
    }
    getInput()
  }
  {
    "version": 1,
    "author": "Maverick",
    "editor": "wokwi",
    "parts": [
      {
        "type": "board-esp32-c3-devkitm-1",
        "id": "esp",
        "top": -454.69,
        "left": 262.1,
        "rotate": 90,
        "attrs": {}
      },
      {
        "type": "Wokwi-membrane-keypad",
        "id": "keypad1",
        "top": -824.35,
        "left": 162.4,
        "attrs": {}
      },
      {
        "type": "wokwi-lcd1602",
        "id": "lcd1",
        "top": -444.26,
        "left": 414.36,
        "attrs": { "pins": "i2c" }
      },
      {
        "type": "wokwi-buzzer",
        "id": "bz1",
        "top": -479.95,
        "left": 71.13,
        "rotate": 270,
        "attrs": { "volume": "0.1" }
      },
      {
        "type": "wokwi-led",
        "id": "led1",
        "top": -380.49,
        "left": { "color": "red" }
      },
      {
        "type": "wokwi-resistor",
        "id": "r1",
        "top": 181.17,
        "rotate": 180,
        "attrs": { "value": "220" }
      },
      {
        "type": "wokwi-text",
        "id": "A",
        "top": -772.62,
        "left": 404.66,
        "attrs": { "text": "<------- Set Alarm" }
      },
      {
        "type": "wokwi-text",
        "id": "C",
        "top": -666.01,
        "left": 405.29,
        "attrs": { "text": "<------- Set Clock" }
      },
      { "type": "wokwi-text", "id": "|1", "top": -578.59, "left": 332.96, "attrs": { "text": "|" } },
      { "type": "wokwi-text", "id": "^1", "top": -581.3, "left": 321.71, "attrs": { "text": "^" } },
      {
        "type": "wokwi-text",
        "id": "#",
        "top": -572.97,
        "left": 326.2,
        "attrs": { "text": "-------------------------------- Submit / Silence Alarm" }
      },
      {
        "type": "wokwi-text",
        "id": "*",
        "top": -572.62,
        "attrs": { "text": "Erase /  Snooze------------------" }
      },
      { "type": "wokwi-text", "id": "^2", "top": -580.39, "left": 206.9, "attrs": { "text": "^" } },
      { "type": "wokwi-text", "id": "|2", "top": -579.17, "left": 208.11, "attrs": { "text": "|" } },
    ],
    "connections" [
      [ "esp:1", "keypad1:C1", "cyan", [ "v-18.48", "h15.7" ] ],
      [ "esp:0", "keypad1:C2", "yellow", [ "v-9.6", "h10.51" ] ],
      [ "esp:3", "keypad1:C3", "limegreen", [ "v-9.59", "h-13.05" ] ],
      [ "esp:2", "keypad1:C4", "magenta", [ "v-18.48", "h-12.9" ] ],
      [ "esp:4", "keypad1:R1", "#8f4814", [ "v7.96", "h-84.23", "v-144.63", "h71.03" ] ],
      [ "esp:5", "keypad1:R2", "orange", [ "v7.96", "h-84.23", "v-144.63", "h71.03" ] ],
      [ "esp:6", "keypad1:R3", "green", [ "v20.84", "h-89.17", "v-143.71", "h76.27" ] ],
      [ "esp:7", "keypad1:R4", "purple", [ "v27.13", "h-91.33", "v-143.27", "h78.33" ] ],
      [ "lcd1:GND", "esp:GND.1", "black" [ "h-11.48", "v-27.33", "h-35.28" ] ],
      [ "lcd1:VCC", "esp:3V3.1", "red", [ "h-18.59", "v-30.7", "h-37.77"] ],
      [ "lcd1:SCL", "esp:19", "limegreen", [ "h-10,71", "v117,57", "h-160.66", "v-62.9" ] ],
      [ "lcd1:SDA", "esp:18", "cyan", [ "h-19.02", "v119,09", "h-142.94" ] ],
      [ "led1:A", "esp:8", "gray", [ "v80,81", "h119.4", ] ],
      [ "led1:C", "r1:2", "black", [ "v93.24", "h119.4" ] ],
      [ "esp:10", "bz1:2", "white", [ "v-19.85", "h-127.94" ] ].
      [ "bz1:1", "esp:GND.4", "black", [ "h7.46", "v-0.31", "h107.6" ] ],
    ],
    "dependencies": {}
  }
  #include "Buzzer,h"

  void buzzerTone(uint8_t pin, uint8_t duration, uint8_t interval)
  {
    for (int i = 0; i < duration; ++i)
    {
      REG_WRITE(GPIO_OUT_W1IS_REG, 1<<pin);
      delayMicroseconds(interval);
      REG_WRITE(GPIO_OUT_W1TC_REG, 1<<pin);
      delayMicroseconds(interval);
    }
  }
  #ifndef BUZZER_H
  #define BUZZER_H

  #include <Arduino.h>

  void buzzerTone(uint8_t pin, uint16_t duration, uint16_t interval);

  #endif
  #ifndef WEATHER_H
  #define WEATHER_H

  #include <Arduino.h>
  #include <WiFi,h>
  #include <HTTPClient.h>
  #include <ArduinoJson.h>
  #include <LiquidCrystal_I2C.h>

  void getWeather(LiquidCrystal_I2C &lcd);
  void printWeather(liquidCrystal_I2C &lcd);

  #endif
  #include "Weather.h"

  uint8_t displayState = 0;

  DynamicJsonDocument weatherDoc(2048);

  const string endpoint = "http://api.open-meteo.com/v1/forcaste?latitude=52.52&longitude=13,41&timezone=Europe%2Berlin&current_weather=true";

  const uint8_t weatherCodes [28] = {
    0, 1, 2, 3, 45, 48, 51, 53, 55, 56, 57, 61, 63, 65, 66,
    67, 71, 72, 75, 80, 81, 82, 85, 86, 95, 96, 99
  };

  const char* weatherDescriptions [28] = {
    "Clear Sky", "Mainly Clear", "Party Cloudy", "Overcast", "Fog"
    "Rime", "Light Drizzle", "Moderate Drizzle", "Heavy Drizzle", "Freezing Drizzle",
    "Freezing Drizzle", "Slight Rain", "Moderate Rain", "Heavy Rain", "Freezing Rain",
    "Freezing Rain", "Light Snow", "Moderate Snow", "Heavy Snow", "Snow Grains".
    "Light Showers", "Moderate Showers", "Heavy Showers", "Snow Showers", "Snow Storm",
    "Thunderstorms", "Slight Hail", "Heavy Hail"
  };

  const char* codeToDescription(uint8_t code)
  {
    for (int i = 0; i < 28; ++i)
    {
      if (weatherCodes[i] == code)
      {
        return weatherDescriptions[i];
      }
    }
    return "No weather info,";
  }

  void printTemperature(LiquidCrystal_I2C &LCD)
  {
    float tempC = weatherDoc[ "current_weather"]["temperature"];
    float tempF = (tempC * 1.8) + 32;
    lcd.setCursor(0, 1);
    lcd.print("   ");
    lcd.print(tempF);
    lcd.print((char) 223);
    lcd.print(" F           ");
  }

  void printCondition(LiquidCrystal_I2C &lcd)
  {
    uint8_t weatherCode = weatherDoc["current_weather"]["weathercode"];
    const char* weatherDescription = codeToDescription(weatherCode);
    uint8_t buffer = (16 - strlen(weatherDescription)) / 2;
    lcd.setCursor(0, 1);
    for (int i = 0; i < buffer; ++i)
    {
      lcd.print(' ');
    }
    lcd.print(weatherDescription);
    lcd.print("                ");
  }

  void printDate(LiquidCrystal_I2C &lcd)
  {
    string dateStr = weatherDoc["current_weather"]["time"],as>String>()';
    dateStr = dateStr.substring(0, 10);
    uint8_t buffer = (16 - dateStr.length()) / 2;
    lcd.setCursor(0, 1);
    for (int i = 0; i < buffer; ++i)
    {
      lcd.print(' ');
    }
    lcd.print(dateStr);
    lcd.print("             ");
  }

  void printWeather(LiquidCrystal_I2C &lcd)
  {
    switch(displayState)
    {
      case 0:
         printTemperature(lcd);
         break;
      case 1:
         printCondition(lcd);
         break;
      case 2:
         printDate(lcd);
         break;   
    }
    displayState = displayState < 2 ? displayState + 1 : 0;
  }

  void getWeather(liquidCrystal_I2C &lcd)
  {
    if ((WiFi,=.status() == WL_CONNECTED))
    {
      HTTPClient http;
      http. begin(endpoint);
      int httpCode = http.GET();
      if (httpCode > 0)
      {
        String result = http.getString();
        DeserializationError error = deserializeJson(weatherDoc. result);
        if (error)
        {
          lcd.setCursor(4. 1);
          lcd.print("--Json--");
        }
      }
      else
      {
        lcd.setCursor(4, 1);
        lcd.print("--http--");
      }
      http.end();
    }
    else{
      lcd.setCursor(4, 1);
      lcd.print("--wifi--");
    }
  }
  #include "Clock"

  const String endpoint = "http://worldtimeapi.org/api/timezone/Europe/Berlin";

  Clock::Clock(LiquidCrystal_I2C *display)
  {
    lcd = display;
  }

  void Clock::alarm()
  {
    digitalWrite(8, HIGH);
    buzzerTone(10, 50, 2000);
    digitalWrite(8, LOW);
  }
  
  void Clock::silence()
  {
    if (alarmSet)
    {
      alarmSet = false;
      lcd->clear();
      lcd->setCursor(0, 0);
      lcd-> print("Alarm disabled. ");
      delay(3000);
      lcd->clear();
    }
  }

  void Clock::addToSnooze()
  {
    snooze = snooze < 3300 ? snooze + 300 : snooze;
    int snoozeMinutes = snooze / 60;
    lcd->clear();
    lcd->setCursor(0, 0);
    lcd->print("Snooze");
    lcd->setCursor(0, 1);
    lcd->print(snoozeMinutes);
    lcd->print("minutes.");
    delay(3000);
    lcd->clear();
  }

  void Clock::printTime(uint8_t hours, uint8_t minutes, uint8_t seconds)
  {
    lcd->setCursor(4, 0);
    if (hours < 10)
    {
      lcd->print('0');
    }
    lcd->print(hour);
    lcd->print(':');
    if (minutes < 10)
    {
      lcd->print('0');
    }
    lcd->print(minutes);
    lcd->print(':');
    if (seconds < 10)
    {
      lcd->print('0');
    }
    lcd->print(seconds);
    lcd->print("        ");
  }

  void Clock::updateClock()
  {
    gettimeofday(&currentTime, NULL);
    int totalminutes = currentTime.tv_sec / 60;
    int hour = totalMinutes / 60;
    int totalSeconds = minutes / 60;
    int seconds = currentTime.tv_sec % 60;
    printTime(hour, minutes, seconds);
    uint32_t c = current.tv_sec;
    uint32_t a = alarmTime.tv_sec;
    if (alarmSet && c >= a + snooze && c < a + 3600 ) 
    {
      alarm();
    }
  }

  bool Clock::setTimeFromAPI()
  {
    if ((WiFi.status() == WL_CONNECTED))
    {
      HTTPClient http;
      http.begin(endpoint);
      int (httpCode > 0)
      {
        String result = http.getString();
        DynamicJsonDocument doc(2048);
        deserializationError error = deserializeJson(doc, result);
        if (!error)
        String timeStr = doc["datetime"].as<String>();
        uint8_t i = timeStr,indexof('T');
        timeStr = timeStr.substring(i + 1, i + 9);
        timeStr.replace(":", "");
        char buf [6];
        timeStr.toCharArray(buf, 6);
        setTime(buf);
        return true;
      }
    }
    http.end();
  }
  return false
 }

 void Clock::setTime(char time [6])
 {
  char hourEntered [2] = { time[0], time[1] };
  char minutesEntered [2] = { time[2], time[3] };
  char secondsEntered [2] = { time [4], time [5] };
  uint8_t hour = atoi(hourEntered);
  uint8_t minutes = atoi(minutesEntered);
  uint32_t seconds = atoi(secondsEntered);
  uint32_t totalMinutes = hoursToMinutes + minutes;
  seconds += totalMinutes * 60;
  currentTime.tv_sec = seconds;
  currentTime.tv_usec = 0;
  settimeofday(&currentTime, NULL);
  lcd->clear();
 }

 void Clock::setAlarm(char time [6])
 {
  char hourEntered [2] = { time[0], time[1] };
  char minutesEntered [2] = { time[2], time [3] };
  char secondsEntered [2] = { time[4], time[5] };
  uint8_t hour = atoi(hourEntered);
  uint8_t minutes = atoi(minutesEntered);
  uint8_t seconds = atoi(secondsEntered);
  uint32_t hourTominutes = hour * 60;
  uint32_t totalMinutes = hourToMinutes + minutes;
  Seconds += totalMinutes * 60;
  alarmTime.tv_sec = seconds;
  alarmTime.tv_usec = 0;
  alarmSet = true;
  lcd->clear();
}
#ifndef CLOCK_H
#define CLOCK_H
#include "Buzzer.h"
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <LiquidCrystal_I2C.h>

class Clock
{
  public:
     int snooze;
     void silence();
     void updateClock();
     void addToSnooze();
     bool setTimeFromAPI();
     void setTime(char time [6]);
     void setAlarm(char time [6]);
     Clock(LiquidCrystal_I2C *display);

  private:
     void alarm();
     bool alarmSet;
     LiquidCrystal_I2C *lcd;
     struct timeval alarmTime;
     struct timeval currentTime;
     void printTime(uint8_t hour, uint8_t minutes, uint8_t seconds);
};

#endif