#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 rgbRedPin 0
#define rgbGreenPin 2
#define rgbBluePin 15

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

#define numberOfButtons 4

int pinArray[numberOfButtons] = {pinA, pinB, pinC, pinD};

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

byte leftArrow[] = { B10000, B01000, B00100, B00010, B00100, B01000, B10000, B00000 };
byte rightArrow[] = { B00001, B00010, B00100, B01000, B00100, B00010, B00001, 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 QuestionManager{
  private:
    JsonArray questionsArray;
    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* 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;
    }

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

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

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 HomeManager{
  private:
    int pageIndex = 2;

    LCDManager& lcdManager;

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

    void printRightArrow(){
      lcdManager.showCharacter(lcdWidth - 1, 1, 1);
    }
    
  public:
    HomeManager(LCDManager& lcdManagerInput) : lcdManager(lcdManagerInput) {
      this->lcdManager.addCharacter(0, leftArrow);
      this->lcdManager.addCharacter(1, rightArrow);
    }

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

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

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

    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){
      this->lcdManager.clear();

      std::vector<String> questionVector = this->splitSentence(question, lcdWidth);
      this->printQuestionFirstLine(questionVector[0]);
      this->printQuestionSecondLine(questionVector[1]);

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

};

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

    virtual String title() const = 0;
};


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

  public:
    void addWindow(Window* window){
        this->windowVector.push_back(window);
    }
    void runCurrentWindow(){
      if (!windowVector.empty()) {
            this->windowVector[this->currentWindowIndex]->run();
        }
    }
    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 QuizModeWindow : public Window{
  private:
    LCDQuestionManager& lcdQuestionManager;
    QuestionManager& questionManager;
    ButtonManager& buttonManager;

  public:

    QuizModeWindow(LCDQuestionManager& lcdQuestionManager, QuestionManager& questionManager, ButtonManager& buttonManager)
                  :lcdQuestionManager(lcdQuestionManager), questionManager(questionManager), buttonManager(buttonManager){

    }

    void run() const override{
      String chosenAnswer = this->buttonManager.checkIfPressedButton();
      if(chosenAnswer != ""){
        Serial.println("\nAnswer: " + chosenAnswer + " ");
        Serial.print("Correct: ");
        if(questionManager.checkIfCorrectAnswer(chosenAnswer)){
          Serial.println("True");
          colorRgbLed(false, true, false);
        }
        else{
          Serial.println("False");
          colorRgbLed(true, false, false);
        }
        questionManager.nextQuestion();
        String* choices = questionManager.getChoices();
        String question = questionManager.getQuestion();
        
        buttonManager.setChoices(choices);

        lcdQuestionManager.show(question, choices);

        printQuestion(question);
        printChoices(choices);
        delay(100); 
      }
    }

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

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

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

    }

    void run() const override{
      
    }

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

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

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

    }

    void run() const override{
      
    }

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

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 colorRgbLed(bool red, bool green, bool blue){
    digitalWrite(rgbRedPin, red);
    digitalWrite(rgbGreenPin, green);
    digitalWrite(rgbBluePin, blue);
}


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

QuestionManager questionManager = QuestionManager();
APIManager apiManager(API_URL, JSON_SIZE);
ButtonManager buttonManager(pinArray);
JsonDocument mainQuestionJson;

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

  lcd.init();
  lcd.backlight();
  
  pinMode(rgbRedPin, OUTPUT);
  pinMode(rgbGreenPin, OUTPUT);
  pinMode(rgbBluePin, OUTPUT);

  startWifi(WIFI_SSID, WIFI_PASSWORD, WIFI_CHANNEL);

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

  String* choices = questionManager.getChoices();
  String question = questionManager.getQuestion();
  buttonManager.setChoices(choices);

  lcdQuestionManager.show(question, choices);

  printQuestion(question);
  printChoices(choices);

}

void loop() {
  String chosenAnswer = buttonManager.checkIfPressedButton();
  if(chosenAnswer != ""){
    Serial.println("\nAnswer: " + chosenAnswer + " ");
    Serial.print("Correct: ");
    if(questionManager.checkIfCorrectAnswer(chosenAnswer)){
      Serial.println("True");
      colorRgbLed(false, true, false);
    }
    else{
      Serial.println("False");
      colorRgbLed(true, false, false);
    }
    questionManager.nextQuestion();
    String* choices = questionManager.getChoices();
    String question = questionManager.getQuestion();
    
    buttonManager.setChoices(choices);

    lcdQuestionManager.show(question, choices);

    printQuestion(question);
    printChoices(choices);
    delay(100); 
  }
  delay(50); 
}
$abcdeabcde151015202530354045505560fghijfghij