#include <vector>

#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <LiquidCrystal_I2C.h>

#define WIFI_SSID "Wokwi-GUEST"
#define WIFI_PASSWORD ""
#define WIFI_CHANNEL 6

#define pinA 4
#define pinB 16
#define pinC 17
#define pinD 18

#define leftArrowPin 32
#define enterPin 33
#define rightArrowPin 25

#define rgbRedPin 0
#define rgbGreenPin 2
#define rgbBluePin 15

#define homePin 26

#define lcdWidth 20
#define lcdHeight 4
#define lcdAddress 0x27

#define numberOfButtons 4
#define numberOfHomeButtons 3

#define transitionMsDelay 500
#define reviewInterval 3000

#define mainLoopMsDelay 50

String title = "StudyBot";

int pinArray[numberOfButtons] = {pinA, pinB, pinC, pinD};
int homeWindowPins[numberOfHomeButtons] = {leftArrowPin, enterPin, rightArrowPin};

const int JSON_SIZE = 5000;
const String API_URL = "https://studybotapi.pythonanywhere.com/getUploadedQuestion";
const String QUESTION_LOG_URL = "https://studybotapi.pythonanywhere.com/uploadQuestionLog";

byte leftArrow[] = { B00001, B00010, B00100, B01000, B00100, B00010, B00001, B00000 };
byte rightArrow[] = { B10000, B01000, B00100, B00010, B00100, B01000, B10000, B00000 };

byte logo1x9[] = { B10010, B10010, B10000, B10000, B10000, B00000, B00000, B00000 };
byte logo0x6[] = { B00000, B00000, B00001, B00110, B11000, B00111, B00000, B00001 };
byte logo0x7[] = { B00000, B00111, B11000, B00000, B00110, B10000, B11111, B00000 };
byte logo0x8[] = { B11110, B00001, B00000, B01000, B00001, B00001, B11111, B00000 };
byte logo0x9[] = { B00000, B10000, B01100, B00011, B11100, B00100, B00100, B10010 };
byte logo1x6[] = { B00001, B00001, B00001, B00001, B00001, B00000, B00000, B00000 };
byte logo1x7[] = { B00110, B00100, B00000, B00100, B00011, B10000, B01111, B00000 };
byte logo1x8[] = { B01100, B01000, B00000, B00100, B11000, B00001, B11110, B00000 };


String choiceLetters[] = {"A", "B", "C", "D"};



class APIManager{
  private:
    
    String apiUrl;
    int jsonSize;

    String callAPI() {
      if (WiFi.status() == WL_CONNECTED) {
        HTTPClient http;
        http.begin(this->apiUrl);

        int httpResponseCode = http.GET();

        if (httpResponseCode > 0) {
          String value = http.getString();
          http.end();
          return value;
        }
        else{
          Serial.print("Api failed");
        }
        http.end();
      } else {
        Serial.println("WiFi not connected");
      }
  }

  public:

    APIManager(String apiUrl, int jsonSize){
      this->apiUrl = apiUrl;
      this->jsonSize = jsonSize;
    }

    JsonDocument getJson(){
      DynamicJsonDocument jsonDoc(this->jsonSize);
      String jsonString = this->callAPI();
      DeserializationError error = deserializeJson(jsonDoc, jsonString);
      if (error) {
        Serial.print("JSON deserialization failed: ");
        Serial.println(error.c_str());
      }
      return jsonDoc;
    }
};

class DatabaseManager{
  private:
    String questionLogUrl;
  public:

    DatabaseManager(String questionLogUrl){
      this->questionLogUrl = questionLogUrl;
    }

    void uploadQuestionLog(String title, String score) {
      StaticJsonDocument<200> jsonDoc;
      jsonDoc["title"] = title;
      jsonDoc["score"] = score;
      
      String jsonString;
      serializeJson(jsonDoc, jsonString);
      
      HTTPClient http;

      http.begin(this->questionLogUrl);
      http.addHeader("Content-Type", "application/json");
      
      int httpResponseCode = http.POST(jsonString);

      if (httpResponseCode > 0) {
        String response = http.getString();
        Serial.println("HTTP Response code: " + String(httpResponseCode));
        Serial.println("Response: " + response);
      } else {
        Serial.println("Error on sending POST: " + String(httpResponseCode));
  }

    http.end();
  }
};

