#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#include <Keypad.h>

#include <FirebaseESP32.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>

#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"
using namespace std;

/*-------KeyPad Init------*/
const byte ROWS = 4;
const byte COLS = 4;

char hexaKeys[ROWS][COLS] =
    {
        {'1', '2', '3', 'A'},
        {'4', '5', '6', 'B'},
        {'7', '8', '9', 'C'},
        {'*', '0', '#', 'D'}};

byte rowPins[ROWS] = {12, 14, 27, 26};
byte colPins[COLS] = {25, 33, 32, 35};

Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

/*-------Dispay Init------*/
#define CS 5
#define RST 15
#define DC 4
#define MOSI 23 // DIN
#define SCLK 18 // CLK
#define MISO 19 // BL

Adafruit_ILI9341 LCD = Adafruit_ILI9341(CS, DC);

char ID_Value[10];
uint8_t b_len = 9;
uint8_t p_len = 5;

// char PostID_Value [5];
uint8_t ValueIndex;
uint8_t PostIDIndex;

uint8_t state; // 0 - Start //1 - BioID //2 - PostCode //3 - voting

/*-------Firebase Init------*/
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define API_KEY "AIzaSyAnIyBy4OkkpnzfaqwVfdFFsntPQZNqgoc"
#define DATABASE_URL "https://votersdatabase-default-rtdb.firebaseio.com/"

FirebaseJson json;
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

unsigned long sendDataPrevMillis = 0;
bool signupOK = false;

/*-----Init Push BUttons-----*/
const int buttonPins[] = {22, 21, 17, 16, 0, 2};
const int numButtons = sizeof(buttonPins) / sizeof(buttonPins[0]);

string defaultPath = "1CJZMmYwsDLgfE5_KTIlcwavROm_LYBryyCkcgagFdVc";
StaticJsonDocument<1024> FingerDatas;
StaticJsonDocument<1024> Candidates;
StaticJsonDocument<200> SelectedVoter;
string ElectID;

void InitFirebase()
{
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(300);
  }

  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  Serial.printf("Firebase Client v%s\n\n", FIREBASE_CLIENT_VERSION);

  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;

  if (Firebase.signUp(&config, &auth, "", ""))
  {
    Serial.println("Firebase signup OK");
    signupOK = true;
  }
  else
  {
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }

  config.token_status_callback = tokenStatusCallback;
  Firebase.begin(&config, &auth);
  Firebase.reconnectNetwork(true);
}

// Start Display message
void initDisplay()
{
  state = 0;
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setTextColor(ILI9341_WHITE);
  LCD.setTextSize(3);
  LCD.setCursor(0, 0);
  LCD.println("Welcome to General Election!!\n");
  LCD.println("Please enter '#' to start..");
}

// Init Push Button pins and mode
void InitPushBtn()
{
  for (int i = 0; i < numButtons; i++)
  {
    pinMode(buttonPins[i], INPUT_PULLUP);
  }
}

