#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <SoftwareSerial.h>
//pin for 8x32 leds
#define MAX_DEVICES 12
#define CLK_PIN   13
#define DATA_PIN  11
#define CS_PIN    10
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
MD_Parola P = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
//pin for ultrasonic
#define trigPin 5
#define echoPin 4
#define float_switch_1 12 //this is a trigger checker if sesnor reading is correct 
#define float_switch_2 9 //this is a trigger checker if sesnor reading is correct 
#define float_switch_3 6 //this is a trigger checker if sesnor reading is correct 
char buffer[6];
const float dis_sens_gnd = 4.0; //actual distance from water ground to sensor location in ft
String numbers[] = {"1xxxxxxxxxxx", "2xxxxxxxxxxx", "3xxxxxxxxxxx"};
String mobileNumber[] = {""};
int count = 0;
int send_multiple = 0;
//float limits[] = {0.7, 1.1, 2.2}; //converted inch value to ft according to given limits
const float lowThreshold = 0.7; // Low level threshold
const float midThreshold = 1.1; // Mid level threshold
const float highThreshold = 2.2; // High level threshold
float prev_limit = 0;
float limit_now = 1;
bool send_sms =  false;
bool send_sms_request = false;
String message = "";
String recieved_message = "";
String go_nogo = "";
bool lowAlertSent = false; // Flag for low level alert
bool midAlertSent = false; // Flag for mid level alert
bool highAlertSent = false; // Flag for high level alert
SoftwareSerial mySerial(7, 8);
void setup() {
  mySerial.begin(115200);
  P.begin();
  P.displayText("Initializing...", PA_CENTER, 50, 0, PA_PRINT, PA_SCROLL_LEFT);
  P.displayAnimate();
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(float_switch_1, INPUT_PULLUP);
  pinMode(float_switch_2, INPUT_PULLUP);
  pinMode(float_switch_3, INPUT_PULLUP);
  Serial.begin(9600);
  Serial.println(" ");
}
void loop() {
  int float_s_1 = digitalRead(float_switch_1);
  int float_s_2 = digitalRead(float_switch_2);
  int float_s_3 = digitalRead(float_switch_3);
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH);
  int distance_cm = duration * 0.034 / 2;
  float distance_m = distance_cm * 0.01; // Convert to meters
  float distance_ft = distance_cm * 0.0328084; // Convert to feet
  //set to 0 if to avoid negative measurement
  if (distance_ft > dis_sens_gnd) {
    distance_ft = dis_sens_gnd;
  }
  float actual_water_level = dis_sens_gnd - distance_ft;
  dtostrf(actual_water_level, 4, 1, buffer); // Convert the float to a string with 1 decimal place to fit iin display
  if (actual_water_level >= highThreshold) {
    go_nogo = "Not Passable";
  } else if (actual_water_level >= midThreshold || actual_water_level <= lowThreshold) {
    go_nogo = "Passable";
  }
  String water_level =go_nogo + " " + String(buffer) + " ft";
  P.displayText(water_level.c_str(), PA_CENTER, 50, 0, PA_PRINT, PA_SCROLL_LEFT);
  P.displayAnimate();
  //if not sending check if level change
  if (!send_sms && !send_sms_request) {
    if (actual_water_level >= lowThreshold && actual_water_level < midThreshold && !lowAlertSent &&
        !float_s_1 && float_s_2 && float_s_3) {
      lowAlertSent = true;
      midAlertSent = false;
      send_sms = true;
      message = "Low Level Alert! " + water_level;
    } else if (actual_water_level >= midThreshold && actual_water_level < highThreshold && !midAlertSent &&
               !float_s_1 && !float_s_2 && float_s_3) {
      midAlertSent = true;
      lowAlertSent = false;
      highAlertSent = false;
      send_sms = true;
      message = "Mid Level Alert! " + water_level;
    } else if (actual_water_level >= highThreshold && !highAlertSent &&
               !float_s_1 && !float_s_2 && !float_s_3) {
      highAlertSent = true;
      midAlertSent = false;
      send_sms = true;
      message =  "High Level Alert! " + water_level;
    }
  }
  //waits and read incomming message
  while (mySerial.available()) {
    char c = mySerial.read();
    recieved_message += c;
    delay(2);
  }
  //set all to lower case and send message if data is true
  if (recieved_message.length() > 0) {
    recieved_message.toLowerCase();  // Convert the string to lowercase
    if (recieved_message.indexOf("request") != -1) {
      mobileNumber[0] = extractMobileNumber(recieved_message);
      send_sms_request = true;
    }
    recieved_message = "";
  }
  //just sending message
  if (send_sms) {
    // Serial.println(message);
    //delay(5000);
    count = sizeof(numbers) / sizeof(numbers[0]);
    Send(message,  send_sms, numbers, count);
  } else if (send_sms_request) {
    message = "Current data is at " + water_level;
    count = sizeof(mobileNumber) / sizeof(mobileNumber[0]);
    Send(message,  send_sms_request, mobileNumber, count);
  }
}
//send the message function
void Send(String text, bool &stats, String main_number[], int counts) {
  //static int counts = sizeof(main_number) / sizeof(main_number[0]);
  //Serial.print(counts);
  static word startTimer, waitTimer;  // waitTimer > 0 causes the timer to run, good up to 65 second intervals
  static byte procState;
  String phone = main_number[send_multiple];
  if (waitTimer > 0) {
    if (word(millis()) - startTimer < waitTimer) return;  // time is not up yet
    else waitTimer = 0;                                   // and on to the switch-case
  }
  switch (procState) {
    case 0:
      mySerial.println("AT+CMGF=1");  // Set SMS Text Mode
      procState = 1;                  //  run case 1 next time
      waitTimer = 1000;
      startTimer = millis();
      Serial.println("set gms to send mode");
      break;
    case 1:
      mySerial.println("AT+CSCS=\"GSM\"");  //
      procState = 2;                        //  run case 2 next time
      waitTimer = 1000;
      startTimer = millis();
      Serial.println("AT command for send txt");
      break;
    case 2:
      mySerial.println("AT+CMGS=\"" + phone + "\"");  // send to desired phone
      procState = 3;                                  //  run case 3 next time
      waitTimer = 1000;
      startTimer = millis();
      Serial.println(phone);
      Serial.println("AT command for receiver number");
      break;
    case 3:
      mySerial.print("\"" + text + "\"");  // containing desired text
      procState = 4;                       //  run case 4 next time
      waitTimer = 1000;
      startTimer = millis();
      Serial.println("AT command for message contain");
      Serial.println("message --------------------------\n");
      Serial.println(text);
      Serial.print("\n");
      Serial.println("message --------------------------");
      break;
    case 4:
      mySerial.print((char)26);  // signals end of text message
      procState = 0;             //  run case 0 next time
      waitTimer = 500;
      startTimer = millis();
      Serial.println("sent");
      if (counts - 1 - send_multiple == 0) {
        stats = !stats;
        send_multiple += 1;
        Serial.print("finished sending ");
        Serial.println(send_multiple);
        send_multiple = 0;
      } else {
        send_multiple += 1;
        Serial.print("still sending ");
        Serial.println(send_multiple);
      }
      break;
  }
}
//get mobile number of requestor in country format
String extractMobileNumber(String input) {
  String result = "";
  for (int i = 0; i < input.length(); i++) {
    if (input.charAt(i) == '+' && i < input.length() - 10) {
      if (input.charAt(i + 1) == '6' && input.charAt(i + 2) == '3') {
        String potentialNumber = input.substring(i + 1, i + 13); // Exclude the '+' character
        bool valid = true;
        // Check if potentialNumber contains only digits
        for (int j = 0; j < potentialNumber.length(); j++) {
          if (!isDigit(potentialNumber.charAt(j))) {
            valid = false;
            break;
          }
        }
        if (valid) {
          result = "+" + potentialNumber;
          break;
        }
      }
    }
  }
  return result;
}