#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <avr/io.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Total Credit Variable
volatile int creditTotal = 0;
volatile bool creditTotalChange = false;
// Interrupt pins for coin slot and hopper
const int coinSlotInt = 2;
const int coinHopperInt = 3;
const int dispenseButton = 4;
const int hopperRelay = 5;
const int ledRelay = 6;
const int buzzerPin = 7;
const int maxRowChar = 20;
bool dispensingCoin = false;
bool shoewInsertCoin = false;
char lcdRowOne[21] = "pysonet05"; // 20 characters + null terminator
char lcdRowTwo[21] = "";
char lcdRowThree[21] = "";
char lcdRowFour[21] = "";
int buttonState = HIGH;     // Current button state
int lastButtonState = HIGH; // Previous button state
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // Debounce time in milliseconds
// Use buzzer type enable if using active buzzer
// #define USE_PASSIVE_BUZZER
#define ACTIVE_HIGH_RELAY
// Count String Length & Add Spaces
void formatText(char *output, const char *text, char func)
{
  int stringLength = strlen(text);
  if (stringLength > maxRowChar)
  {
    strcpy(output, "Error: Max Length");
    return;
  }
  int remainSpaceCount = maxRowChar - stringLength;
  char makeSpace[maxRowChar + 1] = {0};
  if (func == 'c')
  { // Center the text
    int leftSpace = remainSpaceCount / 2;
    int rightSpace = remainSpaceCount - leftSpace;
    memset(makeSpace, ' ', leftSpace);
    strcpy(output, makeSpace);
    strcat(output, text);
    memset(makeSpace, ' ', rightSpace);
    strcat(output, makeSpace);
  }
  else if (func == 'f')
  { // Add spaces in front
    memset(makeSpace, ' ', remainSpaceCount);
    strcpy(output, makeSpace);
    strcat(output, text);
  }
  else if (func == 'b')
  { // Add spaces in back
    strcpy(output, text);
    memset(makeSpace, ' ', remainSpaceCount);
    strcat(output, makeSpace);
  }
}
// Update LCD Display
void updateLcd()
{
  char formattedText[21];
  lcd.setCursor(0, 0);
  formatText(formattedText, lcdRowOne, 'c');
  lcd.print(formattedText);
  lcd.setCursor(0, 1);
  formatText(formattedText, lcdRowTwo, 'b');
  lcd.print(formattedText);
  lcd.setCursor(0, 2);
  formatText(formattedText, lcdRowThree, 'b');
  lcd.print(formattedText);
  lcd.setCursor(0, 3);
  formatText(formattedText, lcdRowFour, 'b');
  lcd.print(formattedText);
  
  // strcpy(lcdRowTwo, " ");
  strcpy(lcdRowThree, " ");
  strcpy(lcdRowFour, " ");
}
// Buzzer function
void beepBuzzer()
{
#ifdef USE_PASSIVE_BUZZER
  tone(buzzerPin, 2000);
  delay(50);
  noTone(buzzerPin);
#else
  digitalWrite(buzzerPin, HIGH);
  delay(50);
  digitalWrite(buzzerPin, LOW);
#endif
  Serial.println("Buzzer triggered");
}
// Get Chip Serial
uint32_t CHIPID()
{
  uint32_t chipId = 0;
  for (int i = 0; i < 4; i++)
  {
    chipId |= (uint32_t)(EEPROM.read(0x0E + i)) << (i * 8);
  }
  Serial.print("Device ID: ");
  Serial.println(chipId);
  const uint32_t expectedChipID = 4294967295;
  if (chipId != expectedChipID)
  {
    lcd.clear();
    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 details!!!");
    while (true)
    {
      beepBuzzer();
      delay(500);
    }
    // Infinite loop to halt execution
  }
  else
  {
    strcpy(lcdRowOne, "PYSONET05");
    strcpy(lcdRowTwo, "1, 10, 20 php");
    strcpy(lcdRowThree, "to 5 php");
    updateLcd();
    delay(5000); // Delay before continuing
  }
  return chipId;
}
// Verify if credit total divisible by 5 before dispense
int verifyDispenseFive(int value)
{
  if (value % 5 == 0)
  {
    // Serial.println(" is a valid multiple of 5.");
    return value;
  }
  // Serial.println(" is not a multiple of 5. Resetting.");
  return 0;
}
void debounceDispense()
{
  int reading = digitalRead(dispenseButton);
  if (reading != lastButtonState)
  {
    lastDebounceTime = millis();
  }
  if ((millis() - lastDebounceTime) > debounceDelay)
  {
    if (reading != buttonState)
    {
      buttonState = reading;
      // Trigger only when button is released (HIGH)
      if (buttonState == HIGH)
      {
        if (verifyDispenseFive(creditTotal))
        {
          digitalWrite(hopperRelay, HIGH);
          dispensingCoin = true;
          Serial.println("Dispensing Coin");
        }
        else
        {
          Serial.println("Can't dispense due to credit not divisible by 5");
        }
      }
    }
  }
  lastButtonState = reading;
}
// Add Credit
void addCredit()
{
  creditTotal++;
  creditTotalChange = true;
}
// Dispense Coin
void dispenseCoin()
{
  creditTotal -= 5;
}
// Setup Function
void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.begin(20, 4);
  lcd.backlight();
  CHIPID();
  pinMode(coinSlotInt, INPUT_PULLUP);
  pinMode(coinHopperInt, INPUT_PULLUP);
  pinMode(dispenseButton, INPUT_PULLUP);
  pinMode(hopperRelay, OUTPUT);
  pinMode(ledRelay, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(coinSlotInt), addCredit, FALLING);
  attachInterrupt(digitalPinToInterrupt(coinHopperInt), dispenseCoin, FALLING);
}
// Loop Function
void loop()
{
  static unsigned long blinkInsert = 0;
  if (creditTotal > 0)
  {
    snprintf(lcdRowTwo, sizeof(lcdRowTwo), "Credit: %d", creditTotal);
  }
  else
  {
    if (millis() - blinkInsert >= 1000)
    {
      if (shoewInsertCoin)
      {
        strcpy(lcdRowTwo, "INSERT COIN");
        shoewInsertCoin = false;
      }
      else
      {
        strcpy(lcdRowTwo, " ");
        shoewInsertCoin = true;
      }
      Serial.println("Blinking Insert Coin");
      blinkInsert = millis();
    }
  }
  if (!verifyDispenseFive(creditTotal) && creditTotal > 0)
  {
    snprintf(lcdRowThree, sizeof(lcdRowThree), "Credit %d not / by 5", creditTotal);
  }
  else if (dispensingCoin)
  {
    strcpy(lcdRowThree, "Dispensing Coin");
  }
  updateLcd();
  if (creditTotalChange)
  {
    Serial.print("Current Credit: ");
    Serial.println(creditTotal);
    beepBuzzer();
    creditTotalChange = false;
  }
  if (creditTotal == 0)
  {
    digitalWrite(hopperRelay, LOW);
    digitalWrite(ledRelay, HIGH);
  }
  else
  {
    digitalWrite(ledRelay, LOW);
  }
  if (!creditTotal && dispensingCoin)
  {
    for (int i = 0; i < 5; i++)
    {
      beepBuzzer();
      delay(100);
    }
    dispensingCoin = false;
    strcpy(lcdRowTwo, "Thank you!");
    strcpy(lcdRowThree, "For using");
    strcpy(lcdRowFour, "PYSONET05");
    updateLcd();
    delay(1000);
  }
  debounceDispense();
}