/*
 * Accessible IoT Interface Lab
 *
 * Demonstrates core UI/UX accessibility principles:
 * - Multimodal feedback (visual, audio, timing)
 * - High contrast mode for visual impairments
 * - Large text mode for readability
 * - Audio confirmation sounds
 * - Persistent user preferences (NVS)
 * - Debounced button input
 *
 * Compatible with: ESP32 DevKit, Wokwi Simulator
 * Display: SSD1306 OLED 128x64 (I2C)
 */

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

// ========== DISPLAY CONFIGURATION ==========
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ========== PIN DEFINITIONS ==========
#define BTN_UP 25
#define BTN_DOWN 26
#define BTN_SELECT 27
#define BTN_BACK 14
#define BUZZER_PIN 13
#define LED_PIN 2

// ========== TIMING CONSTANTS ==========
#define DEBOUNCE_DELAY 200
#define MENU_ANIMATION_DELAY 50
#define FEEDBACK_DURATION 100

// ========== AUDIO FEEDBACK FREQUENCIES ==========
#define TONE_NAVIGATE 800    // Navigation beep
#define TONE_SELECT 1200     // Selection confirmation
#define TONE_BACK 400        // Back/cancel tone
#define TONE_ERROR 200       // Error tone
#define TONE_SUCCESS 1600    // Success confirmation
#define TONE_TOGGLE_ON 1000  // Toggle enabled
#define TONE_TOGGLE_OFF 600  // Toggle disabled

// ========== USER PREFERENCES ==========
Preferences preferences;

struct AccessibilitySettings {
  bool highContrast;
  bool largeText;
  bool soundEnabled;
  uint8_t brightness;
} settings;

// ========== MENU SYSTEM ==========
enum MenuState {
  MENU_HOME,
  MENU_MAIN,
  MENU_SETTINGS,
  MENU_ACCESSIBILITY,
  MENU_SYSTEM_INFO
};

MenuState currentMenu = MENU_HOME;
int menuIndex = 0;
int maxMenuItems = 3;

// Menu item labels
const char* mainMenuItems[] = {"Home", "Settings", "System Info"};
const char* accessibilityItems[] = {"High Contrast", "Large Text", "Sound", "Brightness", "Back"};

// ========== DEBOUNCING ==========
unsigned long lastButtonPress = 0;

// ========== FUNCTION DECLARATIONS ==========
void loadSettings();
void saveSettings();
void playTone(int frequency, int duration);
void playNavigateSound();
void playSelectSound();
void playBackSound();
void playToggleSound(bool enabled);
void drawMenu();
void drawHome();
void drawMainMenu();
void drawAccessibilityMenu();
void drawSystemInfo();
void handleNavigation(int direction);
void handleSelect();
void handleBack();
void updateDisplay();
void showFeedback(const char* message, bool success);
void blinkLED(int times);

// ========== SETUP ==========
void setup() {
  Serial.begin(115200);
  Serial.println("\n=== Accessible IoT Interface Lab ===");
  Serial.println("Demonstrating UI/UX accessibility principles");

  // Initialize pins
  pinMode(BTN_UP, INPUT_PULLUP);
  pinMode(BTN_DOWN, INPUT_PULLUP);
  pinMode(BTN_SELECT, INPUT_PULLUP);
  pinMode(BTN_BACK, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);

  // Initialize display
  Wire.begin(21, 22);
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println("ERROR: SSD1306 allocation failed");
    for (;;); // Halt if display fails
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.display();

  // Load saved preferences
  loadSettings();

  // Apply brightness setting
  display.dim(settings.brightness < 50);

  // Welcome feedback
  Serial.println("Settings loaded:");
  Serial.printf("  High Contrast: %s\n", settings.highContrast ? "ON" : "OFF");
  Serial.printf("  Large Text: %s\n", settings.largeText ? "ON" : "OFF");
  Serial.printf("  Sound: %s\n", settings.soundEnabled ? "ON" : "OFF");
  Serial.printf("  Brightness: %d%%\n", settings.brightness);

  // Startup sound and visual
  if (settings.soundEnabled) {
    playTone(TONE_SUCCESS, 100);
    delay(50);
    playTone(TONE_SUCCESS + 200, 100);
  }
  blinkLED(2);

  // Show home screen
  drawHome();
}

