#include <Adafruit_ILI9341.h>
#include <Keypad.h>
#include <ESP32Firebase.h>
#include <WiFi.h>
#include <Arduino.h>
#include <map>
#include <string>
#include <ArduinoJson.h>
#include <RTClib.h>
using namespace std;

//Sample ID 
//123456789, 
//987654321
//112233445
//111122223
//112233445

/*-------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);

/*-------Firebase Init------*/
#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define API_KEY "AIzaSyAnIyBy4OkkpnzfaqwVfdFFsntPQZNqgoc"
#define DATABASE_URL "https://votersdatabase-default-rtdb.firebaseio.com/"
string defaultPath = "VotingMachine";
Firebase firebase(DATABASE_URL);

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

/*----- RTC ----- */
RTC_DS3231 rtc;
char daysOfWeek[7][12] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

/* Other veriable Initialization  */
uint8_t state; // 0 - Start //1 - BioID //2 - PostCode //3 - voting
const char* ElectID;
unsigned long sendDataPrevMillis = 0;
bool signupOK = false;
DynamicJsonDocument FingerDatas(2048);
DynamicJsonDocument Candidates(2048);
DynamicJsonDocument VoteHistory(2048);
const char* SelectedVoterName;
const char* SelectedVoterID;
char ID_Value[10];
uint8_t b_len = 9;
uint8_t p_len = 5;
uint8_t ValueIndex;
int candidatecount = 5;
string CandidateID[50];
bool isPrint = true;
std::map<string, int> CandidateButtonMap;
bool isfull = false;
int lastpressed = -99;
bool isvoted = false;

//current time 
string getTime()
{    
  DateTime now = rtc.now();
  std::string Day(daysOfWeek[now.dayOfTheWeek()]);
   string formattedTime = Day + "| " +
                          std::to_string(now.day()) + "-" +                  
                          std::to_string(now.month()) + "-" +
                          std::to_string(now.year()) + " " + 
                          
                          std::to_string(now.hour()) + ":" + 
                          std::to_string(now.minute()) + ":" + 
                          std::to_string(now.second()) ;

  Serial.print("The local date and time is: ");
  Serial.println(formattedTime.c_str());
  return formattedTime;
}

//Init Display message
void InitDisplay()
{
  state = 0;
  LCD.begin();
  LCD.setRotation(1);
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setTextColor(ILI9341_GREEN);
  LCD.setTextSize(2);
  LCD.setCursor(0, 50);
  LCD.println("Display Initialization...");
  //delay(5000);
}

void InitRTC()
{
  if (!rtc.begin()) 
  {
    Serial.println("Couldn't find RTC. Dead now.");
      while(1);
  }
}

void StartMessage()
{
  state = 0;
  LCD.setCursor(0, 10);
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setTextColor(ILI9341_WHITE);
  LCD.println("Welcome to General Election!!\n");
  LCD.println("Please enter '#' to start..");
}

//Init Push Button pins and mode
void InitPushBtn()
{
  LCD.setCursor(0, 50);
  LCD.fillScreen(ILI9341_BLACK);
  LCD.println("Push button Initialization...");
  for (int i = 0; i < numButtons; i++)
  {
    pinMode(buttonPins[i], INPUT_PULLUP);
  }
  
  //delay(5000);
}

void InitFirebase()
{
  LCD.setCursor(0, 50);
  LCD.fillScreen(ILI9341_BLACK);
  LCD.println("DataBase Initialization...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");

  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(300);
  }

  Serial.println("Connected with IP: ");
  Serial.print(WiFi.localIP());
  Serial.println();
  
  firebase.json(true); 
  String data = firebase.getString("Connection");

  const size_t capacity = JSON_OBJECT_SIZE(3) + 50;
  DynamicJsonDocument doc(capacity);
  deserializeJson(doc, data);

  int currentSize = doc.size() + 1;
  string vtCount = std::to_string(currentSize);
  string newpath = "Connection/LastConnected_" + vtCount;

  string timeString = getTime();
  firebase.setString(newpath.c_str(), timeString.c_str() );
}

void candidatemapping()
{
  CandidateButtonMap.clear();
  Serial.println("set mapping");
  for (int i = 0; i < numButtons; ++i) 
  {
    string cid = CandidateID[i];  
    Serial.print(cid.c_str());
    Serial.print(" : ");
    Serial.print(buttonPins[i]); 
  
    CandidateButtonMap[cid] = buttonPins[i];
  }
    Serial.println("\nCandidate Mapping....");
    for (const auto& pair : CandidateButtonMap) 
    {
        Serial.print(pair.first.c_str());
        Serial.print(" : ");
        Serial.print(pair.second);
    }
}