class QuestionManager{
  private:
    JsonArray questionsArray;
    String title;
    size_t currentIndex = 0;

    JsonObject getCurrentQuestion(){
      return questionsArray[currentIndex].as<JsonObject>();
    }
    
  public:

    QuestionManager(){
    }

    bool checkIfCorrectAnswer(String chosenAnswer){
      String correctAnswer = this->getCurrentQuestion()["answer"].as<String>();
      return chosenAnswer == correctAnswer;
    }

    String getAnswer(){
      return this->getCurrentQuestion()["answer"].as<String>();
    }

    String* getChoices(){
      JsonObject choicesJson = this->getCurrentQuestion()["choices"].as<JsonObject>();
      static String newChoices[numberOfButtons];
      int index = 0;
      for (JsonPair keyValuePair : choicesJson) {
        String currentChoice = keyValuePair.value().as<String>();
        newChoices[index++] = currentChoice;
      }
      return newChoices;
    }

    void setQuestions(JsonArray questionsArray){
      this->questionsArray = questionsArray;
    }

    void setTitle(String title){
      this->title = title;
    }

    String getTitle(){
      return this->title;
    }

    String getQuestion(){
      return this->getCurrentQuestion()["question"].as<String>();
    }

    void resetIndex(){
      this->currentIndex = 0;
    }

    int questionSize(){
      return (int)this->questionsArray.size();
    }

    bool inLastQuestion(){
      return this->currentIndex == this->questionsArray.size() - 1;
    }

    void nextQuestion(){
      if(this->questionsArray.size() <= 0){
        Serial.println("The question is empty!");
      }
      this->currentIndex++;
      if(this->currentIndex >= questionsArray.size()){
        this->currentIndex = 0;
      }
    }
    void previousQuestion(){
      if(this->questionsArray.size() <= 0){
        Serial.println("The question is empty!");
      }
      if(this->currentIndex == 0){
        this->currentIndex = questionsArray.size();
      }
      this->currentIndex--;
    }
};

struct Button {
      int pin;
      String answer;
};

class ButtonManager{
  private:
    Button buttonList[numberOfButtons];

  public:
    ButtonManager(int buttonPins[]){
      for(int index = 0; index < numberOfButtons; index++){
        int buttonPin = buttonPins[index];
        this->buttonList[index].pin = buttonPin;
        pinMode(buttonPin, INPUT);
      }
    }

    void setChoices(String* choices){
      for(int index = 0; index < numberOfButtons; index++){
        this->buttonList[index].answer = choices[index];
      }
    }

    String checkIfPressedButton(){
      for(Button& button : this->buttonList){
        bool pressedButton = digitalRead(button.pin);
        if(pressedButton){

          return button.answer;
        }
      }
      return "";
    }
};

class LCDManager{
  private:
    LiquidCrystal_I2C& lcd;

  public:
    LCDManager(LiquidCrystal_I2C& lcdInput) : lcd(lcdInput) {
    }

    void show(int column, int line, String character){
      this->lcd.setCursor(column, line);
      this->lcd.print(character);
    }

    void clear(){
      this->lcd.clear();
    }

    void addCharacter(int identifier, byte* character){
      this->lcd.createChar(identifier, character);
    }

    void showCharacter(int column, int line, int identifier){
      this->lcd.setCursor(column, line);
      this->lcd.write(identifier);
    }

};

class Window{
  public:
    virtual void run() = 0;

    virtual String title() const = 0;

    virtual void setup() = 0;
};


class WindowManager{
  private:
    std::vector<Window*> windowVector;
    int currentWindowIndex = 0;

