#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
/////////////////////////////////////////
//    PIN DEFINITION AND VARIABLES     //
//     SSR PIN  BUTTON BUZZER SLOT     //
/////////////////////////////////////////
#define COIN_SLOT 2        // Arduino Uno int pin
#define COIN_SLOT_LIGHT 11 // SSR control coinslot light
#define AIR_PUMP_SSR A0              // SSR control air wash
#define SOLENOID_SSR A1              // SSR air pump control
#define WATER_PUMP_SSR A2            // SSR control water wash
#define WATER_TANK_GATE_VALVE_SSR A3 // SSR control air pump
#define SHAMPO_TANK_CHECK_VALVE 13   /// SSR control solenoid
#define BUZZER 6 // Buzzer
#define AIR_BUTTON 5   // Button
#define WATER_BUTTON 4 // Button
#define FOAM_BUTTON 3  // Button
// ultrasonic pin
#define TRIG_PIN_1 10
#define ECHO_PIN_1 9
#define TRIG_PIN_2 8
#define ECHO_PIN_2 7
#define DEBUG // Comment this line to disable debug messages ->
// will save some memory if deactivated
// Debounce variables for button
unsigned long lastDebounceTime1 = 0;
unsigned long lastDebounceTime2 = 0;
unsigned long lastDebounceTime3 = 0;
const unsigned long debounceDelay = 10;
// Timer start times for each wash
unsigned long previousMillis_timer = 0;
unsigned long countdownTime_timer = 0;
long wtl = 100;
long ftl = 100;
// Button states of debounce
int lastFoamButtonState = HIGH;
int foamButtonState = HIGH;
int lastWaterButtonState = HIGH;
int waterButtonState = HIGH;
int lastAirButtonState = HIGH;
int airButtonState = HIGH;
int price_of_air = 5;
int price_of_water = 5;
int price_of_foam = 5;
int tank_threshold = 30; // unit in percent
int ON = 255;
int OFF = 0;
/////////////////////////////////////////
//           WASH TIME IN SEC          //
//              VARIABLES              //
/////////////////////////////////////////
int FOAM_WASH_TIME = 15;
int AIR_WASH_TIME = 15;
int WATER_WASH_TIME = 15;
// Last displayed values
String lastRow1 = "     PYSONET05  ";
String lastRow2 = "   3 IN 1 WASHING ";
String lastRow3 = "                ";
String lastRow4 = "T1=" + String(wtl) + "%" + " T2=" + String(ftl) + "%"; // Corrected String concatenation
String wftl = String(wtl) + " " + String(ftl);
volatile int credit = 0; // this will use to interrupt
volatile bool is_credited = false;
bool FOAM_WASH = false;
bool AIR_WASH = false;
bool WATER_WASH = false;
bool timerRunning = false;
bool water_tank_ok = true;
bool foam_tank_ok = false;
bool refilled = true;
uint32_t ChipID = 4294967295;             /// DEVICE ID MUST be UNIQUE every board
#define prod;                            ///set to prod once all is OK, this test to include ChipID to avoid copy of HEX code
uint32_t CHIPID() {
  uint32_t chipId = 0;
  for (int i = 0; i < 4; i++) {
    chipId |= (uint32_t)(eeprom_read_byte((uint8_t*)(0x0E + i)) << (i * 8));
  }
  Serial.println(chipId);
  if (chipId != ChipID) {
    lcd.setCursor(0, 0);
    lcd.print("NOT VALID DEVICE ID");
    lcd.setCursor(0, 1);
    lcd.print("CODE has a copyright");
    lcd.setCursor(0, 2);
    lcd.print("contact manufacturer");
    lcd.setCursor(0, 3);
    lcd.print("for code detail !!!");
    while (true);
  } else {
    lcd.setCursor(4, 0);
    lcd.print("WELCOME 2025");
    lcd.setCursor(3, 2);
    lcd.print("* PYSONET05 *");
  }
  delay(5000);
}
void setup()
{
  Serial.begin(9600);
  // Initialize the LCD
  lcd.init();
  lcd.backlight();
  CHIPID();
  updateLCD(); // Initial display update
  // Coin slot Pin and Light
  pinMode(COIN_SLOT, INPUT_PULLUP);
  pinMode(COIN_SLOT_LIGHT, OUTPUT);
  // Setup button pins as input
  pinMode(WATER_TANK_GATE_VALVE_SSR, OUTPUT);
  pinMode(AIR_PUMP_SSR, OUTPUT);
  pinMode(WATER_PUMP_SSR, OUTPUT);
  pinMode(SOLENOID_SSR, OUTPUT);
  pinMode(SHAMPO_TANK_CHECK_VALVE, OUTPUT);
  // Set the trigger and echo pins as OUTPUT and INPUT respectively
  pinMode(TRIG_PIN_1, OUTPUT);
  pinMode(ECHO_PIN_1, INPUT);
  pinMode(TRIG_PIN_2, OUTPUT);
  pinMode(ECHO_PIN_2, INPUT);
  pinMode(FOAM_BUTTON, INPUT_PULLUP);
  pinMode(WATER_BUTTON, INPUT_PULLUP);
  pinMode(AIR_BUTTON, INPUT_PULLUP);
  for (int i = 0; i < 3; i++)
  {
    wftl = check_ultrasonic();
    lcd_display("   * PYSONET05 *", "", "", wftl);
    tone(BUZZER, 2000);
    delay(500);
    lcd_display("   * PYSONET05 *", "   Air/Wash/Foam", "", wftl);
    noTone(BUZZER);
    delay(500);
  }
  // Setup interrupt functionW
  attachInterrupt(digitalPinToInterrupt(COIN_SLOT), add_coin, FALLING); // Coin slot interrupt setup
  lcd.clear();
}
void loop()
{
  wftl = check_ultrasonic();
  if (water_tank_ok && foam_tank_ok)
  {
    ///////////////////////////////////////////////////////////
    //              Credits function                         //
    ///////////////////////////////////////////////////////////
    if (credit)
    {
      if (!FOAM_WASH && !AIR_WASH && !WATER_WASH)
      {
        lcd_display(lastRow1, lastRow2, "   Credit : " + String(credit) + " PHP", wftl);
        if (credit >= price_of_air || credit >= price_of_water || credit >= price_of_foam)
        {
          debounceButton(FOAM_BUTTON, lastFoamButtonState, foamButtonState, lastDebounceTime1, FOAM_WASH);
          debounceButton(WATER_BUTTON, lastWaterButtonState, waterButtonState, lastDebounceTime2, WATER_WASH);
          debounceButton(AIR_BUTTON, lastAirButtonState, airButtonState, lastDebounceTime3, AIR_WASH);
        }
      }
    }
    else
    {
      FOAM_WASH = false;
      WATER_WASH = false;
      AIR_WASH = false;
      timerRunning = false; // Stop timer if no credit
      // lcd_display("WELCOME PYSONET05", "AIR / WASH / FOAM", "INSERT COIN NOW", wftl);
    }
    if (AIR_WASH && credit >= price_of_air)
    {
      if (!timerRunning)
      {
        countdownTime_timer = AIR_WASH_TIME; // Set countdown timer for air wash
        timerRunning = true;                 // Start the timer
      }
      relay_status(ON, ON, OFF, OFF, OFF, OFF);
      updateTimer_timer("AIR TIME STATUS", price_of_air);
      /////////////////////////////////////////////////////////////////////////////////////
    }
    else if (WATER_WASH && credit >= price_of_water)
    {
      if (!timerRunning)
      {
        countdownTime_timer = WATER_WASH_TIME; // Set countdown timer for water wash
        timerRunning = true;                   // Start the timer
      }
      relay_status(OFF, OFF, ON, OFF, OFF, OFF);
      updateTimer_timer("WATER WASH STATUS", price_of_water);
      /////////////////////////////////////////////////////////////////////////////////////
    }
    else if (FOAM_WASH && credit >= price_of_foam)
    {
      if (!timerRunning)
      {
        countdownTime_timer = FOAM_WASH_TIME; // Set countdown timer for foam wash
        timerRunning = true;                  // Start the timer
      }
      relay_status(ON, OFF, ON, OFF, ON, OFF);
      updateTimer_timer("FOAM WASH STATUS", price_of_foam);
      /////////////////////////////////////////////////////////////////////////////////////
    }
    else
    {
      // If no wash is active, ensure all controls are o
      relay_status(OFF, OFF, OFF, OFF, OFF, ON);
      if (!credit)
      {
        lcd_display(lastRow1, "   Air/Wash/Foam", "  Insert Coins Now", wftl);
        delay(100);
        lcd_display(lastRow1, "   Air/Wash/Foam", "  ", wftl);
      }
    }
  }
  else
  {
    String warning;
    if (! water_tank_ok) {
      warning = "WATER TANK CRITICAL.";
      refilled = false;
    } else if (! foam_tank_ok) {
      warning = "FOAM TANK CRITICAL.";
    }
    lcd_display(lastRow1, wftl, "DONT INSERT COIN", warning);
  }
  if (is_credited)
  {
    tone(BUZZER, 1000);
    delay(10);
    is_credited = false;
    Serial.println(is_credited);
  }
  else
  {
    noTone(BUZZER);
  }
  if (!water_tank_ok && !refilled) {
    relay_status(ON, OFF, OFF, OFF, OFF, OFF); // Select desire relay to use
  } else if (water_tank_ok && !refilled){
    relay_status(OFF, OFF, OFF, OFF, OFF, OFF); // must be off all dont change
    refilled = true;
  }
}
void relay_status(int air, int solenoid, int pump, int gate, int shampoo, int light) {
  analogWrite(AIR_PUMP_SSR, air);
  analogWrite(SOLENOID_SSR, solenoid);
  analogWrite(WATER_PUMP_SSR, pump);
  analogWrite(WATER_TANK_GATE_VALVE_SSR, gate);
  analogWrite(SHAMPO_TANK_CHECK_VALVE, shampoo);
  analogWrite(COIN_SLOT_LIGHT, light);
}
String check_ultrasonic()
{
  ///////////////////////////////////////////////////////////
  //              Tanks Check                              //
  ///////////////////////////////////////////////////////////
  // Measure distance for Sensor 1
  long wtl = measureDistance(TRIG_PIN_1, ECHO_PIN_1);
  // Measure distance for Sensor 2
  long ftl = measureDistance(TRIG_PIN_2, ECHO_PIN_2);
  water_tank_ok = (wtl > tank_threshold) ? true : false;
  foam_tank_ok = (ftl > tank_threshold) ? true : false;
  // Print the distances
  print("Water Tank Fill @ :",  0);
  print(wtl,    0);
  print(" %,", 1);
  print("Foam Tank Fill @ : ", 0);
  print(ftl,    0);
  print(" %",             1);
  return "WTL: " + String(wtl) + "% STL: " + String(ftl) + "%";
}
// Function to update the LCD display
void lcd_display(String row_1, String row_2, String row_3, String row_4)
{
  // Only update rows if new value is different from the last displayed value
  if (row_1 != lastRow1)
  {
    lastRow1 = row_1;
    lcd_clear_row(0); // Clear row 1
  }
  if (row_2 != lastRow2)
  {
    lastRow2 = row_2;
    lcd_clear_row(1); // Clear row 2
  }
  if (row_3 != lastRow3)
  {
    lastRow3 = row_3;
    lcd_clear_row(2); // Clear row 3
  }
  if (row_4 != lastRow4)
  {
    lastRow4 = row_4;
    lcd_clear_row(3); // Clear row 4
  }
  // Update the LCD display
  updateLCD();
}
// Function to update the LCD content after clearing
void updateLCD()
{
  lcd.setCursor(0, 0);
  lcd.print(lastRow1);
  lcd.setCursor(0, 1);
  lcd.print(lastRow2);
  lcd.setCursor(0, 2);
  lcd.print(lastRow3);
  lcd.setCursor(0, 3);
  lcd.print(lastRow4);
}
// Function to clear a specific row
void lcd_clear_row(int row)
{
  String spacing = "";
  for (int i = 0; i < 20; i++)
  {
    spacing += " ";
  }
  lcd.setCursor(0, row);
  lcd.print(spacing); // Clear row with spaces
}
// Debounce button function
void debounceButton(int buttonPin, int &lastButtonState, int &buttonState,
                    unsigned long &lastDebounceTime, bool &what_to_wash)
{
  /* Debounce function */
  int reading = digitalRead(buttonPin);
  if (reading != lastButtonState)
  {
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay)
  {
    if (reading != buttonState)
    {
      buttonState = reading;
      if (buttonState == LOW)
      {
        what_to_wash = true;
        print(what_to_wash, 1);
      }
    }
  }
  lastButtonState = reading;
}
// Function to update the timer
void updateTimer_timer(String selected_option, int &price)
{
  unsigned long currentMillis = millis();
  // Check if it's time to update the timer
  if (timerRunning && (currentMillis - previousMillis_timer >= 1000))
  { // Update every second
    previousMillis_timer = currentMillis; // Save the last update time
    countdownTime_timer--;                // Decrease countdown time by 1 second
    // Print the current countdown time to the serial monitor
    printCountdownTime_timer(selected_option);
    if (countdownTime_timer <= 10)
    {
      tone(BUZZER, 2000);
      delay(50);
      noTone(BUZZER);
    }
    // If countdown reaches zero, stop the timer
    if (countdownTime_timer == 0)
    {
      print("Time's up!", 1); // Print to serial monitor
      timerRunning = false;   // Stop the washing process
      FOAM_WASH = false; // Reset wash flags
      AIR_WASH = false;
      WATER_WASH = false;
      credit -= price;
      relay_status(OFF, OFF, OFF, OFF, OFF, ON);
    }
  }
}
// Function to print the current countdown time
void printCountdownTime_timer(String selected_option)
{
  String countdownString = "    Time: " + String(countdownTime_timer / 60) + ":" + (countdownTime_timer % 60 < 10 ? "0" : "") + String(countdownTime_timer % 60);
  print(countdownString, 1); // Print the entire countdown string
  lcd_display(lastRow1, lastRow2, " " + selected_option + " ", countdownString);
}
template <typename T>
void print(T print_value, int next_line)
{
#ifdef DEBUG
  if (next_line)
  {
    Serial.println(print_value);
  }
  else
  {
    Serial.print(print_value);
  }
#endif
}
// Function to measure distance using an ultrasonic sensor
long measureDistance(int trigPin, int echoPin)
{
  // Send a 10 microsecond pulse to trigger
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  // Measure the time for the echo to return
  long duration = pulseIn(echoPin, HIGH);
  // Calculate the distance in cm (duration / 2) * (speed of sound in cm/us)
  long distance = (duration * 0.0343) / 2;           // Convert duration to distance in cm
  return calculateTankFillPercentage(distance, 400); // Assuming tankHeight is 400 cm
}
// Function to calculate the tank fill percentage
float calculateTankFillPercentage(long distance, long tankHeight)
{
  if (tankHeight <= 0)
  {
    return 0; // Avoid division by zero
  }
  // Calculate fill percentage
  float fillPercentage = (1 - (float)distance / tankHeight) * 100;
  // Constrain the fill percentage between 0% and 100%
  return constrain(fillPercentage, 0, 100); // Use constrain for proper range
}
void add_coin()
{
  // Interrupt handler to increment credit
  credit++;
  is_credited = true;
}