/////////////////////////////////////////////////////////////
//    HELMET AUTOMATIC FOG WASHER                          //
//            09-28-24                                     //
//    auth: Oliver Feronel -> Queen
//
//                                                        //
// 10-06-24                                                //
//        initial coding for pins and interrupt            //
// 10-070-24
//        fix timer issue to:
//                        get indevidual time per wash type
//                        turn of and false boolean
//        add "2" ultrasonic for tank check
//        coin slot light active if value > 0
// 10-17-24
//        add air pump control at A2
/////////////////////////////////////////////////////////////
//                  ACRONAMES
//  wtl   = water tank level
//  ftl   = foam tank level
//  wfcl  = water foam current level label
//
#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 A7 // SSR control foam wash
#define AIR_PUMP_SSR A0              // SSR control air wash
#define SOLENOID_SSR A1              // SSR air pupm 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 A6   /// SSR control solenoid
#define BUZZER 9 // Buzzer
#define AIR_BUTTON 8   // Button
#define WATER_BUTTON 7 // Button
#define FOAM_BUTTON 6  // Button
// ultrasonic pin
#define TRIG_PIN_1 10
#define ECHO_PIN_1 11
#define TRIG_PIN_2 12
#define ECHO_PIN_2 13
#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 = 8;
int price_of_foam = 10;
int tank_threshold = 30; // unit in percent
int ON = 255;
int OFF = 0;
/////////////////////////////////////////
//           WASH TIME IN SEC          //
//              VARIABLES              //
/////////////////////////////////////////
int FOAM_WASH_TIME = 60;
int AIR_WASH_TIME = 30;
int WATER_WASH_TIME = 120;
// Last displayed values
String lastRow1 = "     PYSONETO05  ";
String lastRow2 = "3 IN 1 WASHING";
String lastRow3 = "INSERT COIN NOW !!!";
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;
void setup()
{
    Serial.begin(9600);
    // Initialize the LCD
    lcd.init();
    lcd.backlight();
    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 < 5; i++)
    {
        wftl = check_ultrasonic();
        lcd_display("WELCOME PYSONET05", "3 & 1 Washer", "INSERT COIN NOW", wftl);
        tone(BUZZER, 2000);
        delay(500);
        lcd_display("WELCOME PYSONET05", "AIR / WASH / FOAM", "", wftl);
        noTone(BUZZER);
        delay(500);
    }
    // Setup interrupt functionW
    attachInterrupt(digitalPinToInterrupt(COIN_SLOT), add_coin, FALLING); // Coin slot interrupt setup
}
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, ON);
            updateTimer_timer("AIR_WASH", 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, ON);
            updateTimer_timer("WATER_WASH", 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, ON, OFF, OFF, OFF, ON);
            updateTimer_timer("FOAM_WASH", price_of_foam);
            /////////////////////////////////////////////////////////////////////////////////////
        }
        else
        {
            // If no wash is active, ensure all controls are o
            relay_status(OFF , OFF, OFF, OFF, OFF, OFF);
            if (!credit)
            {
                lcd_display(lastRow1, "DONT INSERT COIN", "INSERT COIN", wftl);
            }
        }
    }
    else
    {
        lcd_display(lastRow1, wftl, "DONT INSERT COIN", "TANK LEVEL CRITICAL.");
    }
    if (is_credited)
    {
        tone(BUZZER, 1000);
        delay(10);
        is_credited = false;
    }
    else
    {
        noTone(BUZZER);
    }
    Serial.println(is_credited);
}
void relay_status(int r1, int r2, int r3, int r4, int r5, int r6)
{
    //rx represent relay NO.
    analogWrite(AIR_PUMP_SSR,               r1);
    analogWrite(SOLENOID_SSR,               r2);
    analogWrite(WATER_PUMP_SSR,             r3);
    analogWrite(WATER_TANK_GATE_VALVE_SSR,  r4);
    analogWrite(SHAMPO_TANK_CHECK_VALVE,    r5);
    analogWrite(COIN_SLOT_LIGHT,            r6);
}
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("Tank Fill @ :",  0);
    print(wtl,    0);
    print(" %,", 1);
    print("Tank Fill @ : ", 0);
    print(ftl,    0);
    print(" %",             1);
    return "WTR : " + String(wtl) + "% FWM : " + 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, OFF);
        }
    }
}
// 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, "Function: " + selected_option + " PHP", 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;
}