#include <WiFi.h>
#include "PubSubClient.h"
#include <LiquidCrystal.h>
#include <ESP32Servo.h>
#include <string.h>
#include "ThingSpeak.h"
#include <ArduinoJson.h> // Thêm dòng này
#include <cstring>

//-UltraSonic
const int trigPin_1 = 4;
const int trigPin_2 = 16;
const int trigPin_3 = 23;
const int trigPin_4 = 2;

const int echoPin_1 = 34;
const int echoPin_2 = 35;
const int echoPin_3 = 22;
const int echoPin_4 = 21;

//--Temp
bool parking_lot[2] = {false, false};
int status[3];
unsigned long previousMillis_1 = 0; 
unsigned long previousMillis_2 = 0; 
unsigned long previousMillis_3 = 0; 
unsigned long previousMillis_4 = 0; 
const int interval = 3000;  

const char* host = "maker.ifttt.com";
const uint16_t port1 = 80;
//--Servo
Servo sv1, sv2;

//--Led
LiquidCrystal lcd(27, 26, 25, 14, 13, 12);
const int redLED_1 = 33;
const int greenLED_1 = 32;
const int redLED_2 = 5;
const int greenLED_2 = 17;

//Wifi and MQTT
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqttServer = "test.mosquitto.org";
const int port = 1883;
WiFiClient espClient;
PubSubClient client(espClient);
bool try_connect = 0;

//global running
int globalSwitchID = -1;
String globalStatus = "";
// int lastStatus[3];

//MQTT topic
const char* mqtt_receive_topic1 = "servo";
const char* mqtt_receive_topic2 = "form";

//ThingSpeak
unsigned long myChannelNumber = 2392345;
const char* myWriteAPIKey = "G5JDZCMV0WJXJOFW";
const char* myReadAPIKey = "V5MHQIXN2W5TYT9D";

// Car Information
String carAct[] = {"IN", "OUT"};  //Two car actions are go in and go out
String carParkingTime[] = {"", ""};  //The time car go in
const int parkingFeePerSec = 10; // Parking fee per second

// Form - Parking Infomation
bool publishTrigger = false;
char buffer[30] = {'\0'};

// Chart - Statistical Information
const int timeInDay = 180000;  // Time in 1 day (3 minutes)
unsigned long previousTime = 0;  // Previous time
int carCounterPerDay = 0;

void wifiConnect() {
  if (try_connect == 0) {
    Serial.println(" Attempting WiFi_connection");
    WiFi.begin(ssid, password);
    try_connect = 1;
  }
  if (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
  }
  else {
    Serial.println(" Connected!");
    try_connect = 0;
  }
}

unsigned long parseAndConvertToTimestamp(String createdAtString) {
  int year, month, day, hour, minute, second;

  // Parse the ISO 8601 timestamp
  sscanf(createdAtString.c_str(), "%d-%d-%dT%d:%d:%dZ", &year, &month, &day, &hour, &minute, &second);

  // Convert to Unix timestamp
  struct tm timeinfo;
  timeinfo.tm_year = year - 1900;
  timeinfo.tm_mon = month - 1;
  timeinfo.tm_mday = day;
  timeinfo.tm_hour = hour;
  timeinfo.tm_min = minute;
  timeinfo.tm_sec = second;

  return mktime(&timeinfo);
}

void getFormRequest(int position, const char* carID, bool park, bool leave) {
  // Store data to Cloud
  ThingSpeak.setField(1, position);
  ThingSpeak.setField(2, carID);
  ThingSpeak.setField(3, (park) ? carAct[0] : carAct[1]);
  int returncode = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);

  // ThingSpeak - Check return code
  if (returncode == 200) {
    Serial.println("ThingSpeak: Channel update successfully.");
  } else {
    Serial.println("ThingSpeak: Problem updating channel.");
  }

  // Read the time associated with the latest update
  String time_str = ThingSpeak.readCreatedAt(myChannelNumber, myReadAPIKey);
  delay(1000);

  // Calculate the parking fee
  //   Case01: Car go in
  if (park) {
    // Store the time car go in
    Serial.println("Car go in!!!");
    carParkingTime[position - 1] = time_str;

    // Reset fee and carID
    publishTrigger = true;
    sprintf(buffer, "{\"fee\":\"\",\"carID\":\"\"}");
  }
  //   Case02: Car go out
  if (leave) {
    Serial.println("Car go out!!!");
    unsigned long timeIn = parseAndConvertToTimestamp(carParkingTime[position - 1]);
    unsigned long timeOut = parseAndConvertToTimestamp(time_str);
    unsigned long duration_sec = timeOut - timeIn;  // Duration as seconds
    
    // Calculate total fee
    int totalFee = duration_sec * parkingFeePerSec;
    Serial.print("-- Parking Fee: ");
    Serial.println(totalFee);

    // Reset time
    carParkingTime[position - 1] = "";

    // Prepare to send to website
    publishTrigger = true;
    sprintf(buffer, "{\"fee\":%d,\"carID\":\"%s\"}", totalFee, carID);
  }

  // Count number of cars in 1 day
  if (leave)
    carCounterPerDay++;
}

