// Mission 2 · Mustaflash, version holo-écran
// Le même programme que Mustaflash, avec en plus l'écran OLED qui affiche :
// le scénario en cours, un « miroir » des 10 LEDs, la luminosité, la vitesse
// et le courant estimé du ruban. Sans écran branché, tout marche quand même.

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

// ================= Réglages =================
#define PIN_RUBAN  5             // le fil de données (DIN), via la résistance de 330 Ω
#define NB_LEDS    10            // on ne commande que les 10 premières LEDs
const int LUM_MAX = 120;         // plafond de luminosité (sur 255) : l'USB ne suivrait pas plus
const int MILLIAMPERES_MAX = 400;// FastLED baisse tout seul la luminosité au-delà de ce courant

CRGB leds[NB_LEDS];              // une case par LED : sa couleur
ControllerPtr manette = nullptr;

// ================= Les scénarios =================
// 0 éteint, 1 réacteur, 2 alerte, 3 hyperespace, 4 arc-en-ciel
const int NB_SCENARIOS = 5;
const char* NOMS[NB_SCENARIOS] = {"éteint", "réacteur", "alerte rouge", "hyperespace", "arc-en-ciel"};
const char* NOMS_ECRAN[NB_SCENARIOS] = {"ETEINT", "REACTEUR", "ALERTE !", "HYPERESPACE", "ARC-EN-CIEL"};  // sans accents
bool joystickActif = false;      // vrai quand le joystick droit commande la couleur
int scenario = 0;
int luminosite = 40;             // de 5 à LUM_MAX
int vitesse = 100;               // en % : 100 = normal
unsigned long horloge = 0;       // horloge des animations (avance selon la vitesse)
unsigned long dernierTemps = 0;

// ================= Mémoire des boutons =================
enum { B_CROIX, B_ROND, B_TRIANGLE, B_CARRE, B_R1, B_HAUT, B_BAS, B_GAUCHE, B_DROITE, NB_BOUTONS };
bool avant[NB_BOUTONS];

bool vientDAppuyer(int numero, bool appuye) {   // vrai seulement au moment de l'appui
  bool nouveau = appuye && !avant[numero];
  avant[numero] = appuye;
  return nouveau;
}

// ================= Connexion de la manette =================
void onConnectedController(ControllerPtr ctl) {
  if (manette != nullptr) return;               // une seule manette à la fois
  manette = ctl;
  ctl->setColorLED(0, 0, 255);
  Serial.println("🎮 Manette connectée : à toi de jouer !");
}

void onDisconnectedController(ControllerPtr ctl) {
  if (manette == ctl) {
    manette = nullptr;
    scenario = 0;                               // par prudence, on éteint le ruban
    Serial.println("❌ Manette déconnectée : ruban éteint");
  }
}

void choisir(int s, uint8_t r, uint8_t g, uint8_t b) {
  scenario = s;
  manette->setColorLED(r, g, b);                // la barre lumineuse prend la couleur du scénario
  Serial.printf("Scénario : %s\n", NOMS[s]);
}

// ================= Lecture de la manette =================
void lireManette() {
  if (vientDAppuyer(B_CROIX,    manette->a()))  choisir(1, 0, 80, 255);    // réacteur bleu
  if (vientDAppuyer(B_ROND,     manette->b()))  choisir(2, 255, 0, 0);     // alerte rouge
  if (vientDAppuyer(B_TRIANGLE, manette->y()))  choisir(3, 255, 255, 255); // hyperespace
  if (vientDAppuyer(B_CARRE,    manette->x()))  choisir(4, 0, 255, 0);     // arc-en-ciel
  if (vientDAppuyer(B_R1,       manette->r1())) choisir(0, 20, 20, 20);    // extinction

  uint8_t f = manette->dpad();
  if (vientDAppuyer(B_HAUT, f & DPAD_UP)) {
    luminosite = min(luminosite + 10, LUM_MAX);
    Serial.printf("Luminosité : %d / %d\n", luminosite, LUM_MAX);
  }
  if (vientDAppuyer(B_BAS, f & DPAD_DOWN)) {
    luminosite = max(luminosite - 10, 5);
    Serial.printf("Luminosité : %d / %d\n", luminosite, LUM_MAX);
  }
  if (vientDAppuyer(B_DROITE, f & DPAD_RIGHT)) {
    vitesse = min(vitesse + 25, 400);
    Serial.printf("Vitesse : %d %%\n", vitesse);
  }
  if (vientDAppuyer(B_GAUCHE, f & DPAD_LEFT)) {
    vitesse = max(vitesse - 25, 25);
    Serial.printf("Vitesse : %d %%\n", vitesse);
  }
}