  public:
    void addWindow(Window* window){
        this->windowVector.push_back(window);
    }
    void runCurrentWindow(){
      if (!windowVector.empty()) {
            this->windowVector[this->currentWindowIndex]->run();
        }
    }
    void setupCurrentWindow(){
      if (!windowVector.empty()) {
            this->windowVector[this->currentWindowIndex]->setup();
        }
    }
     String getCurrentWindowTitle(){
      if (!windowVector.empty()) {
            return this->windowVector[this->currentWindowIndex]->title();
        }
    }
    void previousWindow(){
      if(this->currentWindowIndex == 0){
        this->currentWindowIndex = this->windowVector.size();
      }
      this->currentWindowIndex--;
    }
    void nextWindow(){
      this->currentWindowIndex++;
      if(this->currentWindowIndex == this->windowVector.size()){
        this->currentWindowIndex = 0;
      }
    }
};

class HomeManager{
  private:
    bool runState = false;
    WindowManager& windowManager;
    LCDManager& lcdManager;

    void printLeftArrow(){
      lcdManager.showCharacter(0, 1, 0);
    }

    void printRightArrow(){
      lcdManager.showCharacter(lcdWidth - 1, 1, 1);
    }

    void printArrows(){
      printLeftArrow();
      printRightArrow();
    }

    void updateView(){
      lcdManager.clear();
      printArrows();
      printCurrentMode();
    }

    void printCurrentMode(){
      String title = windowManager.getCurrentWindowTitle();
      lcdManager.show(((lcdWidth / 2) - (title.length() / 2)), 1, title);
    }

    void enter(){
      runState = false;
      lcdManager.clear();
      windowManager.setupCurrentWindow();
    }
    
  public:
    HomeManager(LCDManager& lcdManagerInput, WindowManager& windowManager, int* homeWindowPins) 
               : lcdManager(lcdManagerInput), windowManager(windowManager) {
      for(int index = 0; index < numberOfHomeButtons;  index++){
        pinMode(homeWindowPins[index], INPUT);
      }
    }

    void run(){
      bool pressedLeft = digitalRead(leftArrowPin);
      bool pressedRight = digitalRead(rightArrowPin);
      bool pressedEnter = digitalRead(enterPin);
      if(pressedLeft){
        Serial.println("Pressed Left");
        this->windowManager.previousWindow();
        this->updateView();
        delay(100);
      }
      else if(pressedRight){
        Serial.println("Pressed Right");
        this->windowManager.nextWindow();
        this->updateView();
        delay(100);
      }
      else if(pressedEnter){
        Serial.println("Pressed Enter");
        this->enter();
        delay(100);
      }
    }

    void startHome(){
      this->runState = true;
      this->lcdManager.clear();
      delay(transitionMsDelay);
      this->updateView();
    }

    bool running(){
      return this->runState;
    }
};

class LCDQuestionManager{
  private:
    LCDManager& lcdManager;
    
    void printQuestionFirstLine(String questionLine){
      lcdManager.show(0, 0, questionLine);
    }

    void printQuestionSecondLine(String questionLine){
      lcdManager.show(0, 1, questionLine);
    }

    void printLineCentered(int column, String line){
      lcdManager.show((lcdWidth / 2) - (line.length() / 2), column, line);
    }

    void printChoiceA(String choice){
      lcdManager.show(0, 2, choice);
    }
    void printChoiceB(String choice){
      lcdManager.show(0, 3, choice);
    }
    void printChoiceC(String choice){
      int edgePadding = 10 - choice.length(); // so that the choice is in the edge
      lcdManager.show(10 + edgePadding, 2, choice);
    }
    void printChoiceD(String choice){
      int edgePadding = 10 - choice.length(); // so that the choice is in the edge
      lcdManager.show(10 + edgePadding, 3, choice);
    }

    std::vector<String> splitSentence(const String& sentence, size_t maxLength) {
      std::vector<String> chunks;
      String currentChunk = "";
      String word;
      int lastSpace = 0;

      for (int index = 0; index <= sentence.length(); index++) {
          char currentCharacter = sentence.charAt(index);

          if (currentCharacter == ' ' || currentCharacter == '\0') {
              word = sentence.substring(lastSpace, index);
              lastSpace = index + 1;

              if (currentChunk.length() + word.length() + 1 > maxLength) {
                  if (currentChunk.length() > 0) {
                      chunks.push_back(currentChunk);
                  }
                  currentChunk = word;
              } else {
                  if (currentChunk.length() > 0) {
                      currentChunk += " ";
                  }
                  currentChunk += word;
              }
          }
      }
        if (currentChunk.length() > 0) {
            chunks.push_back(currentChunk);
        }

        return chunks;
      }
    
