#include <Arduino.h>
#include<digitalWriteFast.h>
#include<LiquidCrystal_I2C.h>
#include<WiFi.h>
#include<PubSubClient.h>

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 2);

// WiFi Credentials:
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const char* mqtt_server = "test.mosquitto.org";

WiFiClient espClient;
PubSubClient client(espClient);

// Define Pins
const int TRIGGPIN1 = 4;
const int TRIGGPIN2 = 19;
const int ECHOPIN1 = 5;
const int ECHOPIN2 = 18;
const int VALVE1 = 13;
const int VALVE2 = 12;
const int PUMP = 14;
const float SPEED_OF_SOUND = 0.034 / 2;
// Define Values
const int tank1sensorHeight = 95;  // Height of the sensor in cm for Tank 1
const int tank2sensorHeight = 164; // Height of the sensor in cm for Tank 2
const int tank1height = 80;        // In cm for Tank 1
const int tank2height = 150;       // In cm for Tank 2
const int tank1radius = 45;        // In cm for Tank 1
const int tank2radius = 50;        // In cm for Tank 2
const int tank1triggerlevel = 87;  // Trigger level for Pump to start for Tank1
const int tank2triggerlevel = 149; // Trigger level for Pump to start for Tank2
const int tank1triggeroff = 14;
const int tank2triggeroff = 30;
const double pi = 3.1415926535897932384626433832795; // Value of PI
int lastDistance1 = -1; // Initialize with a value that's outside the normal range of distances
int lastDistance2 = -1; // Initialize with a value that's outside the normal range of distances
long duration1;
long duration2;
int distance1;
int distance2;

void setup() {
  Serial.begin(115200);
  // Initialize the LCD
  lcd.init();

  // Turn on the backlight
  lcd.backlight();

  // Print introduction on LCD
  intro();

  // Set pin modes
  pinMode(TRIGGPIN1, OUTPUT);
  pinMode(ECHOPIN1, INPUT);
  pinMode(TRIGGPIN2, OUTPUT);
  pinMode(ECHOPIN2, INPUT);
  pinMode(VALVE1, OUTPUT);
  pinMode(VALVE2, OUTPUT);
  pinMode(PUMP, OUTPUT);

  delay(3000);

  // Clear LCD
  lcd.clear();
  setupWiFi();
  client.setServer(mqtt_server, 1883);
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  ULSensor1();
  ULSensor2();
  int waterheight1;
  int waterheight2;

  // Calculate water height for Tank 1
  if (distance1 <= 2) {
    waterheight1 = tank1height; // Set to maximum possible water height
  } else if (distance1 >= tank1sensorHeight) {
    waterheight1 = 0; // Set to minimum water height (tank empty)
  } else {
    waterheight1 = tank1sensorHeight - distance1; // Calculate water height from sensor
    if (waterheight1 > tank1height) {
      waterheight1 = tank1height; // Cap water height at tank height
    } else if (waterheight1 < 0) {
      waterheight1 = 0; // Ensure water height doesn't become negative
    }
  }

  // Calculate water height for Tank 2
  if (distance2 <= 2) {
    waterheight2 = tank2height; // Set to maximum possible water height
  } else if (distance2 >= tank2sensorHeight) { // Adjusted for Tank 2
    waterheight2 = 0; // Set to minimum water height (tank empty)
  } else {
    waterheight2 = tank2sensorHeight - distance2; // Calculate water height from sensor
    if (waterheight2 > tank2height) {
      waterheight2 = tank2height; // Cap water height at tank height
    } else if (waterheight2 < 0) {
      waterheight2 = 0; // Ensure water height doesn't become negative
    }
  }

  // Calculate tank fill percentages
  float tankFilledPercentage1 = (waterheight1 / (float)tank1height) * 100;
  float tankFilledPercentage2 = (waterheight2 / (float)tank2height) * 100;

  // Check if there's a change in the sensor readings
  if (distance1 != lastDistance1 || distance2 != lastDistance2) {
    // Print tank data via Serial
    Serial.println("------------------------------------");
    Serial.print("Tank 1 - Distance: ");
    Serial.print(distance1);
    Serial.println(" cm");
    Serial.print("Tank 1 - Water Height: ");
    Serial.print(waterheight1);
    Serial.println(" cm");
    Serial.print("Tank 1 - Tank Filled Percentage: ");
    Serial.print(tankFilledPercentage1);
    Serial.println("%");
    Serial.println("------------------------------------");
    Serial.print("Tank 2 - Distance: ");
    Serial.print(distance2);
    Serial.println(" cm");
    Serial.print("Tank 2 - Water Height: ");
    Serial.print(waterheight2);
    Serial.println(" cm");
    Serial.print("Tank 2 - Tank Filled Percentage: ");
    Serial.print(tankFilledPercentage2);
    Serial.println("%");
    Serial.println("------------------------------------");

    // Update LCD display
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Tank 1: ");
    lcd.print(tankFilledPercentage1);
    lcd.print("%");

    lcd.setCursor(0, 1);
    lcd.print("Tank 2: ");
    lcd.print(tankFilledPercentage2);
    lcd.print("%");

    // Update VALVE status
    if (distance1 >= tank1triggerlevel) {
      digitalWriteFast(VALVE1, HIGH);  // Turn on VALVE when tank is less than or equal to 10% full
    } else if (distance1 <= tank1triggeroff) {
      digitalWriteFast(VALVE1, LOW);   // Turn off VALVE when tank is 100% full
    }

    if (distance2 >= tank2triggerlevel) {
      digitalWriteFast(VALVE2, HIGH);  // Turn on VALVE when tank is less than or equal to 10% full
    } else if (distance2 <= tank2triggeroff) {
      digitalWriteFast(VALVE2, LOW);   // Turn off VALVE when tank is 100% full
    }

    // Update PUMP status
    if (digitalRead(VALVE1) || digitalRead(VALVE2)) {
      digitalWriteFast(PUMP, HIGH);  // Turn on PUMP when either tank is less than or equal to 10% full
    } else {
      digitalWriteFast(PUMP, LOW);   // Turn off PUMP when tank is 100% full
    }



    // Update last distance values
    lastDistance1 = distance1;
    lastDistance2 = distance2;
    // Print tank volume and filled percentage
    volume(waterheight1, tankFilledPercentage1, 1);
    volume(waterheight2, tankFilledPercentage2, 2);
  }

  // Delay before next loop iteration
  delay(500);
}

