#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHTesp.h>
#include <WiFi.h>


#include <PubSubClient.h>
#include <ESP32Servo.h>

#define Screen_Width 128
#define Screen_Height 64
#define OLED_reset -1
#define screen_address 0x3Co
#define Buzzer 14
#define PB_Cancel 13
#define PB_OK 33
#define PB_UP 35
#define PB_Down 32
#define DHTPIN 26
#define AlarmLed 2
#define NTP_SERVER     "pool.ntp.org"
#define UTC_OFFSET_DST 5.5 * 3600

#define MOTOR 18
#define LDR_Right 36
#define LDR_Left 39


WiFiClient espClient;

PubSubClient mqttClient(espClient);
Servo servo;

Adafruit_SSD1306 display(Screen_Width,Screen_Height,&Wire,OLED_reset);
DHTesp dhtSensor;

int days = 0;
int hours = 0;
int minitues = 0;
int seconds = 0;
int UTC_OFFSET = 0;
int timezone_hours = 0;
int timezone_minitues = 0;
bool alarm_enabled = true;
int n_alarms = 3;
int alarm_hours[] = {9,9,11};
int alarm_minitues[] = {30,32,28};
bool alarm_triggered[] = {false,false,false};
int current_mode = 0;
int max_modes = 5;
String modes[] = {"1-Set Time Zone","2-Set Alarm 1","3-Set Alarm 2","4-Set Alarm 3","5 - Cancel alarms"};

int angle = 0;
int Angle_Offset = 30;
float lux = 0.5;
float Controlling_Factor = 0.75;
char tempAr[6];
char intensity_Arr[6];
char max_Intensity_LDR[6];
float D = 0;

void setup()
{
  Serial.begin(115200);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); 
  }
  display.display();
  delay(2000);
  display.clearDisplay();
  print_line("Welcome to Medibox",0,0,2);
  delay(2000);
  display.clearDisplay();
  pinMode(Buzzer, OUTPUT);
  pinMode(AlarmLed, OUTPUT);  
  pinMode(PB_OK, INPUT);
  pinMode(PB_Cancel, INPUT);
  pinMode(PB_UP, INPUT);
  pinMode(PB_Down, INPUT);
  pinMode(LDR_Right, INPUT);
  pinMode(LDR_Left, INPUT);
  pinMode(MOTOR, OUTPUT);
  
  dhtSensor.setup(DHTPIN,DHTesp::DHT22);
  setupWifi();
  setupMqtt();
  configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
  servo.attach(MOTOR);
}

void loop()
{
  update_time_with_check_alarm();
  check_Temp_and_Humidity();
  delay(1000);
  if(digitalRead(PB_OK)== LOW ){
    delay(200);
    goToMenu();
    
  }
  if(!mqttClient.connected()){
    connectToBroker();
  }
  mqttClient.loop();

  mqttClient.publish("Medibox-Temp",tempAr);
  mqttClient.publish("Medibox-Intensity",intensity_Arr);
  mqttClient.publish("Medibox-Intensity-LDR",max_Intensity_LDR);

  updateLightIntensity();
  rotateMotor();
}

void buzzerOn(bool on){
  if(on){
    tone(Buzzer,256);
  }
  else{
    noTone(Buzzer);
  }
}

void setupMqtt(){
  mqttClient.setServer("test.mosquitto.org",1883);
  mqttClient.setCallback(receive);

}

void receive(char* topic,byte* payload,unsigned int length){
  Serial.print("Message arrived[");
  Serial.print(topic);
  Serial.print("] : ");
 

  char payloadCharAr[length];
  for(int i=0;i<length;i++){
    Serial.print((char)payload[i]);
    payloadCharAr[i] = (char)payload[i];
  }
  Serial.println("");

  if(strcmp(topic,"Medibox-Alarm") == 0){
      if(payloadCharAr[0]=='1'){
        buzzerOn(true);
      }
      else{
        buzzerOn(false);
      }
  }

  if(strcmp(topic,"ADMIN-ANGLE-OFFSET") == 0){
    Angle_Offset = atoi(payloadCharAr);
    Serial.println(Angle_Offset);
  }

  if(strcmp(topic,"ADMIN-CONTROLLING_FACTOR") == 0){
    Controlling_Factor = atof(payloadCharAr);
    Serial.println(Controlling_Factor);
  }

}