  public:
    LCDQuestionManager(LCDManager& lcdManagerInput) : lcdManager(lcdManagerInput) {
      
    }

    void show(String question, String* choices){
      showQuestion(question);

      this->printChoiceA(choices[0]);
      this->printChoiceB(choices[1]);
      this->printChoiceC(choices[2]);
      this->printChoiceD(choices[3]);
    }

    void showQuestion(String question){
      this->lcdManager.clear();
      std::vector<String> questionVector = this->splitSentence(question, lcdWidth);
      this->printQuestionFirstLine(questionVector[0]);
      this->printQuestionSecondLine(questionVector[1]);
    }

    void showQuestionCentered(String question, int column = 1, bool clearLcd = true){
      if(clearLcd){
        this->lcdManager.clear();
      }
      std::vector<String> questionVector = this->splitSentence(question, lcdWidth);
      this->printLineCentered(column, questionVector[0]);
      this->printLineCentered(column + 1, questionVector[1]);
    }

    void showAnswer(String answer, int column = 1, bool clearLcd = true){
      if(clearLcd){
        this->lcdManager.clear();
      }
      this->lcdManager.show((lcdWidth / 2) - (answer.length() / 2), column, answer);
    }

    void clear(){
      this->lcdManager.clear();
    }

    void showIncorrect(){
      String label = "Incorrect!";
      this->lcdManager.show((lcdWidth / 2) - (label.length() / 2), 1, label);
      delay(transitionMsDelay * 2);
      this->lcdManager.clear();
    }

    void showCorrect(){
      String label = "Correct!";
      this->lcdManager.show((lcdWidth / 2) - (label.length() / 2), 1, label);
      delay(transitionMsDelay * 2);
      this->lcdManager.clear();
    }

    void showEndScreen(String title, String score){
      static String back = "Back";
      this->lcdManager.show(0, 0, title);
      this->lcdManager.show(0, 1, score);
      this->lcdManager.showCharacter(0, 3, 0);
      this->lcdManager.show(2, 3, back);
    }

    void showUploadingQuestionLog(){
      this->lcdManager.clear();
      String labelOne = "Uploading to";
      String labelTwo = "Database...";
      this->lcdManager.show((lcdWidth / 2) - (labelOne.length() / 2), 1, labelOne);
      this->lcdManager.show((lcdWidth / 2) - (labelTwo.length() / 2), 2, labelTwo);
    }
};

class ScoreManager{
  private:
    int totalQuestions = 0;
    int correctAnswers = 0;
  public:
    void reset(){
      this->totalQuestions = 0;
      this->correctAnswers = 0;
    }

    void setQuestions(int questionAmount){
        this->totalQuestions = questionAmount;
    }

    String getScore(){
      String scoreString = (String)this->correctAnswers + "/" + (String)this->totalQuestions;
      return scoreString;
    }

    void addCorrect(){
      this->correctAnswers++;
    }
};

class QuizModeWindow : public Window{
  private:
    LCDQuestionManager& lcdQuestionManager;
    QuestionManager& questionManager;
    ButtonManager& buttonManager;
    HomeManager& homeManager;
    ScoreManager& scoreManager;
    DatabaseManager& databaseManager;
    bool finished = false;

    void nextQuestion(){
      questionManager.nextQuestion();
      delay(100); 
    }

    void returnHome(){
      questionManager.resetIndex();
      finished = false;
      colorRgbLed(false, false, false);
      homeManager.startHome();
      scoreManager.reset();

    }

    void showCurrentQuestion(){
      String* choices = questionManager.getChoices();
      String question = questionManager.getQuestion();
      buttonManager.setChoices(choices);
      lcdQuestionManager.show(question, choices);
    }