// ================= Les animations =================
void reacteur(unsigned long t) {
  int boost = manette ? manette->throttle() / 4 : 0;          // R2 : 0 à 255
  unsigned long bpm = 30 + boost / 4;                         // battements par minute : R2 accélère
  uint8_t phase = t * bpm * 256UL / 60000UL;                  // où on en est dans le battement
  uint8_t base = 40 + scale8(sin8(phase), 215);               // pulsation douce entre 40 et 255
  fill_solid(leds, NB_LEDS, CRGB(boost / 3, boost / 3, base)); // bleu, qui blanchit avec R2
}

void alerte(unsigned long t) {
  bool phase = (t / 300) % 2;                                 // change toutes les 300 ms
  for (int i = 0; i < NB_LEDS; i++) {
    bool gauche = i < NB_LEDS / 2;
    leds[i] = (gauche == phase) ? CRGB::Red : CRGB::Black;    // une moitié, puis l'autre
  }
}

void hyperespace(unsigned long t) {
  fadeToBlackBy(leds, NB_LEDS, 60);                           // la traînée s'efface doucement
  int pos = (t / 60) % NB_LEDS;                               // le point avance d'une LED / 60 ms
  leds[pos] = CRGB::White;
}

void arcEnCiel(unsigned long t) {
  fill_rainbow(leds, NB_LEDS, t / 10, 255 / NB_LEDS);         // tout l'arc-en-ciel, qui défile
}

void couleurJoystick(int rx, int ry) {
  int angle = atan2(-ry, rx) * 180 / PI;                      // direction du joystick droit
  uint8_t teinte = (angle + 360) % 360 * 255 / 360;           // de 0 à 255 pour FastLED
  fill_solid(leds, NB_LEDS, CHSV(teinte, 255, 255));
}

void afficher() {
  unsigned long maintenant = millis();
  horloge += (maintenant - dernierTemps) * vitesse / 100;
  dernierTemps = maintenant;

  int rx = manette ? manette->axisRX() : 0;
  int ry = manette ? manette->axisRY() : 0;
  joystickActif = abs(rx) > 200 || abs(ry) > 200;
  if (joystickActif) {
    couleurJoystick(rx, ry);                                  // le joystick droit a la priorité
  } else {
    switch (scenario) {
      case 0: fill_solid(leds, NB_LEDS, CRGB::Black); break;
      case 1: reacteur(horloge); break;
      case 2: alerte(horloge); break;
      case 3: hyperespace(horloge); break;
      case 4: arcEnCiel(horloge); break;
    }
  }
  FastLED.setBrightness(luminosite);
  FastLED.show();                                             // on envoie tout au ruban
}

// ================= L'holo-écran =================
Adafruit_SSD1306 ecran(128, 64, &Wire, -1);
bool ecranPret = false;            // faux si l'écran n'est pas branché : le reste marche quand même
unsigned long derniereImage = 0;
const int PERIODE_ECRAN = 100;     // une image toutes les 100 ms : 10 images par seconde

void demarrerEcran() {
  Wire.begin(21, 22);              // SDA sur D21, SCL sur D22
  ecranPret = ecran.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  if (!ecranPret) {
    Serial.println("📟 Pas d'écran trouvé : on continue sans lui.");
    return;
  }
  ecran.setTextColor(SSD1306_WHITE);
  ecran.setTextWrap(false);
}