void callback(char* topic, byte* message, unsigned int length) {
  Serial.print("Message from topic: ");
  Serial.println(topic);

  // Read Message
  String stMessage;
  for (int i = 0; i < length; i++) {
    stMessage += (char) message[i];
  }
  
  // Extract values from JSON
  DynamicJsonDocument doc(1024);
  deserializeJson(doc, stMessage);

  // Message from topic 1
  if (strcmp(topic, mqtt_receive_topic1) == 0) {
    delay(1000);
    globalSwitchID = doc["switch_id"];
    globalStatus = doc["action"].as<String>();
  }

  // Message from topic 2
  if (strcmp(topic, mqtt_receive_topic2) == 0) {
    delay(1000);
    getFormRequest(doc["position"], doc["carID"], doc["park"], doc["leave"]);
  }
}

void mqttReconnect() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT_connection...");
    if (client.connect("12345678")) {
      Serial.println(" connected");
      client.subscribe(mqtt_receive_topic1);
      client.subscribe(mqtt_receive_topic2);
    } else {
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void sendRequest(String vitri) {
  Serial.print("connecting to ");
  Serial.print(host); Serial.print(":");
  Serial.println(port1);
  WiFiClient client;
  while (!client.connect(host, port1)) {
    Serial.println("connection fail");
    delay(1000);
  }
  String data = "?value1=" + vitri;

  String url = "/trigger/" + String("thong_bao_vat_can") + "/with/key/" + String("dCRD_aUd9nW0TkNyDOq8vM2ogjIsFxx7m5UAfiqUa85") + data;
  client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
  delay(500);
}

void updateInformation(int status[3]){
  // 1 day -> filled
  // 2 trong -> empty
  // 3 loi -> obstacle
  String jsonData = "{";//\"1\":\"filled\",\"2\":\"obstacle\",\"3\":\"empty\"}";

  if (status[0] == 1)
    jsonData = jsonData + "\"1\":\"filled\",";
  else if (status[0] == 2)
    jsonData = jsonData + "\"1\":\"empty\",";
  else
    jsonData = jsonData + "\"1\":\"obstacle\",";

  if (status[1] == 1)
    jsonData = jsonData + "\"2\":\"filled\",";
  else if (status[1] == 2)
    jsonData = jsonData + "\"2\":\"empty\",";
  else
    jsonData = jsonData + "\"2\":\"obstacle\",";

  jsonData = jsonData + "\"3\":\"empty\"}";

  client.publish("test", jsonData.c_str());
  Serial.println(jsonData);
}

const char* name_status[] = {"filled", "empty", "obstacle"};

void printStatus(int status[3]) {
  lcd.clear();

  // Print the first row
  lcd.setCursor(0, 0);
  delay(100);  
  lcd.print("s1: ");
  delay(100);
  lcd.print(name_status[status[0] - 1]);
  delay(100);

  // Print the second row
  lcd.setCursor(0, 1); 
  delay(100); 
  lcd.print("s2: ");
  delay(100);
  lcd.print(name_status[status[1] - 1]);
  delay(100);
}

void setup() {
  Serial.begin(9600);

  lcd.begin(16, 2);
  delay(100);

  parking_lot[0] = 0;
  parking_lot[1] = 0;

  status[0] = 2;
  status[1] = 2;

  // lastStatus[0] = 0;
  // lastStatus[1] = 0;
  
  updateInformation(status);

  sv1.attach(18);
  sv2.attach(19);

  sv1.write(0);
  sv2.write(180);

  pinMode(trigPin_1, OUTPUT);
  pinMode(trigPin_2, OUTPUT);
  pinMode(trigPin_3, OUTPUT);
  pinMode(trigPin_4, OUTPUT);
  pinMode(echoPin_1, INPUT);
  pinMode(echoPin_2, INPUT);
  pinMode(echoPin_3, INPUT);
  pinMode(echoPin_4, INPUT);

  pinMode(redLED_1, OUTPUT);
  pinMode(greenLED_1, OUTPUT);
  pinMode(redLED_2, OUTPUT);
  pinMode(greenLED_2, OUTPUT);

  Serial.println("Connecting to WiFi");
  wifiConnect();
  // sendRequest();
  client.setServer(mqttServer, port);
  client.setCallback(callback);
  ThingSpeak.begin(espClient);
}

int getDistance(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH);
  int distance = duration * 0.034 / 2;  // Tính khoảng cách, với tốc độ âm thanh là 34 cm/ms
  return distance;
}