    void colorRgbLed(bool red, bool green, bool blue){
      digitalWrite(rgbRedPin, red);
      digitalWrite(rgbGreenPin, green);
      digitalWrite(rgbBluePin, blue);
    }

    void showEndScreen(){
      finished = true;
      String title = questionManager.getTitle();
      String score = scoreManager.getScore();
      lcdQuestionManager.showUploadingQuestionLog();
      databaseManager.uploadQuestionLog(title, score);
      lcdQuestionManager.clear();
      delay(transitionMsDelay);
      lcdQuestionManager.showEndScreen(title, score);
    }

  public:

    QuizModeWindow(LCDQuestionManager& lcdQuestionManager, QuestionManager& questionManager, ButtonManager& buttonManager, HomeManager& homeManager, ScoreManager& scoreManager, DatabaseManager& databaseManager)
                  :lcdQuestionManager(lcdQuestionManager), questionManager(questionManager), buttonManager(buttonManager), homeManager(homeManager), scoreManager(scoreManager), databaseManager(databaseManager){

    }

    void run() override{
      if(this->finished){
        bool pressedLeft = digitalRead(leftArrowPin);
        bool pressedHome = digitalRead(homePin);
        if(pressedLeft | pressedHome){
          this->returnHome();
        }
        return;
      }
      String chosenAnswer = this->buttonManager.checkIfPressedButton();
      bool pressedHome = digitalRead(homePin);
      if(pressedHome){
        this->returnHome();
      }
      if(chosenAnswer == ""){
        return;
      }
      if(questionManager.checkIfCorrectAnswer(chosenAnswer)){
        this->lcdQuestionManager.clear();
        this->scoreManager.addCorrect();
        delay(transitionMsDelay);
        this->colorRgbLed(false, true, false);
        this->lcdQuestionManager.showCorrect();
        this->colorRgbLed(false, false, false);
        delay(transitionMsDelay);
      }
      else{
        this->lcdQuestionManager.clear();
        delay(transitionMsDelay);
        this->colorRgbLed(true, false, false);
        this->lcdQuestionManager.showIncorrect();
        this->colorRgbLed(false, false, false);
        delay(transitionMsDelay);
      }
      if(this->questionManager.inLastQuestion()){
        this->showEndScreen();
        return;
      }
      this->nextQuestion();
      this->showCurrentQuestion();
    }

    String title() const override{
      return "Quiz Mode";
    }

    void setup() override{
      this->lcdQuestionManager.clear();
      delay(transitionMsDelay);
      this->scoreManager.setQuestions(this->questionManager.questionSize());
      this->showCurrentQuestion();
    }
};

class StudyModeWindow : public Window{
  private:
    LCDQuestionManager& lcdQuestionManager;
    QuestionManager& questionManager;
    HomeManager& homeManager;

    bool showingAnswer = false;

    void showCurrentQuestion(){
      String question = questionManager.getQuestion();
      lcdQuestionManager.showQuestionCentered(question);
    }

    void showCurrentAnswer(){
      String answer = questionManager.getAnswer();
      lcdQuestionManager.showAnswer(answer);
    }

    void returnHome(){
      this->questionManager.resetIndex();
      homeManager.startHome();

    }
    
    
  public:

    StudyModeWindow(LCDQuestionManager& lcdQuestionManager, QuestionManager& questionManager, HomeManager& homeManager)
                  :lcdQuestionManager(lcdQuestionManager), questionManager(questionManager), homeManager(homeManager){

    }

    void run() override{
      bool pressedLeft = digitalRead(leftArrowPin);
      bool pressedRight = digitalRead(rightArrowPin);
      bool pressedEnter = digitalRead(enterPin);
      bool pressedHome = digitalRead(homePin);
      if(pressedHome){
        this->showingAnswer = false;
        this->returnHome();
      }
      if(pressedLeft){
        Serial.println("Previous Question");
        this->questionManager.previousQuestion();
        this->showingAnswer = false;
        this->showCurrentQuestion();
        delay(100);
      }
      else if(pressedRight){
        Serial.println("Next Question");
        this->questionManager.nextQuestion();
        this->showingAnswer = false;
        this->showCurrentQuestion();
        delay(100);
      }
      else if(pressedEnter){
        if(this->showingAnswer){
          this->showingAnswer = false;
          this->showCurrentQuestion();
        }
        else{
          this->showingAnswer = true;
          this->showCurrentAnswer();
        }
        delay(100);
      }
    }

