// Mission 1 · Endor-Pad : le banc d'essai de la manette
// Chaque bouton, flèche, gâchette et joystick fait réagir quelque chose :
// la LED bleue de l'ESP32, la barre lumineuse de la manette, ses vibrations
// et le moniteur série. Quand les 19 commandes sont validées : célébration !

#include <Bluepad32.h>

// ================= Réglages =================
const int LED = 2;             // LED bleue intégrée (GPIO 2)
const int ZONE_MORTE = 100;    // joystick en dessous de 100 : considéré au repos
const int SEUIL_DIRECTION = 400; // joystick poussé presque au bout (max ~510)

// ================= La manette =================
ControllerPtr manette = nullptr;

// ================= Les scénarios de la LED bleue =================
// 0 éteint, 1 lent, 2 rapide, 3 SOS, 4 respiration, 5 pilote (au joystick)
const int NB_SCENARIOS = 6;
const char* NOMS_SCENARIOS[NB_SCENARIOS] = {
  "éteint", "clignotement lent", "clignotement rapide", "SOS", "respiration", "pilote"
};
int scenario = 0;
int vitesse = 100;             // en % : 100 = normal, 200 = deux fois plus vite
unsigned long horloge = 0;     // horloge des scénarios (avance selon la vitesse)
unsigned long dernierTemps = 0;

// ================= La check-list des commandes =================
enum {
  T_CROIX, T_ROND, T_TRIANGLE, T_CARRE,
  T_HAUT, T_BAS, T_GAUCHE, T_DROITE,
  T_L1, T_R1, T_L2, T_R2, T_L3, T_R3,
  T_SHARE, T_OPTIONS, T_PS,
  T_STICK_G, T_STICK_D,
  NB_TESTS
};
const char* NOMS_TESTS[NB_TESTS] = {
  "✕", "○", "△", "□",
  "flèche ↑", "flèche ↓", "flèche ←", "flèche →",
  "L1", "R1", "L2", "R2", "L3 (clic joystick gauche)", "R3 (clic joystick droit)",
  "SHARE", "OPTIONS", "PS",
  "joystick gauche (4 directions)", "joystick droit (4 directions)"
};
bool teste[NB_TESTS];
int nbTestes = 0;

// Directions atteintes par chaque joystick : 1 haut, 2 bas, 4 gauche, 8 droite
int directionsG = 0;
int directionsD = 0;

// ================= Barre lumineuse et vibrations =================
uint8_t couleurR = 0, couleurG = 0, couleurB = 255;  // couleur choisie aux boutons
uint8_t envoyeR = 1, envoyeG = 1, envoyeB = 1;       // dernière couleur envoyée
unsigned long dernierEnvoiCouleur = 0;
unsigned long dernierEnvoiVibration = 0;
unsigned long finCelebration = 0;

// Radar (SHARE) : affiche en continu les valeurs des joysticks et des gâchettes
bool radar = false;
unsigned long dernierRadar = 0;

// ================= Mémoire des boutons (pour détecter l'appui) =================
bool avant[NB_TESTS];

// Renvoie true UNIQUEMENT au moment où le bouton vient d'être enfoncé.
bool vientDAppuyer(int numero, bool appuye) {
  bool nouveau = appuye && !avant[numero];
  avant[numero] = appuye;
  return nouveau;
}

// ------------------------------------------------------------------
void celebrer() {
  Serial.println("\n🎉🎉🎉 MANETTE 100 % OPÉRATIONNELLE ! 🎉🎉🎉");
  Serial.println("Mission 1 réussie. Que la Force soit avec toi, Nathan !");
  finCelebration = millis() + 4000;             // 4 secondes d'arc-en-ciel
  manette->playDualRumble(0, 800, 255, 255);    // grosse vibration
}

void valider(int numero) {
  if (teste[numero]) return;                    // déjà validé : rien à faire
  teste[numero] = true;
  nbTestes++;
  Serial.printf("   ✔ Nouveau : %s  (%d/%d)\n", NOMS_TESTS[numero], nbTestes, NB_TESTS);
  if (nbTestes == NB_TESTS) celebrer();
}