void connectToBroker(){
  while (!mqttClient.connected()){
    Serial.println("Attempting MQTT Connection...");
    if(mqttClient.connect("Esp32-123456")){
      Serial.println("Connected");
      mqttClient.subscribe("Medibox-Alarm");
      mqttClient.subscribe("ADMIN-ANGLE-OFFSET");
      mqttClient.subscribe("ADMIN-CONTROLLING_FACTOR");
    }
    else{
      Serial.print("Failed");
      Serial.print(mqttClient.state());
      Serial.println("");
      delay(5000);
    }
  }
}

void updateLightIntensity(){
  int analogValue_Right = 4063-analogRead(LDR_Right);
  int analogValue_Left = 4063-analogRead(LDR_Left);
  int analogValue = 0;
  String side = "";
  
  if(analogValue_Right >= analogValue_Left){
    analogValue = analogValue_Right;
    side = "Right";
    D = 0.5;
  }
  else{
    analogValue = analogValue_Left;
    side = "Left";
    D = 1.5;
  }
  
  lux = (float)analogValue/4031;
  String(lux).toCharArray(intensity_Arr,6);
  side.toCharArray(max_Intensity_LDR,6);
  Serial.println(lux);
}

void rotateMotor(){
  angle = min((float)180.0,Angle_Offset*D + (180 - Angle_Offset) * lux * Controlling_Factor);
  servo.write(angle);
  delay(15);

}

void setupWifi(){
  WiFi.begin("Wokwi-GUEST", "", 6);
  while (WiFi.status() != WL_CONNECTED) {
    delay(250);
    display.clearDisplay();
    print_line("Coonnecting to wifi",0,0,2);
  }
  
  display.clearDisplay();
  print_line("Connected to the wifi",0,0,2);
}

void print_line(String text , int column,int row,int text_size){
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(column,row);
  display.println(text);
  display.display();
}

void print_time_now(void){
  display.clearDisplay();
  print_line(String(days),0,0,2);
  print_line(":",20,0,2);
  print_line(String(hours),30,0,2);
  print_line(":",50,0,2);
  print_line(String(minitues),60,0,2);
  print_line(":",80,0,2);
  print_line(String(seconds),90,0,2);



}

//Updates the internal time variables based on the current local time.
void updateTime(){
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char timeHour[3];
  strftime(timeHour,3,"%H",&timeinfo);
  hours = atoi(timeHour);

  char timeMinitue[3];
  strftime(timeMinitue,3,"%M",&timeinfo);
  minitues = atoi(timeMinitue);

  char timeSecond[3];
  strftime(timeSecond,3,"%S",&timeinfo);
  seconds = atoi(timeSecond);

  char timeDay[3];
  strftime(timeDay,3,"%d",&timeinfo);
  days = atoi(timeDay);

  }

  void update_time_with_check_alarm(){
  updateTime();
  print_time_now();

  if(alarm_enabled == true){
    for(int i=0; i<n_alarms; i++){
      if(alarm_triggered[i] == false && alarm_hours[i] == hours && alarm_minitues[i]== minitues){
        ring_alarm(i);
      }
    }
  }
  }

  void ring_alarm(int i){
    display.clearDisplay();
    print_line("Medicine Time",0,0,2);
    while(digitalRead(PB_Cancel) == HIGH){     
      tone(Buzzer,256);
      tone(Buzzer,290);
      digitalWrite(AlarmLed, HIGH);
      delay(200);}
    noTone(Buzzer);
    digitalWrite(AlarmLed, LOW);
    alarm_triggered[i] = true;
    
    
  }

int wait_for_button_press(){
  while(true){
 
    if(digitalRead(PB_UP)  == LOW){
      delay(200);
      return PB_UP;
    }
    else if(digitalRead(PB_Down )== LOW){
      delay(200);
      return PB_Down;
    }
    else if(digitalRead(PB_OK )== LOW ){
      delay(200);
      return PB_OK;
    }
    else if(digitalRead(PB_Cancel )== LOW){
      delay(200);
      return PB_Cancel;
    }
    updateTime();
  }
}

  void goToMenu(){
    while(digitalRead(PB_Cancel) == HIGH){
        display.clearDisplay();
        print_line(modes[current_mode],0,0,2);
        delay(200);
        int pressed = wait_for_button_press();
        if(pressed == PB_UP){
          delay(200);
          current_mode +=1;
          current_mode = current_mode % max_modes;
        }
        else if(pressed == PB_Down){
          delay(200);
          current_mode -=1;
          if(current_mode < 0){
            current_mode = max_modes - 1;
          }
        }
        else if(pressed == PB_OK){
          delay(200);       
          run_mode(current_mode);
        }
        else if(pressed == PB_Cancel){
          delay(200);
          break;
        }
        

    }
  }