void ULSensor1() {
  digitalWriteFast(TRIGGPIN1, LOW);
  delayMicroseconds(2);
  digitalWriteFast(TRIGGPIN1, HIGH);
  delayMicroseconds(10);
  digitalWriteFast(TRIGGPIN1, LOW);
  duration1 = pulseIn(ECHOPIN1, HIGH);
  distance1 = duration1 * SPEED_OF_SOUND + 1;
}

void ULSensor2() {
  digitalWriteFast(TRIGGPIN2, LOW);
  delayMicroseconds(2);
  digitalWriteFast(TRIGGPIN2, HIGH);
  delayMicroseconds(10);
  digitalWriteFast(TRIGGPIN2, LOW);
  duration2 = pulseIn(ECHOPIN2, HIGH);
  distance2 = duration2 * SPEED_OF_SOUND + 1;
}

void intro() {
  // Introductory Screen
  lcd.println("WATER MONITORING");
  lcd.setCursor(0, 1);
  lcd.println("SYSTEM:1.0");
}

void volume(int waterheight, float tankFilledPercentage, int tankNumber) {
  // Calculate volume of water in tank
  if (waterheight == 0) {
    Serial.print("Tank ");
    Serial.print(tankNumber);
    Serial.println(" is empty"); // Print a message indicating the tank is empty
  } else {
    float tankvolume = pi * (tankNumber == 1 ? tank1radius : tank2radius) * (tankNumber == 1 ? tank1radius : tank2radius) * waterheight;
    float litre = tankvolume * 0.001;
    // Print volume
    Serial.print("Volume of Tank ");
    Serial.print(tankNumber);
    Serial.print(" is: ");
    Serial.print(litre);
    Serial.println(" L");

    // Print tank filled percentage
    Serial.print("Tank ");
    Serial.print(tankNumber);
    Serial.print(" filled percentage: ");
    Serial.print(tankFilledPercentage);
    Serial.println("%");
  }
}

void spinner() {
  static int8_t counter = 0;
  const char* glyphs = "\xa1\xa5\xdb";
  lcd.setCursor(15, 0);
  lcd.print(glyphs[counter++]);
  if (counter == strlen(glyphs)) {
    counter = 0;
  }
}

// Connecting to WiFi
void setupWiFi() {
  delay(1000);
  Serial.print("Connecting to ");
  lcd.setCursor(0, 0);
  lcd.print("Connecting to ");
  Serial.println(ssid);
  lcd.setCursor(0, 1);
  lcd.print(ssid);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    spinner();
    Serial.print(".");
    delay(500);
  }
  Serial.println("\nConnected to WiFi");
}

// Reconnect to MQTT server
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");

    // Attempt to connect
    if (client.connect("espClient")) {
      Serial.println("connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}