void GetCandidateData()
{
  string path = defaultPath + "/Candidates";
  String data = firebase.getString(path.c_str());

  deserializeJson(Candidates, data);
  candidatecount = Candidates.size();

  Serial.print("Received Candidate :\t");
  int index = 0;
      for (JsonPair detail : Candidates.as<JsonObject>())
      {
          const char* CandicateID = detail.value()["CandicateID"];
          const char* CandidateName = detail.value()["CandidateName"];
          const char* Election = detail.value()["Election"];
          Serial.print("CandicateID: ");
          Serial.println(CandicateID);
          Serial.print("CandidateName: ");
          Serial.println(CandidateName);
          Serial.print("Election: ");
          Serial.println(Election);
          Serial.println();          
          //std::string electids(Election);
          //if(ElectID == Election)
          {
            std::string myString(CandicateID);
            CandidateID[index] = myString; 
            index++;
          }          
      }
      candidatemapping();
}

void GetVotersData()
{
  string path = defaultPath + "/Fingers";
  String data = firebase.getString(path.c_str());

  deserializeJson(FingerDatas, data);

  Serial.print("Received Candidate :\t");
  for (JsonPair detail : FingerDatas.as<JsonObject>())
      {
          const int BioID = detail.value()["BioID"];
          const char* FingerID = detail.value()["FingerID"];
          const char* Name = detail.value()["Name"];
          const int PostID = detail.value()["PostID"];
          Serial.print("BioID: ");
          Serial.println(BioID);
          Serial.print("FingerID: ");
          Serial.println(FingerID);
          Serial.print("Name: ");
          Serial.println(Name);
          Serial.print("PostID: ");
          Serial.println(PostID);
          Serial.println();
      }
}

void GetVoteHistory()
{
  string Path = defaultPath + "/Voting";
  String data = firebase.getString(Path.c_str());
  deserializeJson(VoteHistory, data);
  Serial.print(data);

  Serial.println("Vote history :\t");
  for (JsonPair detail : VoteHistory.as<JsonObject>())
      {
          const char* FingID = detail.value()["FingID"];
          const char* CandiID = detail.value()["CandiID"];
          const char* ElectID = detail.value()["ElectID"];
          Serial.print("FingID: ");
          Serial.println(FingID);
          Serial.print("CandiID: ");
          Serial.println(CandiID);
          Serial.print("ElectID: ");
          Serial.println(ElectID);
          Serial.println();
      }
}

void fnCollectData()
{
  LCD.println("Data collecting...");
  GetCandidateData();
  GetVotersData();
  GetVoteHistory();
}

int getVotersCount()
{
  int count = 0;
  for (JsonPair detail : VoteHistory.as<JsonObject>())
  {
    count++;
    }
      return count;
}

bool isAlredyVoted()
{
  for (JsonPair detail : VoteHistory.as<JsonObject>())
  {
    if( /*detail.value()["ElectID"] == ElectID && */
        detail.value()["FingID"] == SelectedVoterID)
        {
          return true;
        }
  }
  return false;
}

bool StoreVote(string Candicate)
{
  std::map<string, string> VotesMap; 
  std::string SelectedVoter(SelectedVoterID);
  std::string election(ElectID);
  int voteCount = getVotersCount() + 1;
  string count = "V" + std::to_string(voteCount);

  VotesMap["VoteID"] = count;
  VotesMap["CandiID"] = Candicate;
  VotesMap["ElectID"] = election;
  VotesMap["FingID"] = SelectedVoter;
  VotesMap["VoteTime"] = getTime();

  string path = defaultPath + "/Voting/" + count;
  int isdone = 0;
  for (const auto& pair : VotesMap) 
  {
    string newpath = path + "/" + pair.first;
      Serial.println("Data Sending to server..");
      Serial.print(pair.first.c_str());
      Serial.print(" : ");
      Serial.print(pair.second.c_str());
      isdone = firebase.setString(newpath.c_str(), pair.second.c_str());
      Serial.print(" / ");
      Serial.println(isdone);
    }
  return isdone == 200;
}

bool isCorrectBIO()
{
  bool isCorrect = false;
  for (JsonPair detail : FingerDatas.as<JsonObject>())
  {
    int Orgval = detail.value()["BioID"];
    int intValue = std::stoi(ID_Value);
    if (Orgval == intValue)
    /*detail.value()["BioID"] != nullptr && */
      {
        isCorrect = true;
        SelectedVoterName = detail.value()["Name"];
        SelectedVoterID = detail.value()["FingerID"];
      }
  }
  return isCorrect;
}

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

  bool iscrt = isCorrectBIO();
  //LCD.print("entered id is \t");
  //LCD.println(iscrt);
  if (!iscrt)
  {
    LCD.fillScreen(ILI9341_BLACK);
    LCD.setCursor(0, 20);
    LCD.setTextColor(ILI9341_RED);
    LCD.println("Invalid BIO ID");
    LCD.println("Please wait");
    int val = 0;
    while(val < 5)
    {
      LCD.print(".");
      delay(1000);
      val++;
    }
    //delay(5000);    
    state = 1;
  }
  else
  {
    LCD.fillScreen(ILI9341_BLACK);
    LCD.setCursor(0, 20);
    LCD.println("Checking please wait...\n");
    bool isVoted = isAlredyVoted();
    if (isVoted)
    {
      LCD.fillScreen(ILI9341_BLACK);
      LCD.setCursor(0, 30);
      LCD.setTextColor(ILI9341_RED);
      LCD.println("You are alredy voted!!!!\n");

      LCD.println("Please reach the assistant...");
      delay(30000);

      state = 3;
    }
    else
    {
      LCD.println("Correct User...");
      state = 5;
      isPrint = true;
    }
  }
}

