#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display(128, 64, &Wire, -1);

int LDR_PIN      = A3;
int LDR_THRESHOLD = 80;
int SS1_PIN      = 8;
int SS2_PIN      = 10;
int BUTTON_PIN   = 2;
int C2_PIN       = A0;
int S1_PIN       = A1;
int S0_PIN       = A2;
int PB1_PIN      = 9;
int PB2_PIN      = 11;
int BUZZER_PIN   = 12;

int inputPins[7]  = {BUTTON_PIN, SS1_PIN, SS2_PIN, PB1_PIN, PB2_PIN, C2_PIN, S1_PIN};

bool menuShown = false;
int p1Score    = 0;
int p2Score    = 0;

int qType[12]  = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0};
int qValue[12] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3, 9};

String binTable[10] = {
  "0000","0001","0010","0011","0100",
  "0101","0110","0111","1000","1001"
};

void drawText(int size, int x, int y, String text) {
  display.setTextSize(size);
  display.setCursor(x, y);
  display.print(text);
}

void showScreen(String a, String b) {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  drawText(2, 0, 15, a);
  drawText(2, 0, 38, b);
  display.display();
}

void playTones(int freqs[], int durations[], int count) {
  for (int i = 0; i < count; i++) {
    tone(BUZZER_PIN, freqs[i]);
    delay(durations[i]);
  }
  noTone(BUZZER_PIN);
}

void winSound() {
  int freqs[3]     = {523, 659, 784};
  int durations[3] = {200, 250, 300};
  playTones(freqs, durations, 3);
}

void clickSound() {
  int freqs[1]     = {1000};
  int durations[1] = {60};
  playTones(freqs, durations, 1);
}

void correctSound() {
  int freqs[2]     = {784, 988};
  int durations[2] = {120, 150};
  playTones(freqs, durations, 2);
}

void wrongSound() {
  int freqs[1]     = {200};
  int durations[1] = {300};
  playTones(freqs, durations, 1);
}

void showIdle() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  drawText(2, 0, 15, "COVER LDR");
  drawText(2, 0, 35, "TO PLAY!");
  display.display();
  menuShown = false;
}

void showPointWon(int player, int p1, int p2, bool correct) {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  if (correct) {
    drawText(3, 10, 0, "P" + String(player));
    drawText(2, 10, 30, "GETS POINT");
  } else {
    drawText(3, 10, 0, "WRONG!");
  }
  drawText(1, 10, 52, "P1:" + String(p1) + " P2:" + String(p2));
  display.display();
}

void showWinner() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  drawText(3, 10, 5, (p1Score > p2Score) ? "P1" : "P2");
  drawText(3, 10, 35, "WINS!");
  display.display();
  winSound();
  delay(800);
  display.clearDisplay();
  drawText(1, 10, 20, "Cover LDR");
  drawText(1, 10, 35, "to restart");
  display.display();
}

void adderMode() {
  showScreen("2 BIT", "ADDER");
  display.drawLine(0, 38, 127, 38, SSD1306_WHITE);
  drawText(1, 8, 45, "Press button");
  drawText(1, 8, 56, "for result");
  display.display();
  delay(1500);

  while (true) {
    if (analogRead(LDR_PIN) < LDR_THRESHOLD) return;

    if (digitalRead(BUTTON_PIN)) {
      delay(50);
      int bits[3]    = {digitalRead(C2_PIN), digitalRead(S1_PIN), digitalRead(S0_PIN)};
      int decVal     = (bits[0] << 2) | (bits[1] << 1) | bits[2];

      display.clearDisplay();
      display.setTextColor(SSD1306_WHITE);
      drawText(2, 0, 0, "BIN:");
      for (int i = 0; i < 3; i++) display.print(bits[i]);
      drawText(2, 0, 32, "DEC:" + String(decVal));
      display.display();
      delay(500);
    }
  }
}

void handleAnswer(int player, bool correctIsA, bool pressedA) {
  bool correct = (pressedA && correctIsA) || (!pressedA && !correctIsA);
  if (correct) {
    if (player == 1) p1Score++; else p2Score++;
    correctSound();
  } else {
    if (player == 1) p2Score++; else p1Score++;
    wrongSound();
  }
  showPointWon(player, p1Score, p2Score, correct);
}

void gameMode() {
  showScreen("BINARY", "BATTLE!");
  delay(1500);

  for (int i = 3; i > 0; i--) {
    display.clearDisplay();
    drawText(4, 55, 15, String(i));
    display.display();
    delay(800);
  }

  while (true) {
    if (p1Score >= 3 || p2Score >= 3) {
      showWinner();
      while (analogRead(LDR_PIN) >= LDR_THRESHOLD) {}
      delay(500);
      return;
    }

    int q          = random(0, 12);
    int type       = qType[q];
    int value      = qValue[q];
    int wrong;
    do { wrong = random(0, 10); } while (wrong == value);

    bool correctIsA = random(0, 2);
    String optionA  = correctIsA ? (type == 0 ? binTable[value] : String(value))
                                 : (type == 0 ? binTable[wrong]  : String(wrong));
    String optionB  = correctIsA ? (type == 0 ? binTable[wrong]  : String(wrong))
                                 : (type == 0 ? binTable[value] : String(value));

    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);
    drawText(1, 0, 0, "P1 SW=A BTN=B");
    drawText(1, 0, 9, "P2 SW=A BTN=B");
    display.drawLine(0, 18, 127, 18, SSD1306_WHITE);
    drawText(2, 20, 24, type == 0 ? String(value) : binTable[value]);
    drawText(1, 0, 48, "A:" + optionA);
    drawText(1, 70, 48, "B:" + optionB);
    display.display();

    bool answered = false;
    while (!answered) {
      bool p1A = digitalRead(SS1_PIN);
      bool p1B = digitalRead(PB1_PIN);
      bool p2A = digitalRead(SS2_PIN);
      bool p2B = digitalRead(PB2_PIN);

      if (p1A || p1B || p2A || p2B) clickSound();

      if (p1A || p1B) { handleAnswer(1, correctIsA, p1A); answered = true; }
      if (p2A || p2B) { handleAnswer(2, correctIsA, p2A); answered = true; }
    }
    delay(1500);
  }
}

void showMenu() {
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  drawText(2, 10, 0, "CHOOSE");
  drawText(1, 0, 25, "SW1: 2-BIT ADDER");
  drawText(1, 0, 42, "SW2: BINARY GAME");
  display.display();
  menuShown = true;

  while (true) {
    if (digitalRead(SS1_PIN)) { delay(200); adderMode(); showIdle(); break; }
    if (digitalRead(SS2_PIN)) { delay(200); p1Score = 0; p2Score = 0; gameMode(); showIdle(); break; }
  }
}

void setup() {
  for (int i = 0; i < 7; i++) pinMode(inputPins[i], INPUT);
  pinMode(S0_PIN, INPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  showIdle();
}

void loop() {
  if (!menuShown && analogRead(LDR_PIN) < LDR_THRESHOLD) {
    delay(100);
    showMenu();
  }
}