// ========== MAIN LOOP ==========
void loop() {
  // Check for button presses with debouncing
  if (millis() - lastButtonPress > DEBOUNCE_DELAY) {

    // UP button
    if (digitalRead(BTN_UP) == LOW) {
      lastButtonPress = millis();
      handleNavigation(-1);
      Serial.println("BTN: UP pressed");
    }

    // DOWN button
    else if (digitalRead(BTN_DOWN) == LOW) {
      lastButtonPress = millis();
      handleNavigation(1);
      Serial.println("BTN: DOWN pressed");
    }

    // SELECT button
    else if (digitalRead(BTN_SELECT) == LOW) {
      lastButtonPress = millis();
      handleSelect();
      Serial.println("BTN: SELECT pressed");
    }

    // BACK button
    else if (digitalRead(BTN_BACK) == LOW) {
      lastButtonPress = millis();
      handleBack();
      Serial.println("BTN: BACK pressed");
    }
  }

  delay(10); // Small delay for stability
}

// ========== SETTINGS MANAGEMENT ==========
void loadSettings() {
  preferences.begin("access", false);
  settings.highContrast = preferences.getBool("contrast", false);
  settings.largeText = preferences.getBool("largetext", false);
  settings.soundEnabled = preferences.getBool("sound", true);
  settings.brightness = preferences.getUChar("brightness", 100);
  preferences.end();
}

void saveSettings() {
  preferences.begin("access", false);
  preferences.putBool("contrast", settings.highContrast);
  preferences.putBool("largetext", settings.largeText);
  preferences.putBool("sound", settings.soundEnabled);
  preferences.putUChar("brightness", settings.brightness);
  preferences.end();
  Serial.println("Settings saved to NVS");
}

// ========== AUDIO FEEDBACK ==========
void playTone(int frequency, int duration) {
  if (!settings.soundEnabled) return;
  tone(BUZZER_PIN, frequency, duration);
  delay(duration);
  noTone(BUZZER_PIN);
}

void playNavigateSound() {
  playTone(TONE_NAVIGATE, 30);
}

void playSelectSound() {
  playTone(TONE_SELECT, 50);
  delay(30);
  playTone(TONE_SELECT + 200, 50);
}

void playBackSound() {
  playTone(TONE_BACK, 80);
}

void playToggleSound(bool enabled) {
  if (enabled) {
    playTone(TONE_TOGGLE_ON, 50);
    delay(30);
    playTone(TONE_TOGGLE_ON + 400, 80);
  } else {
    playTone(TONE_TOGGLE_OFF + 200, 50);
    delay(30);
    playTone(TONE_TOGGLE_OFF, 80);
  }
}

void playErrorSound() {
  playTone(TONE_ERROR, 100);
  delay(50);
  playTone(TONE_ERROR, 100);
}

// ========== VISUAL FEEDBACK ==========
void blinkLED(int times) {
  for (int i = 0; i < times; i++) {
    digitalWrite(LED_PIN, HIGH);
    delay(100);
    digitalWrite(LED_PIN, LOW);
    delay(100);
  }
}