void CorrectID()
{
  if (state == 4 && ValueIndex == b_len)
  {
    CheckBioID();
  }
}

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, 20);
    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);
  }
}

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 BioIDRequest()
{
  memset(ID_Value, '\0', sizeof(ID_Value));
  ValueIndex = 0;
  state = 4;
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setCursor(0, 10);
  LCD.println("Please enter the ID\n");
}

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

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

int GetVote()
{
  Serial.println("GetVote");
  int pressedVote = getPressedVoteBtn(); //get pin number
  Serial.print(pressedVote);Serial.println("Pressed");
  if (pressedVote != -99 && lastpressed != pressedVote && !isvoted)
  {
    Serial.print("Vote button ");
    Serial.print(pressedVote);
    Serial.println(" pressed");
    lastpressed = pressedVote;
    int voteindex = buttonPins[pressedVote];
    isvoted = true;

    //get candidate id using mapp 
    for (const auto& pair : CandidateButtonMap) 
    {
      if(pair.second == voteindex)
      {
        Serial.println("Correct user");
        string canid = pair.first;
        for (JsonPair detail : Candidates.as<JsonObject>())
        {
          const char* Candt = detail.value()["CandicateID"];
          std::string CandicateID(Candt);
          if(CandicateID == canid)
          {       
            LCD.fillScreen(ILI9341_BLACK);
            LCD.setCursor(0, 20);
            const char* CandidateName = detail.value()["CandidateName"] ;    
            LCD.println(CandidateName);
            LCD.println(" Selected..\n\n");
            delay(2000);
            LCD.println("Please wait...\n");
            if(StoreVote(CandicateID))
            {
              LCD.fillScreen(ILI9341_BLACK);
              LCD.setCursor(0, 29);
              LCD.println("Thanks for voting...\n ");
              delay(10000);
              state = 2;
              //getKeyValue();
              isvoted = false;
            }
            else
            {
              isvoted = false;
              LCD.fillScreen(ILI9341_BLACK);
              LCD.setCursor(0, 20);
              LCD.println("Something went wrong..\n");
              LCD.println("Please try again..\n");
              int time = 0;
              LCD.setCursor(0, 100);
              LCD.print("Wait.");
              while(time < 10)
              {
                LCD.print(" .");
                delay((1000));
                time++;
              }
              state = 1;
            }
          }
        }    
      }
    }
  }
  isvoted = false;
  return 0;
}

void printCandidate()
{
  const char* name = SelectedVoterName; // SelectedVoter["Name"];    
  LCD.fillScreen(ILI9341_BLACK);
  LCD.setCursor(0, 20);
  LCD.print("Hi! ");
  LCD.print(name);
  LCD.println(" ..");
  LCD.println("Please Select your votes..\n");
  
  for (JsonPair detail : Candidates.as<JsonObject>())
      {
          const char* CandicateID = detail.value()["CandicateID"];
          const char* CandidateName = detail.value()["CandidateName"];
          LCD.print(CandicateID);
          LCD.print(" : ");
          LCD.println(CandidateName);
      }
}

void awaitCandidateSelection()
{
  Serial.println("awaitCandidateSelection");
  if(isPrint)
  {
    isPrint = false;
    printCandidate();
  }

  int voteRes = GetVote();
  if (voteRes == 0)
  {    
    //state = 3;
    // start again
  }
}

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

void StoreVote1()
{
  GetVoteHistory();

  std::map<string, string> VotesMap; 

  string ElectID = "E1";
  string Candt = "C4";
  string SelectedVoterID = "F1";
  int voteCount = getVotersCount() + 1;
  string count = "V" + std::to_string(voteCount);

  VotesMap["VoteID"] = count;
  VotesMap["CandiID"] = Candt;
  VotesMap["ElectID"] = ElectID;
  VotesMap["FingID"] = SelectedVoterID;

  string path = defaultPath + "/Voting/" + count;

  Serial.print(" Path //");
  Serial.println(path.c_str());
  for (const auto& pair : VotesMap) 
  {
    string newpath = path + "/" + pair.first;
      Serial.print(pair.first.c_str());
      Serial.print(" : ");
      Serial.print(pair.second.c_str());

      firebase.setString(newpath.c_str(), pair.second.c_str());
    }
}

void setup()
{
  Serial.begin(9600);
  InitDisplay();
  InitRTC();
  InitPushBtn();
  InitFirebase();
  fnCollectData();
  ElectID = "E1";
    //StoreVote1();
  StartMessage();
}

void loop()
{
  getKeyValue();

}
$abcdeabcde151015202530fghijfghij
GND5VSDASCLSQWRTCDS1307+