void loop() {
  // Check if 3 minutes have passed
  unsigned long currentTime = millis();
  if (currentTime - previousTime >= timeInDay) {
    // Save the current time
    previousTime = currentTime;

    // Sent the number of cars per day to the web
    char counterData[10];
    sprintf(counterData, "%d", carCounterPerDay);
    if (!client.publish("car_per_day", counterData)) {
      Serial.println("Failed to publish message to \"car_per_day\"");
      Serial.println(counterData);
    } else {
      Serial.println("Success to publish message to \"car_per_day\"");
      Serial.println(counterData);
    }

    // Reset counter
    carCounterPerDay = 0;
  }
  
  status[0] = 2;
  status[1] = 2;
  delay(100);
 
  if (!client.connected()) { 
    mqttReconnect();
  }
  client.loop();

  if (globalSwitchID != -1) {
    Serial.print("Bat/Tat servo o: ");
    Serial.println(globalSwitchID);
    parking_lot[globalSwitchID - 1] = (globalStatus == "on");
    //parking_lot[globalSwitchID - 1] = !parking_lot[globalSwitchID - 1];
    globalSwitchID = -1;
  }

  // Send total parking fee to website
  // [NOTE: The message needs to be sent twice]
  if (publishTrigger) {    
    if (!client.publish("parking_fee", buffer)) {
      Serial.println("Failed to publish message to \"parking_fee\"");
      Serial.println(buffer);
    } else {
      Serial.println("Success to publish message to \"parking_fee\"");
      Serial.println(buffer);

      publishTrigger = false;
      memset(buffer, '\0', sizeof(buffer));
    }
  }

  // O so 1
  if (parking_lot[0]){
    sv1.write(90);
    Serial.print("Mo cua o so: ");
    Serial.println(1);
  } else {
    sv1.write(0);
    Serial.print("Dong cua o so: ");
    Serial.println(1);
  }

  // O so 2
  if (parking_lot[1]){
    sv2.write(90);
    Serial.print("Mo cua o so: ");
    Serial.println(2);
  } else {
    sv2.write(180);
    Serial.print("Dong cua o so: ");
    Serial.println(2);
  }

  //--------------Ultra Sonic-------------------//
    // Đọc khoảng cách từng cảm biến
  int distance_1 = getDistance(trigPin_1, echoPin_1);
  delay(100);
  int distance_2 = getDistance(trigPin_2, echoPin_2);
  delay(100);
  int distance_3 = getDistance(trigPin_3, echoPin_3);
  delay(100);
  int distance_4 = getDistance(trigPin_4, echoPin_4);
  delay(100);

  // In giá trị khoảng cách ra Serial Monitor
  Serial.print("O so 1, phia truoc: ");
  Serial.println(distance_2);
  Serial.print("O so 1, phia sau: ");
  Serial.println(distance_1);
  Serial.print("O so 2, phia truoc: ");
  Serial.println(distance_3);
  Serial.print("O so 2, phia sau: ");
  Serial.println(distance_4);

  unsigned long currentMillis = millis();
  if (parking_lot[0]){
    //Truong hop servo 1 dang mo
    //TH: Co xe vao
    if (distance_1 < 100 && distance_2 < 100) {
        if (currentMillis - previousMillis_1 >= interval && currentMillis - previousMillis_2 >= interval) {
          //sendRequest(1, 2);
          previousMillis_1 = currentMillis;
          previousMillis_2 = currentMillis;
          
          Serial.print("Có xe ở ô số: ");
          Serial.println(1);
          digitalWrite(redLED_1, LOW);
          digitalWrite(greenLED_1, LOW);

          status[0] = 1;
        }
    } else
    //TH: Khong co xe vao
    if (distance_1 >= 100 && distance_2 >= 100) {
      if (currentMillis - previousMillis_1 >= interval && currentMillis - previousMillis_2 >= interval) {
        //sendRequest(1, 2);
        previousMillis_1 = currentMillis;
        previousMillis_2 = currentMillis;
        
        Serial.print("Đang trống ô số: ");
        Serial.println(1);
        digitalWrite(redLED_1, LOW);
        digitalWrite(greenLED_1, HIGH);

        status[0] = 2;
      }
    } else
    //TH: Co vat can
    if (distance_1 < 100) {
      if (currentMillis - previousMillis_1 >= interval >= interval) {
        //sendRequest(1, 2);
        previousMillis_1 = currentMillis;
        
        Serial.print("Phát hiện vật cản ở ô số:");
        Serial.println(1);
        digitalWrite(redLED_1, HIGH);
        digitalWrite(greenLED_1, LOW);
        status[0] = 3;
        sendRequest("1");

      }
    }
  } else {
    //Truong hop servo 1 dang dong
    //TH: Co vat can
    if (distance_1 < 100 || distance_2 < 100) {
      if (currentMillis - previousMillis_1 >= interval || currentMillis - previousMillis_2 >= interval) {
        //sendRequest(1, 2);
        previousMillis_1 = currentMillis;
        previousMillis_2 = currentMillis;
        
        Serial.print("Phát hiện vật cản ở ô số: ");
        Serial.println(1);
        digitalWrite(redLED_1, HIGH);
        digitalWrite(greenLED_1, LOW);
        status[0] = 3;
        sendRequest("1");

      }
    }
    //TH: Khong co vat can
    if (distance_1 >= 100 && distance_2 >= 100) {
      if (currentMillis - previousMillis_1 >= interval || currentMillis - previousMillis_2 >= interval) {
        //sendRequest(1, 2);
        previousMillis_1 = currentMillis;
        previousMillis_2 = currentMillis;
        
        Serial.print("Đang trống ô số: ");
        Serial.println(1);
        digitalWrite(redLED_1, LOW);
        digitalWrite(greenLED_1, LOW);

        status[0] = 2;
      }
    }
  }
  delay(100);
  if (parking_lot[1]){
    //Truong hop servo 2 dang mo
    //TH: Co xe vao
    if (distance_3 < 100 && distance_4 < 100) {
        if (currentMillis - previousMillis_3 >= interval && currentMillis - previousMillis_4 >= interval) {
        //sendRequest(1, 2);
        previousMillis_3 = currentMillis;
        previousMillis_4 = currentMillis;
        
        Serial.print("Có xe ở ô số: ");
        Serial.println(2);
        digitalWrite(redLED_2, LOW);
        digitalWrite(greenLED_2, LOW);

        status[1] = 1;
      }
    } else
    //TH: Khong co xe vao
    if (distance_3 >= 100 && distance_4 >= 100) {
      if (currentMillis - previousMillis_3 >= interval && currentMillis - previousMillis_4 >= interval) {
        //sendRequest(1, 2);
        previousMillis_3 = currentMillis;
        previousMillis_4 = currentMillis;
        
        Serial.print("Đang trống ô số: ");
        Serial.println(2);
        digitalWrite(redLED_2, LOW);
        digitalWrite(greenLED_2, HIGH);

        status[1] = 2;
      }
    } else
    //TH: Co vat can
    if (distance_4 < 100) {
      if (currentMillis - previousMillis_4 >= interval) {
        //sendRequest(1, 2);
        previousMillis_4 = currentMillis;
        
        Serial.print("Phát hiện vật cản ở ô số:");
        Serial.println(2);
        digitalWrite(redLED_2, HIGH);
        digitalWrite(greenLED_2, LOW);

        status[1] = 3;
        sendRequest("2");

      }
    }
  } else {
    //Truong hop servo 2 dang dong
    //TH: Co vat van
    if (distance_3 < 100 || distance_4 < 100) {
      if (currentMillis - previousMillis_3 >= interval || currentMillis - previousMillis_4 >= interval) {
        //sendRequest(1, 2);
        previousMillis_3 = currentMillis;
        previousMillis_4 = currentMillis;
        
        Serial.print("Phát hiện vật cản ở ô số: ");
        Serial.println(2);
        digitalWrite(redLED_2, HIGH);
        digitalWrite(greenLED_2, LOW);

        status[1] = 3;
        sendRequest("2");

      }
    }
    //TH: Khong co vat van
    if (distance_3 >= 100 && distance_4 >= 100) {
      if (currentMillis - previousMillis_3 >= interval || currentMillis - previousMillis_4 >= interval) {
        //sendRequest(1, 2);
        previousMillis_3 = currentMillis;
        previousMillis_4 = currentMillis;
        
        Serial.print("Đang trống ô số: ");
        Serial.println(2);
        digitalWrite(redLED_2, LOW);
        digitalWrite(greenLED_2, LOW);

        status[1] = 2;
      }
    }
  }
  delay(100);
  updateInformation(status);
  delay(100);
  printStatus(status);
  delay(2000);
}


//- nếu 1 trong 2 mà bật, trong khi đó servo đang đóng, thì gửi thông báo về lcd: có vật cản//
//- 2 cảm biến bật cùng lúc, servo đang mở thì nó sẽ báo là có xe
//- 2 cảm biến bật cùng lúc, servo đang đóng thì nó sẽ báo là có vật cản, đèn đỏ báo
//- nếu 1 trong 2 mà bật, trong khi đó servo đang đóng, thì gửi thông báo về lcd: có vật cản
//- Nếu servo mở, báo đèn xanh
$abcdeabcde151015202530354045505560fghijfghij