    String title() const override{
      return "Study Mode";
    }

    void setup() override{
      this->lcdQuestionManager.clear();
      delay(transitionMsDelay);
      this->showCurrentQuestion();
    }
};


class ReviewModeWindow : public Window{
  private:
    LCDQuestionManager& lcdQuestionManager;
    QuestionManager& questionManager;
    HomeManager& homeManager;
    unsigned long previousMillis = 0;
    long msInterval;
    bool showingAnswer = false;

    void showCurrentQuestion(bool clearLcd = false){
      String question = questionManager.getQuestion();
      lcdQuestionManager.showQuestionCentered(question, 0, clearLcd);
    }

    void showCurrentAnswer(){
      String answer = questionManager.getAnswer();
      lcdQuestionManager.showAnswer(answer, 3, false);
    }

    void returnHome(){
      this->questionManager.resetIndex();
      homeManager.startHome();
    }

    void checkIfFinishedInterval(){
      unsigned long currentMillis = millis();
      if (!(currentMillis - previousMillis >= msInterval)) {
        return;
      }
      previousMillis = currentMillis;
      if(!showingAnswer){
        showCurrentAnswer();
        showingAnswer = true;
      }
      else{
        questionManager.nextQuestion();
        showingAnswer = false;
        showCurrentQuestion(true);
      }
    }

    void reset(){
        showingAnswer = false;
        showCurrentQuestion(true);
        previousMillis = millis();
    }
    
    
  public:

    ReviewModeWindow(LCDQuestionManager& lcdQuestionManager, QuestionManager& questionManager, HomeManager& homeManager, long msInterval)
                  :lcdQuestionManager(lcdQuestionManager), questionManager(questionManager), homeManager(homeManager){
        this->msInterval = msInterval;
    }

    void run() override{
      bool pressedLeft = digitalRead(leftArrowPin);
      bool pressedRight = digitalRead(rightArrowPin);
      bool pressedHome = digitalRead(homePin);
      if(pressedHome){
        this->showingAnswer = false;
        this->returnHome();
      }
      else if(pressedLeft){
        Serial.println("Previous Question");
        this->questionManager.previousQuestion();
        this->reset();
        delay(100);
      }
      else if(pressedRight){
        Serial.println("Next Question");
        this->questionManager.nextQuestion();
        this->reset();
        delay(100);
      }
      this->checkIfFinishedInterval();
    }

    String title() const override{
      return "Review Mode";
    }

    void setup() override{
      this->previousMillis = millis();
      this->lcdQuestionManager.clear();
      delay(transitionMsDelay);
      this->showCurrentQuestion();
    }
};

class MultiplayerModeWindow : public Window{
  private:
    LCDQuestionManager& lcdQuestionManager;
    QuestionManager& questionManager;
    
  public:

    MultiplayerModeWindow(LCDQuestionManager& lcdQuestionManager, QuestionManager& questionManager)
                  :lcdQuestionManager(lcdQuestionManager), questionManager(questionManager){

    }

    void run() override{
      
    }

    String title() const override{
      return "Multiplayer Mode";
    }

    void setup() override{}
};

void printChoices(String *choices){
  for(int index = 0; index < numberOfButtons; index++){
    Serial.println(choiceLetters[index] + ": " + choices[index]);
  }
}

void printQuestion(String question){
  Serial.println("\n" + question + "\n");
}

void startWifi(String wifiSSID, String wifiPassword, int wifiChannel){
  WiFi.begin(wifiSSID, wifiPassword, wifiChannel);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Connected to Wi-Fi");
}

void setupLcd(LiquidCrystal_I2C& lcd){
  lcd.init();
  lcd.backlight();
}