//set the time zone by getting offset from the user
void set_time_zone(){
    int temp_hours = timezone_hours;
    while(true){
    display.clearDisplay();
    print_line("Enter hour: " + String(temp_hours),0,0,2);
    int pressed = wait_for_button_press();
        if(pressed == PB_UP){
          delay(200);
          temp_hours +=1;
          if(temp_hours > 14){
            temp_hours = -12;
          }
        }
        else if(pressed == PB_Down){
          delay(200);
          temp_hours -=1;
          if(temp_hours < -12){
            temp_hours = 14;
          }
        }
        else if(pressed == PB_OK){
          delay(200);
       
          timezone_hours = temp_hours;
          break;
        }
        else if(pressed == PB_Cancel){
          delay(200);
          break;
        }
        }
    
    int temp_minitues = timezone_minitues;
    while(true){
    display.clearDisplay();
    print_line("Enter minite: " + String(temp_minitues),0,0,2);
    int pressed = wait_for_button_press();
        if(pressed == PB_UP){
          delay(200);
          temp_minitues +=15;
          temp_minitues = temp_minitues % 60;
        }
        else if(pressed == PB_Down){
          delay(200);
          temp_minitues -=15;
          if(temp_minitues < 0){
            temp_minitues = 45;
          }
        }
        else if(pressed == PB_OK){
          delay(200);
       
          timezone_minitues = temp_minitues;
          break;
        }
        else if(pressed == PB_Cancel){
          delay(200);
          break;
        }
        }
        display.clearDisplay();        
        UTC_OFFSET = 3600 * timezone_hours + 60* timezone_minitues;
        configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
        print_line("Time Zone is set",0,0,2);
        delay(500);

}


void set_alarm(int alarm){
    int temp_hours =  alarm_hours[alarm];
    while(true){
    display.clearDisplay();
    print_line("Enter hour: " + String(temp_hours),0,0,2);
    int pressed = wait_for_button_press();
        if(pressed == PB_UP){
          delay(200);
          temp_hours +=1;
          temp_hours = temp_hours % 24;
        }
        else if(pressed == PB_Down){
          delay(200);
          temp_hours -=1;
          if(temp_hours < 0){
            temp_hours = 23;
          }
        }
        else if(pressed == PB_OK){
          delay(200);
       
         alarm_hours[alarm] = temp_hours;
          break;
        }
        else if(pressed == PB_Cancel){
          delay(200);
          break;
        }
        }
    
    int temp_minitues =  alarm_minitues[alarm];
    while(true){
    display.clearDisplay();
    print_line("Enter minite: " + String(temp_minitues),0,0,2);
    int pressed = wait_for_button_press();
        if(pressed == PB_UP){
          delay(200);
          temp_minitues +=1;
          temp_minitues = temp_minitues % 60;
        }
        else if(pressed == PB_Down){
          delay(200);
          temp_minitues -=1;
          if(temp_minitues < 0){
            temp_minitues = 59;
          }
        }
        else if(pressed == PB_OK){
          delay(200);
       
          alarm_minitues[alarm] = temp_minitues;
          break;
        }
        else if(pressed == PB_Cancel){
          delay(200);
          break;
        }
        }
        display.clearDisplay();
        alarm_enabled = true;
        alarm_triggered[alarm] = false;     //If an alarm is canceled and then set again, the alarm should be set.
        print_line("Alarm"+ String(alarm+1) +"is set",0,0,2);
        delay(500);

}

// run mode that choose from the menu
void run_mode(int mode){
  if(mode == 0){
   set_time_zone();
  }
  else if(mode == 1){
    set_alarm(0);

  }
  else if(mode == 2){
    set_alarm(1);
  }
  else if(mode == 3){
    set_alarm(2);
  }
  else if(mode == 4){
    alarm_enabled = false;
    print_line("Alarms are disabled",0,0,2);
    delay(500);
  }
}

// check temperature and humidity
void check_Temp_and_Humidity(){
  TempAndHumidity data = dhtSensor.getTempAndHumidity();
  
  String(data.temperature,2).toCharArray(tempAr,6);
  
  if (data.temperature>32){
    display.clearDisplay();
    print_line("Temparature is high",0,20,1);
    delay(500);
  }
  else if (data.temperature<26){
    display.clearDisplay();
    print_line("Temparature is low",0,20,1);
    delay(500);
  }
  if (data.humidity>80){
    display.clearDisplay();
    print_line("Humidity is high",0,20,1);
    delay(500);
  }
  else if (data.humidity<60){
    display.clearDisplay();
    print_line("Humidity is low",0,20,1);
    delay(500);
  }

}

$abcdeabcde151015202530fghijfghij
$abcdeabcde151015202530fghijfghij