void afficherBilan() {
  Serial.printf("\n📋 Bilan : %d/%d commandes validées\n", nbTestes, NB_TESTS);
  for (int i = 0; i < NB_TESTS; i++) {
    if (!teste[i]) Serial.printf("   ☐ reste à tester : %s\n", NOMS_TESTS[i]);
  }
  Serial.printf("   Scénario : %s, vitesse %d %%, batterie %d %%\n",
                NOMS_SCENARIOS[scenario], vitesse, manette->battery() * 100 / 255);
}

void choisirScenario(int s, uint8_t r, uint8_t g, uint8_t b) {
  scenario = s;
  couleurR = r; couleurG = g; couleurB = b;
  Serial.printf("   → LED : %s\n", NOMS_SCENARIOS[scenario]);
}

// Couleur de l'arc-en-ciel : angle de 0 à 359 degrés → rouge, vert, bleu
void arcEnCiel(int angle, uint8_t &r, uint8_t &g, uint8_t &b) {
  angle = (angle % 360 + 360) % 360;
  int zone = angle / 60;
  int x = (angle % 60) * 255 / 60;              // monte de 0 à 255 dans la zone
  switch (zone) {
    case 0: r = 255;     g = x;       b = 0;       break;
    case 1: r = 255 - x; g = 255;     b = 0;       break;
    case 2: r = 0;       g = 255;     b = x;       break;
    case 3: r = 0;       g = 255 - x; b = 255;     break;
    case 4: r = x;       g = 0;       b = 255;     break;
    default: r = 255;    g = 0;       b = 255 - x; break;
  }
}

// ================= Connexion / déconnexion =================
void onConnectedController(ControllerPtr ctl) {
  if (manette != nullptr) {
    Serial.println("Une manette est déjà connectée : la nouvelle est ignorée.");
    return;
  }
  manette = ctl;
  Serial.printf("\n🎮 Manette connectée : %s\n", ctl->getModelName().c_str());
  Serial.println("Teste chaque bouton, flèche, gâchette et joystick.");
  Serial.println("OPTIONS = bilan, SHARE = radar des joysticks.");
  envoyeR = envoyeG = envoyeB = 1;              // force le renvoi de la couleur
}

void onDisconnectedController(ControllerPtr ctl) {
  if (manette == ctl) {
    manette = nullptr;
    Serial.println("\n❌ Manette déconnectée");
  }
}