void setupPins(){
  pinMode(rgbRedPin, OUTPUT);
  pinMode(rgbGreenPin, OUTPUT);
  pinMode(rgbBluePin, OUTPUT);

  pinMode(homePin, INPUT);
}

void addIntroCharacters(LCDManager lcdManager) {
    lcdManager.addCharacter(1, logo0x6);
    lcdManager.addCharacter(2, logo0x7);
    lcdManager.addCharacter(3, logo0x8);
    lcdManager.addCharacter(4, logo0x9);
    lcdManager.addCharacter(5, logo1x6);
    lcdManager.addCharacter(6, logo1x7);
    lcdManager.addCharacter(7, logo1x8);
    lcdManager.addCharacter(8, logo1x9);
}

void addHomeLogo(LCDManager lcdManager) {
    lcdManager.addCharacter(0, leftArrow);
    lcdManager.addCharacter(1, rightArrow);
}

void showIntro(LCDManager lcdManager){
  lcdManager.showCharacter(4, 1, 1);
  lcdManager.showCharacter(5, 1, 2);
  lcdManager.showCharacter(6, 1, 3);
  lcdManager.showCharacter(7, 1, 4);
  lcdManager.showCharacter(4, 2, 5);
  lcdManager.showCharacter(5, 2, 6);
  lcdManager.showCharacter(6, 2, 7);
  lcdManager.showCharacter(7, 2, 8);

  // lcdManager.showCharacter(8, 0, 1);
  // lcdManager.showCharacter(9, 0, 2);
  // lcdManager.showCharacter(10, 0, 3);
  // lcdManager.showCharacter(11, 0, 4);
  // lcdManager.showCharacter(8, 1, 5);
  // lcdManager.showCharacter(9, 1, 6);
  // lcdManager.showCharacter(10, 1, 7);
  // lcdManager.showCharacter(11, 1, 8);

  lcdManager.show(9, 1, title);
  // lcdManager.show(((lcdWidth / 2) - (title.length() / 2)), 2, title);
}

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(lcdAddress, lcdWidth, lcdHeight);
LCDManager lcdManager(lcd);
LCDQuestionManager lcdQuestionManager(lcdManager);
DatabaseManager databaseManager(QUESTION_LOG_URL);

ButtonManager buttonManager(pinArray);
WindowManager windowManager = WindowManager();
QuestionManager questionManager = QuestionManager();
ScoreManager scoreManager = ScoreManager();

HomeManager homeManager(lcdManager, windowManager, homeWindowPins);
QuizModeWindow quizModeWindow(lcdQuestionManager, questionManager, buttonManager, homeManager, scoreManager, databaseManager);
StudyModeWindow studyModeWindow(lcdQuestionManager, questionManager, homeManager);
ReviewModeWindow reviewModeWindow(lcdQuestionManager, questionManager, homeManager, reviewInterval);

APIManager apiManager(API_URL, JSON_SIZE);
JsonDocument mainQuestionJson;

void setupWindow(){
  windowManager.addWindow(&quizModeWindow);
  windowManager.addWindow(&studyModeWindow);
  windowManager.addWindow(&reviewModeWindow);
}

void setup() {
  Serial.begin(115200);

  setupLcd(lcd);
  setupPins();
  setupWindow();
  
  addIntroCharacters(lcdManager);
  delay(transitionMsDelay);

  showIntro(lcdManager);

  startWifi(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);
  mainQuestionJson = apiManager.getJson();

  questionManager.setTitle(mainQuestionJson["title"].as<String>());
  questionManager.setQuestions(mainQuestionJson["questions"].as<JsonArray>());

  lcdManager.clear();
  addHomeLogo(lcdManager);
  delay(transitionMsDelay);
  
  homeManager.startHome();
  
}

void loop() {
  if(homeManager.running()){
    homeManager.run();
  }
  else{
    windowManager.runCurrentWindow();
  }
  delay(mainLoopMsDelay); 
}
$abcdeabcde151015202530354045505560fghijfghij
$abcdeabcde151015202530fghijfghij