void showFeedback(const char* message, bool success) {
  // Visual feedback on display
  display.fillRect(0, 54, 128, 10, settings.highContrast ? SSD1306_WHITE : SSD1306_BLACK);
  display.setTextColor(settings.highContrast ? SSD1306_BLACK : SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(4, 55);
  display.print(message);
  display.display();

  // Audio feedback
  if (success) {
    playTone(TONE_SUCCESS, 100);
  } else {
    playErrorSound();
  }

  // LED feedback
  blinkLED(success ? 1 : 3);

  delay(500);
  updateDisplay();
}

// ========== NAVIGATION HANDLING ==========
void handleNavigation(int direction) {
  menuIndex += direction;

  // Wrap around menu
  if (menuIndex < 0) menuIndex = maxMenuItems - 1;
  if (menuIndex >= maxMenuItems) menuIndex = 0;

  playNavigateSound();
  blinkLED(1);
  updateDisplay();
}

void handleSelect() {
  playSelectSound();
  blinkLED(1);

  switch (currentMenu) {
    case MENU_HOME:
      currentMenu = MENU_MAIN;
      menuIndex = 0;
      maxMenuItems = 3;
      break;

    case MENU_MAIN:
      switch (menuIndex) {
        case 0: // Home
          currentMenu = MENU_HOME;
          break;
        case 1: // Settings -> Accessibility
          currentMenu = MENU_ACCESSIBILITY;
          menuIndex = 0;
          maxMenuItems = 5;
          break;
        case 2: // System Info
          currentMenu = MENU_SYSTEM_INFO;
          break;
      }
      break;

    case MENU_ACCESSIBILITY:
      switch (menuIndex) {
        case 0: // Toggle High Contrast
          settings.highContrast = !settings.highContrast;
          playToggleSound(settings.highContrast);
          saveSettings();
          Serial.printf("High Contrast: %s\n", settings.highContrast ? "ON" : "OFF");
          break;
        case 1: // Toggle Large Text
          settings.largeText = !settings.largeText;
          playToggleSound(settings.largeText);
          saveSettings();
          Serial.printf("Large Text: %s\n", settings.largeText ? "ON" : "OFF");
          break;
        case 2: // Toggle Sound
          settings.soundEnabled = !settings.soundEnabled;
          // Play sound BEFORE disabling
          if (!settings.soundEnabled) {
            tone(BUZZER_PIN, TONE_TOGGLE_OFF + 200, 50);
            delay(30);
            tone(BUZZER_PIN, TONE_TOGGLE_OFF, 80);
            noTone(BUZZER_PIN);
          } else {
            playToggleSound(true);
          }
          saveSettings();
          Serial.printf("Sound: %s\n", settings.soundEnabled ? "ON" : "OFF");
          break;
        case 3: // Adjust Brightness
          settings.brightness += 25;
          if (settings.brightness > 100) settings.brightness = 25;
          display.dim(settings.brightness < 50);
          playTone(800 + (settings.brightness * 8), 50);
          saveSettings();
          Serial.printf("Brightness: %d%%\n", settings.brightness);
          break;
        case 4: // Back
          handleBack();
          return;
      }
      break;

    case MENU_SYSTEM_INFO:
      // Return to main menu
      handleBack();
      return;
  }

  updateDisplay();
}

void handleBack() {
  playBackSound();
  blinkLED(1);

  switch (currentMenu) {
    case MENU_HOME:
      // Already at home, do nothing
      break;
    case MENU_MAIN:
      currentMenu = MENU_HOME;
      break;
    case MENU_ACCESSIBILITY:
    case MENU_SYSTEM_INFO:
      currentMenu = MENU_MAIN;
      menuIndex = 0;
      maxMenuItems = 3;
      break;
  }

  updateDisplay();
}

// ========== DISPLAY RENDERING ==========
void updateDisplay() {
  switch (currentMenu) {
    case MENU_HOME:
      drawHome();
      break;
    case MENU_MAIN:
      drawMainMenu();
      break;
    case MENU_ACCESSIBILITY:
      drawAccessibilityMenu();
      break;
    case MENU_SYSTEM_INFO:
      drawSystemInfo();
      break;
  }
}

void drawHome() {
  display.clearDisplay();

  // Background for high contrast mode
  if (settings.highContrast) {
    display.fillScreen(SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.setTextColor(SSD1306_WHITE);
  }

  // Title
  display.setTextSize(settings.largeText ? 2 : 1);
  display.setCursor(settings.largeText ? 10 : 25, 4);
  display.print("IoT Device");

  // Status indicator with icon
  display.setTextSize(1);
  display.setCursor(4, settings.largeText ? 28 : 20);
  display.print("Status: ");
  display.print("ONLINE");

  // Draw status indicator circle
  int circleX = settings.largeText ? 100 : 75;
  int circleY = settings.largeText ? 32 : 23;
  if (settings.highContrast) {
    display.fillCircle(circleX, circleY, 4, SSD1306_BLACK);
  } else {
    display.fillCircle(circleX, circleY, 4, SSD1306_WHITE);
  }

  // Show active accessibility features
  display.setCursor(4, settings.largeText ? 44 : 36);
  if (settings.highContrast || settings.largeText) {
    display.print("A11y: ");
    if (settings.highContrast) display.print("HC ");
    if (settings.largeText) display.print("LT ");
  }

  // Navigation hint
  display.setCursor(4, 54);
  display.setTextSize(1);
  display.print("[SELECT] Open Menu");

  display.display();
}

void drawMainMenu() {
  display.clearDisplay();

  if (settings.highContrast) {
    display.fillScreen(SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.setTextColor(SSD1306_WHITE);
  }

  // Title
  display.setTextSize(settings.largeText ? 2 : 1);
  display.setCursor(settings.largeText ? 20 : 40, 2);
  display.print("MENU");

  // Draw menu items
  display.setTextSize(settings.largeText ? 2 : 1);
  int yStart = settings.largeText ? 22 : 18;
  int ySpacing = settings.largeText ? 14 : 12;

  for (int i = 0; i < 3; i++) {
    int y = yStart + (i * ySpacing);

    // Highlight selected item
    if (i == menuIndex) {
      if (settings.highContrast) {
        display.fillRect(0, y - 2, 128, ySpacing, SSD1306_BLACK);
        display.setTextColor(SSD1306_WHITE);
      } else {
        display.fillRect(0, y - 2, 128, ySpacing, SSD1306_WHITE);
        display.setTextColor(SSD1306_BLACK);
      }
      display.setCursor(4, y);
      display.print("> ");
    } else {
      display.setTextColor(settings.highContrast ? SSD1306_BLACK : SSD1306_WHITE);
      display.setCursor(4, y);
      display.print("  ");
    }
    display.print(mainMenuItems[i]);
  }

  // Navigation hints
  display.setTextSize(1);
  display.setTextColor(settings.highContrast ? SSD1306_BLACK : SSD1306_WHITE);
  display.setCursor(4, 54);
  display.print("[UP/DN] Nav [SEL] OK");

  display.display();
}

void drawAccessibilityMenu() {
  display.clearDisplay();

  if (settings.highContrast) {
    display.fillScreen(SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.setTextColor(SSD1306_WHITE);
  }

  // Title
  display.setTextSize(1);
  display.setCursor(20, 2);
  display.print("ACCESSIBILITY");

  // Draw menu items with current values
  int yStart = 14;
  int ySpacing = 10;

  for (int i = 0; i < 5; i++) {
    int y = yStart + (i * ySpacing);

    // Highlight selected item
    if (i == menuIndex) {
      if (settings.highContrast) {
        display.fillRect(0, y - 1, 128, ySpacing, SSD1306_BLACK);
        display.setTextColor(SSD1306_WHITE);
      } else {
        display.fillRect(0, y - 1, 128, ySpacing, SSD1306_WHITE);
        display.setTextColor(SSD1306_BLACK);
      }
    } else {
      display.setTextColor(settings.highContrast ? SSD1306_BLACK : SSD1306_WHITE);
    }

    display.setCursor(4, y);
    if (i == menuIndex) display.print(">");
    display.setCursor(12, y);
    display.print(accessibilityItems[i]);

    // Show current values
    display.setCursor(90, y);
    switch (i) {
      case 0: display.print(settings.highContrast ? "[ON]" : "[OFF]"); break;
      case 1: display.print(settings.largeText ? "[ON]" : "[OFF]"); break;
      case 2: display.print(settings.soundEnabled ? "[ON]" : "[OFF]"); break;
      case 3:
        display.print("[");
        display.print(settings.brightness);
        display.print("%]");
        break;
      case 4: display.print(""); break;
    }
  }

  // Navigation hints
  display.setTextColor(settings.highContrast ? SSD1306_BLACK : SSD1306_WHITE);
  display.setCursor(4, 54);
  display.print("[SEL] Toggle [BACK]");

  display.display();
}

void drawSystemInfo() {
  display.clearDisplay();

  if (settings.highContrast) {
    display.fillScreen(SSD1306_WHITE);
    display.setTextColor(SSD1306_BLACK);
  } else {
    display.setTextColor(SSD1306_WHITE);
  }

  // Title
  display.setTextSize(1);
  display.setCursor(25, 2);
  display.print("SYSTEM INFO");

  // System information
  display.setCursor(4, 16);
  display.print("Device: ESP32");

  display.setCursor(4, 26);
  display.print("Uptime: ");
  display.print(millis() / 1000);
  display.print("s");

  display.setCursor(4, 36);
  display.print("Free Heap: ");
  display.print(ESP.getFreeHeap() / 1024);
  display.print("KB");

  display.setCursor(4, 46);
  display.print("Display: 128x64 OLED");

  // Navigation hint
  display.setCursor(4, 54);
  display.print("[BACK] Return");

  display.display();
}