// ================= Lecture des boutons =================
void lireBoutons() {
  // --- Les 4 boutons de face : un scénario + une couleur chacun ---
  if (vientDAppuyer(T_CROIX, manette->a())) {
    Serial.println("Bouton ✕"); valider(T_CROIX);
    choisirScenario(1, 0, 0, 255);              // bleu
  }
  if (vientDAppuyer(T_ROND, manette->b())) {
    Serial.println("Bouton ○"); valider(T_ROND);
    choisirScenario(2, 255, 0, 0);              // rouge
  }
  if (vientDAppuyer(T_TRIANGLE, manette->y())) {
    Serial.println("Bouton △"); valider(T_TRIANGLE);
    choisirScenario(3, 0, 255, 0);              // vert
  }
  if (vientDAppuyer(T_CARRE, manette->x())) {
    Serial.println("Bouton □"); valider(T_CARRE);
    choisirScenario(4, 255, 0, 160);            // rose
  }

  // --- Les flèches : ↑ ↓ changent la vitesse, ← → changent de scénario ---
  uint8_t f = manette->dpad();
  if (vientDAppuyer(T_HAUT, f & DPAD_UP)) {
    vitesse = min(vitesse + 25, 400);
    Serial.printf("Flèche ↑ : vitesse %d %%\n", vitesse); valider(T_HAUT);
  }
  if (vientDAppuyer(T_BAS, f & DPAD_DOWN)) {
    vitesse = max(vitesse - 25, 25);
    Serial.printf("Flèche ↓ : vitesse %d %%\n", vitesse); valider(T_BAS);
  }
  if (vientDAppuyer(T_GAUCHE, f & DPAD_LEFT)) {
    Serial.println("Flèche ←"); valider(T_GAUCHE);
    choisirScenario((scenario + NB_SCENARIOS - 1) % NB_SCENARIOS, couleurR, couleurG, couleurB);
  }
  if (vientDAppuyer(T_DROITE, f & DPAD_RIGHT)) {
    Serial.println("Flèche →"); valider(T_DROITE);
    choisirScenario((scenario + 1) % NB_SCENARIOS, couleurR, couleurG, couleurB);
  }

  // --- Les boutons de tranche ---
  if (vientDAppuyer(T_L1, manette->l1())) {
    Serial.println("L1 : mode pilote, la LED suit le joystick gauche"); valider(T_L1);
    choisirScenario(5, 255, 160, 0);            // jaune
  }
  if (vientDAppuyer(T_R1, manette->r1())) {
    Serial.println("R1 : extinction"); valider(T_R1);
    choisirScenario(0, 20, 20, 20);             // presque éteinte
  }
  if (vientDAppuyer(T_L2, manette->l2())) {
    Serial.println("L2 : appuie plus ou moins fort… (gros moteur)"); valider(T_L2);
  }
  if (vientDAppuyer(T_R2, manette->r2())) {
    Serial.println("R2 : appuie plus ou moins fort… (petit moteur)"); valider(T_R2);
  }

  // --- Clic sur les joysticks : affiche leurs valeurs exactes ---
  if (vientDAppuyer(T_L3, manette->thumbL())) {
    Serial.printf("L3 : joystick gauche X=%d Y=%d\n", manette->axisX(), manette->axisY());
    valider(T_L3);
  }
  if (vientDAppuyer(T_R3, manette->thumbR())) {
    Serial.printf("R3 : joystick droit X=%d Y=%d\n", manette->axisRX(), manette->axisRY());
    valider(T_R3);
  }

  // --- Les boutons du milieu ---
  if (vientDAppuyer(T_OPTIONS, manette->miscStart())) {
    Serial.println("OPTIONS"); valider(T_OPTIONS);
    afficherBilan();
  }
  if (vientDAppuyer(T_SHARE, manette->miscSelect())) {
    radar = !radar;
    Serial.printf("SHARE : radar %s\n", radar ? "activé" : "désactivé"); valider(T_SHARE);
  }
  if (vientDAppuyer(T_PS, manette->miscSystem())) {
    Serial.println("PS : bzzz !"); valider(T_PS);
    manette->playDualRumble(0, 300, 200, 0);    // petite vibration
  }
}

// ================= Lecture des joysticks =================
// Note : pour Y, vers le HAUT = négatif, vers le BAS = positif.
int directionsAtteintes(int x, int y) {
  int d = 0;
  if (y < -SEUIL_DIRECTION) d |= 1;   // haut
  if (y >  SEUIL_DIRECTION) d |= 2;   // bas
  if (x < -SEUIL_DIRECTION) d |= 4;   // gauche
  if (x >  SEUIL_DIRECTION) d |= 8;   // droite
  return d;
}

void suivreJoystick(int x, int y, int &directions, int numero, const char* nom) {
  int nouvelles = directionsAtteintes(x, y) & ~directions;
  if (nouvelles == 0) return;
  directions |= nouvelles;
  Serial.printf("Joystick %s : %s%s%s%s  (%d/4 directions)\n", nom,
                (nouvelles & 1) ? "↑ " : "", (nouvelles & 2) ? "↓ " : "",
                (nouvelles & 4) ? "← " : "", (nouvelles & 8) ? "→ " : "",
                __builtin_popcount(directions));
  if (directions == 15) valider(numero);
}