void GetVotersData()
{
  string path = defaultPath + "/Fingers";
  if (Firebase.ready() && signupOK &&
      (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();
    if (Firebase.getString(fbdo, path.c_str()))
    {
      FingerDatas.clear();
      DeserializationError error1 = deserializeJson(FingerDatas, fbdo.stringData());

      if (error1)
      {
        Serial.print("Failed to parse Finger JSON: ");
        Serial.println(error1.f_str());
      }

      for (JsonVariant value : FingerDatas.as<JsonArray>())
      {
        if (!value.isNull())
        {
          const char *BioID = value["BioID"];
          const char *FingerID = value["FingerID"];
          const char *FingerTemplate = value["FingerTemplate"];
          const char *Name = value["Name"];
          const char *PostID = value["PostID"];
          Serial.print("BioID: ");
          Serial.println(BioID);
          Serial.print("FingerID: ");
          Serial.println(FingerID);
          Serial.print("FingerTemplate: ");
          Serial.println(FingerTemplate);
          Serial.print("Name: ");
          Serial.println(Name);
          Serial.print("PostID: ");
          Serial.println(PostID);
          Serial.println();
        }
      }
    }
  }
}

void GetCandidateData()
{
  string path = defaultPath + "/Candidates";
  if (Firebase.ready() && signupOK &&
      (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();
    if (Firebase.getString(fbdo, path.c_str()))
    {
      Candidates.clear();
      DeserializationError error1 = deserializeJson(Candidates, fbdo.stringData());

      if (error1)
      {
        Serial.print("Failed to parse Candidates JSON: ");
        Serial.println(error1.f_str());
      }

      int index = 1;
      for (JsonVariant value : Candidates.as<JsonArray>())
      {
        if (!value.isNull())
        {
          value["IndexID"] = index++;
          const char *CandicateID = value["CandicateID"];
          const char *CandidateName = value["CandidateName"];
          const char *Election = value["Election"];
          Serial.print("CandicateID: ");
          Serial.println(CandicateID);
          Serial.print("CandidateName: ");
          Serial.println(CandidateName);
          Serial.print("Election: ");
          Serial.println(Election);
          Serial.println();
        }
      }
    }
  }
}

bool isAlredyVoted()
{
  string Path = defaultPath + "/Voting";
  if (Firebase.ready() && signupOK &&
      (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();
    if (Firebase.getString(fbdo, Path.c_str()))
    {
      StaticJsonDocument<1024> Vote;
      DeserializationError error1 = deserializeJson(Vote, fbdo.stringData());

      if (error1)
      {
        Serial.print("Failed to parse Candidates JSON: ");
        Serial.println(error1.f_str());
      }

      for (JsonVariant value : Vote.as<JsonArray>())
      {
        if (!value.isNull())
        {
          if (Vote["ElectID"] == ElectID && Vote["FingID"] == SelectedVoter["FingerID"])
          {
            return true;
          }
        }
      }
    }
  }
  return false;
}

int getVotersCount()
{
  int count = 0;
  string path = defaultPath + "/Voting";
  if (Firebase.ready() && signupOK &&
      (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();

    if (Firebase.getString(fbdo, path.c_str()))
    {
      StaticJsonDocument<1024> Votes;
      DeserializationError error1 = deserializeJson(Votes, fbdo.stringData());

      if (error1)
      {
        Serial.print("Failed to parse JSON2: ");
        Serial.println(error1.f_str());
      }

      for (JsonVariant value : Votes.as<JsonArray>())
      {
        if (!value.isNull())
        {
          count++;
        }
      }
      return count;
    }
  }
  return count;
}

void StoreVotes()
{
  string Path = defaultPath + "/Voting/";
  int voteCount = getVotersCount();
  voteCount++;
  if (Firebase.ready() && signupOK &&
      (millis() - sendDataPrevMillis > 15000 || sendDataPrevMillis == 0))
  {
    sendDataPrevMillis = millis();

    json.set("CandiID", "2");
    json.set("ElectID", ElectID);
    json.set("FingID", "E1");
    json.set("ID", voteCount);
    json.set("TimeStamp", millis() / 1000);

    string vtCount = std::to_string(voteCount);
    string NewPath = Path + vtCount;

    if (Firebase.setJSON(fbdo, NewPath.c_str(), json))
    {
      Serial.println("Data sent to Firebase successfully");
    }
    else
    {
      Serial.print("Failed to send data to Firebase: ");
      Serial.println(fbdo.errorReason());
    }
  }
}

bool isfull = false;
void nextChar(char key)
{
  if ((state == 4 && ValueIndex < b_len) || (state == 5 && ValueIndex < p_len))
  {
    LCD.setTextColor(ILI9341_WHITE);
    ID_Value[ValueIndex] = key;
    LCD.setCursor((ValueIndex * 18), 100);
    LCD.println(key);

    ValueIndex++;
  }

  isfull = (state == 4 && ValueIndex >= b_len) || (state == 5 && ValueIndex >= p_len);
  if (isfull)
  {
    LCD.setTextColor(ILI9341_WHITE);
    LCD.setCursor(20, 140);
    LCD.println("Please press '#'");
  }
}

void eraseChar()
{
  if (ValueIndex > 0)
  {
    LCD.setTextColor(ILI9341_WHITE);
    ValueIndex--;
    ID_Value[ValueIndex] = '\0';

    LCD.fillScreen(ILI9341_BLACK);
    LCD.setCursor(0, 0);
    string msgVal = state == 4 ? "BIO ID" : state == 5 ? "Post ID" : "";
    string msg = "Please enter the " + msgVal + "\n";
    LCD.println(msg.c_str());
    LCD.setCursor(0, 100);
    LCD.println(ID_Value);
  }
}

bool isCorrectBIO()
{
  bool isCorrect = false;
  for (JsonVariant value : FingerDatas.as<JsonArray>())
  {
    if (!value.isNull())
    {
      if (value["BioID"] != nullptr && strcmp(value["BioID"], ID_Value) == 0)
      {
        isCorrect = true;
        SelectedVoter["BioID"] == value["BioID"];
        SelectedVoter["FingerID"] == value["FingerID"];
        SelectedVoter["FingerTemplate"] == value["FingerTemplate"];
        SelectedVoter["Name"] == value["Name"];
        SelectedVoter["PostID"] == value["PostID"];
      }
    }
  }
  return isCorrect;
}

bool isCorrectPostID()
{
  return SelectedVoter["PostID"] == ID_Value;
}

void CheckPostID()
{
  bool isvalid = FingerDatas.size() <= 0 || SelectedVoter.size() <= 0;
  if (!isvalid)
  {
    bool validpost = isCorrectPostID();
    if (validpost)
    {
      // goto voing
      // print candidates
    }
    else
    {
      // PostIDRequest();
    }
  }
  else
  {
    LCD.fillScreen(ILI9341_RED);
    LCD.setCursor(0, 0);
    LCD.println("Something Wrong Try Again\n");
    delay(10000);
    // BioIDRequest();
  }
}

int cnt = 0;
void BioIDRequest()
{
  memset(ID_Value, '\0', sizeof(ID_Value));
  state = 4;
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setCursor(0, 0);
  LCD.println("Please enter the BIO ID\n");
  LCD.print(cnt++);
  //IDEntery(4);
}

void CheckBioID()
{
  if (FingerDatas.size() <= 0)
  {
    GetVotersData();
  }

  bool iscrt = isCorrectBIO();
  if (!iscrt)
  {
    LCD.fillScreen(ILI9341_BLACK);
    LCD.setCursor(0, 0);
    LCD.setTextColor(ILI9341_RED);
    LCD.println("Invalid BIO ID\n");
    delay(10000);
    //BioIDRequest();
    //change state
    state = 1;

  }
  else
  {
    if (isAlredyVoted())
    {
      LCD.fillScreen(ILI9341_BLACK);
      LCD.setTextSize(3);
      LCD.setCursor(0, 0);
      LCD.setTextColor(ILI9341_RED);
      LCD.println("You are alredy voted!!!!\n");
    }
    else
    {
      // PostIDRequest();
      state = 2;
    }
  }
}

void CorrectID()
{
  if (state == 4 && ValueIndex == b_len)
  {
    CheckBioID();
  }
  else if (state == 5 && ValueIndex == p_len)
  {
    // CheckPostID();
  }
}

void IDEntery(uint8_t _st)
{
  state = _st;
  char key = customKeypad.getKey();
  switch (key)
  {
  case '#':
    CorrectID();
    break;
  case '*':
    eraseChar();
    break;
  default:
    if (isDigit(key))
    {
      nextChar(key);
    }
    break;
  }
}

void PostIDRequest()
{
  memset(ID_Value, '\0', sizeof(ID_Value));
  state = 2;
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setCursor(0, 0);
  LCD.println("Please enter the Post ID\n");
  IDEntery(5);
}

void startVoting()
{
  char key = customKeypad.getKey();
  if (key != '\0')
  {
    switch (key)
    {
    case '#':
      BioIDRequest();
      break;
    case '*':
      initDisplay();
      // getKeyValue();
      break;
    default:
      LCD.fillScreen(ILI9341_BLACK);
      LCD.setCursor(0, 20);
      LCD.println("# - Start / Enter\n");
      LCD.println("* - Cancel");
      break;
    }
  }
}

int getPressedVoteBtn()
{
  for (int i = 0; i < numButtons; i++)
  {
    int currentState = digitalRead(buttonPins[i]);
    if (currentState == LOW)
    {
      while (digitalRead(buttonPins[i]) == LOW)
        ;
      return i;
    }
  }
  return -1;
}

int lastpressed = -1;
int GetVote()
{
  int pressedVote = getPressedVoteBtn();
  if (pressedVote != -1 && lastpressed != pressedVote)
  {
    Serial.print("Vote button ");
    Serial.print(pressedVote + 1);
    Serial.println(" pressed");
    lastpressed = pressedVote;

    // get who selected

    return 1;
  }
  return -1;
}

void awaitCandidateSelection()
{

  int voteRes = GetVote();
  if (voteRes == 1)
  {
  }
  else
  {
    // start again
  }
}

void Voting()
{
  state = 3;
  if (Candidates.size() <= 0)
  {
    GetCandidateData();
  }

  LCD.fillScreen(ILI9341_BLACK);
  LCD.setCursor(0, 0);
  LCD.setTextSize(2);
  int cnt = 0;
  for (JsonVariant value : Candidates.as<JsonArray>())
  {
    if (!value.isNull())
    {
      const char *CandidateName = value["CandidateName"];
      LCD.setCursor(0, cnt * 18);
      LCD.println(CandidateName);
    }
  }

  LCD.setCursor(0, cnt * 18);
  LCD.println("Please select your candidate");
  state = 5;
  awaitCandidateSelection();
}

void getKeyValue()
{
  {
    switch (state)
    {
    case 0:
      startVoting(); // done
      break;
    case 1:
      BioIDRequest();
      break;
    case 2:
      PostIDRequest();
      break;
    case 3:
      Voting();
      break;
    case 4:
      IDEntery(4);
      break;
    case 5:
      awaitCandidateSelection();
      break;
    }
  }
}

void setup()
{
  Serial.begin(9600);
  LCD.begin();
  LCD.setRotation(1);
  InitFirebase();
  initDisplay();
  InitPushBtn();

  // get candidates
  GetCandidateData();
  GetVotersData();

  ElectID = "E1";
}

void loop()
{

  getKeyValue();
  //}

  // char key = customKeypad.getKey();
  // if(key)
  //{
  //   LCD.fillScreen(ILI9341_BLACK);
  // LCD.setCursor(0, 0);
  // LCD.println(key);
  // LCD.println("..............");

  //}
  // LCD.fillScreen(ILI9341_BLACK);
  // LCD.setCursor(0, 0);

  // delay(50);
}
$abcdeabcde151015202530fghijfghij
$abcdeabcde151015202530fghijfghij