// Une jauge horizontale : un cadre, rempli selon la valeur (0 à maxi)
void dessinerJauge(int x, int y, int largeur, int valeur, int maxi) {
  ecran.drawRect(x, y, largeur, 7, SSD1306_WHITE);
  int plein = (largeur - 2) * constrain(valeur, 0, maxi) / maxi;
  ecran.fillRect(x + 1, y + 1, plein, 5, SSD1306_WHITE);
}

// Le titre dans la bande jaune (lignes 0 à 15), avec la batterie de la manette à droite (ou « ... » qui clignote sans manette)
void dessinerTitre(const char* titre) {
  ecran.setTextSize(1);
  ecran.setCursor(0, 3);
  ecran.print(titre);
  if (manette && manette->battery() > 0) dessinerJauge(100, 3, 28, manette->battery(), 255);
  else if (!manette && (millis() / 500) % 2) { ecran.setCursor(110, 3); ecran.print("..."); }
  ecran.drawFastHLine(0, 13, 128, SSD1306_WHITE);  // la bande jaune s'arrête à la ligne 15
}

void mettreAJourEcran() {
  if (!ecranPret || millis() - derniereImage < PERIODE_ECRAN) return;
  derniereImage = millis();
  ecran.clearDisplay();
  dessinerTitre("MUSTAFLASH");

  // Le nom du scénario, en grand s'il tient sur la largeur (10 lettres de 12 pixels)
  const char* nom = joystickActif ? "JOYSTICK" : NOMS_ECRAN[scenario];
  int taille = strlen(nom) <= 10 ? 2 : 1;
  ecran.setTextSize(taille);
  ecran.setCursor((128 - strlen(nom) * 6 * taille) / 2, taille == 2 ? 16 : 20);   // centré
  ecran.print(nom);

  // Le miroir du ruban : une barre par LED, d'autant plus haute que la LED est vive
  for (int i = 0; i < NB_LEDS; i++) {
    uint8_t vive = max(leds[i].r, max(leds[i].g, leds[i].b));   // sa couleur la plus forte
    int hauteur = 1 + vive * 10 / 255;
    ecran.fillRect(5 + i * 12, 45 - hauteur, 9, hauteur, SSD1306_WHITE);
  }

  // Luminosité et vitesse
  ecran.setTextSize(1);
  ecran.setCursor(0, 47);
  ecran.print("LUM");
  dessinerJauge(20, 47, 44, luminosite, LUM_MAX);
  ecran.setCursor(72, 47);
  ecran.printf("VIT %d%%", vitesse);

  // Le courant estimé : FastLED connaît la consommation de chaque couleur
  uint32_t milliwatts = calculate_unscaled_power_mW(leds, NB_LEDS) * luminosite / 255;
  ecran.setCursor(0, 56);
  ecran.printf("Ruban ~%d mA", (int)(milliwatts / 5));   // P = U x I, donc I = P / 5 V
  ecran.display();
}

// ================= Programme principal =================
void setup() {
  Serial.begin(115200);
  FastLED.addLeds<WS2812B, PIN_RUBAN, GRB>(leds, NB_LEDS);
  FastLED.setMaxPowerInVoltsAndMilliamps(5, MILLIAMPERES_MAX);
  FastLED.setBrightness(luminosite);
  fill_solid(leds, NB_LEDS, CRGB::Black);
  FastLED.show();

  demarrerEcran();
  BP32.setup(&onConnectedController, &onDisconnectedController);
  BP32.forgetBluetoothKeys();   // à mettre en commentaire une fois que tout marche
  Serial.println("Mustaflash prêt. Maintiens SHARE + PS pour coupler la manette.");
}

void loop() {
  BP32.update();
  if (manette && manette->isConnected()) lireManette();
  afficher();
  mettreAJourEcran();
  delay(10);
}