void lireJoysticks() {
  suivreJoystick(manette->axisX(),  manette->axisY(),  directionsG, T_STICK_G, "gauche");
  suivreJoystick(manette->axisRX(), manette->axisRY(), directionsD, T_STICK_D, "droit");

  // Radar : 4 fois par seconde, toutes les valeurs d'un coup
  if (radar && millis() - dernierRadar > 250) {
    dernierRadar = millis();
    Serial.printf("📡 G X=%4d Y=%4d | D X=%4d Y=%4d | L2=%4d R2=%4d\n",
                  manette->axisX(), manette->axisY(), manette->axisRX(), manette->axisRY(),
                  manette->brake(), manette->throttle());
  }
}

// ================= Barre lumineuse et vibrations =================
void piloterManette() {
  unsigned long maintenant = millis();

  // Couleur à afficher : arc-en-ciel (célébration), joystick droit, ou bouton
  uint8_t r = couleurR, g = couleurG, b = couleurB;
  int rx = manette->axisRX(), ry = manette->axisRY();
  if (maintenant < finCelebration) {
    arcEnCiel(maintenant / 4, r, g, b);                  // défile tout seul
  } else if (abs(rx) > 2 * ZONE_MORTE || abs(ry) > 2 * ZONE_MORTE) {
    int angle = atan2(-ry, rx) * 180 / PI;               // direction du joystick
    arcEnCiel(angle, r, g, b);                           // = une couleur
  }

  // On n'envoie la couleur que si elle a changé, et pas plus de 20 fois/s
  if ((r != envoyeR || g != envoyeG || b != envoyeB) && maintenant - dernierEnvoiCouleur > 50) {
    manette->setColorLED(r, g, b);
    envoyeR = r; envoyeG = g; envoyeB = b;
    dernierEnvoiCouleur = maintenant;
  }

  // Gâchettes : plus on appuie, plus ça vibre (L2 = gros moteur, R2 = petit)
  int fort = manette->brake() / 4;      // 0 à 255
  int doux = manette->throttle() / 4;   // 0 à 255
  if ((fort > 10 || doux > 10) && maintenant - dernierEnvoiVibration > 100) {
    manette->playDualRumble(0, 150, doux, fort);
    dernierEnvoiVibration = maintenant;
  }
}

// ================= La LED bleue =================
void jouerScenario() {
  unsigned long maintenant = millis();
  horloge += (maintenant - dernierTemps) * vitesse / 100;   // l'horloge avance plus ou moins vite
  dernierTemps = maintenant;
  unsigned long t = horloge;

  int lumiere = 0;
  if (maintenant < finCelebration) {
    lumiere = (maintenant / 60) % 2 ? 255 : 0;               // célébration : stroboscope
  } else {
    switch (scenario) {
      case 1: lumiere = (t / 800) % 2 ? 255 : 0; break;      // lent
      case 2: lumiere = (t / 120) % 2 ? 255 : 0; break;      // rapide
      case 3: {                                              // S O S : ...  ---  ...
        const char* sos = "1010100011101110111000101010000000";
        lumiere = sos[(t / 150) % strlen(sos)] == '1' ? 255 : 0;
        break;
      }
      case 4: {                                              // respiration
        int v = (t / 6) % 512;
        lumiere = v < 256 ? v : 511 - v;
        break;
      }
      case 5: {                                              // pilote : joystick gauche vers le haut
        int y = manette ? -manette->axisY() : 0;
        lumiere = y > ZONE_MORTE ? map(y, ZONE_MORTE, 508, 0, 255) : 0;
        lumiere = constrain(lumiere, 0, 255);
        break;
      }
    }
  }
  analogWrite(LED, lumiere);
}

// ================= Programme principal =================
void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  BP32.setup(&onConnectedController, &onDisconnectedController);
  // Oublie les manettes déjà couplées : il faudra refaire SHARE + PS à chaque démarrage.
  // Une fois que tout marche, mets cette ligne en commentaire (//) :
  // un simple appui sur PS suffira alors pour reconnecter la manette.
  BP32.forgetBluetoothKeys();
  Serial.println("Endor-Pad prêt. Maintiens SHARE + PS pour coupler la manette.");
}

void loop() {
  BP32.update();
  if (manette && manette->isConnected()) {
    lireBoutons();
    lireJoysticks();
    piloterManette();
  }
  jouerScenario();
  delay(10);
}
