// Mission 5 · Jakkumulateur, version holo-écran
// Le même programme que Jakkumulateur, avec en plus l'écran OLED qui affiche :
// le mode, une barre par moteur (vers le haut : avant, vers le bas : arrière),
// la tension moyenne reçue par chaque moteur, la puissance maximale, et FREIN / ROUE LIBRE.
// Sans écran branché, tout marche quand même.

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

// ================= Branchements =================
const int A_IN1 = 25, A_IN2 = 26;    // moteur A (sorties OUT1 / OUT2 du driver)
const int B_IN1 = 32, B_IN2 = 33;    // moteur B (sorties OUT3 / OUT4), facultatif
const int ZONE_MORTE = 30;           // en dessous, on considère « pas de commande »

ControllerPtr manette = nullptr;
bool modeChar = false;               // faux : gâchettes ; vrai : deux joysticks
int puissanceMax = 200;              // sur 255 : réglable avec ↑ ↓
int sensPrecedent = 99;
int vitesseA = 0, vitesseB = 0;      // les dernières vitesses envoyées, pour l'écran
const char* etat = "";               // "FREIN", "ROUE LIBRE" ou rien
const float TENSION_DRIVER = 5.0;    // la tension qui alimente le driver (l'alimentation externe, 5 V)

enum { B_TRIANGLE, B_HAUT, B_BAS, NB_BOUTONS };
bool avant[NB_BOUTONS];

bool vientDAppuyer(int numero, bool appuye) {
  bool nouveau = appuye && !avant[numero];
  avant[numero] = appuye;
  return nouveau;
}

// ================= Piloter un moteur =================
// v de -255 (arrière à fond) à +255 (avant à fond), 0 = roue libre
void moteur(int in1, int in2, int v) {
  v = constrain(v, -255, 255);
  if (v > 0)      { analogWrite(in1, v);  analogWrite(in2, 0); }    // avant
  else if (v < 0) { analogWrite(in1, 0);  analogWrite(in2, -v); }   // arrière
  else            { analogWrite(in1, 0);  analogWrite(in2, 0); }    // roue libre
}

void freiner(int in1, int in2) {
  analogWrite(in1, 255);                 // les deux entrées à 1 :
  analogWrite(in2, 255);                 // le driver court-circuite le moteur, qui s'arrête net
}

// ================= Connexion =================
void onConnectedController(ControllerPtr ctl) {
  if (manette != nullptr) return;
  manette = ctl;
  sensPrecedent = 99;
  Serial.println("🎮 Manette connectée : moteurs prêts.");
}

void onDisconnectedController(ControllerPtr ctl) {
  if (manette == ctl) {
    manette = nullptr;
    Serial.println("❌ Manette déconnectée : moteurs arrêtés.");
  }
}

// ================= Calculs =================
// Transforme une valeur de manette (de -min à +max) en vitesse de moteur (-puissanceMax à +puissanceMax)
int versVitesse(int valeur, int maxi) {
  if (abs(valeur) < ZONE_MORTE * maxi / 512) return 0;   // zone morte
  return constrain(map(valeur, -maxi, maxi, -puissanceMax, puissanceMax), -puissanceMax, puissanceMax);
}

void signaler(int v) {
  int sens = (v > 0) - (v < 0);
  if (sens == sensPrecedent || !manette) return;
  sensPrecedent = sens;
  if (sens > 0)      manette->setColorLED(0, 255, 0);    // vert : avant
  else if (sens < 0) manette->setColorLED(0, 80, 255);   // bleu : arrière
  else               manette->setColorLED(40, 40, 40);   // gris : arrêt
}

// ================= 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
}

// Une barre verticale centrée sur zéro : vers le haut = avant, vers le bas = arrière
void dessinerBarreMoteur(int x, const char* nom, int v) {
  const int milieu = 36, demi = 17;
  ecran.drawRect(x, milieu - demi - 1, 14, 2 * demi + 3, SSD1306_WHITE);
  ecran.drawFastHLine(x - 3, milieu, 20, SSD1306_WHITE);           // le repère du zéro
  int h = demi * abs(v) / 255;
  if (v > 0) ecran.fillRect(x + 2, milieu - h, 10, h, SSD1306_WHITE);
  else       ecran.fillRect(x + 2, milieu + 1, 10, h, SSD1306_WHITE);
  ecran.setCursor(x + 4, 57);
  ecran.print(nom);
}

void mettreAJourEcran() {
  if (!ecranPret || millis() - derniereImage < PERIODE_ECRAN) return;
  derniereImage = millis();
  ecran.clearDisplay();
  dessinerTitre(modeChar ? "MODE CHAR" : "MODE GACHETTES");

  dessinerBarreMoteur(6, "A", vitesseA);
  dessinerBarreMoteur(30, "B", vitesseB);

  // La tension moyenne que reçoit chaque moteur : la part du temps « allumé » x la tension du driver
  ecran.setTextSize(1);
  ecran.setCursor(56, 18);
  ecran.printf("A %.1f V", abs(vitesseA) / 255.0 * TENSION_DRIVER);
  ecran.setCursor(56, 28);
  ecran.printf("B %.1f V", abs(vitesseB) / 255.0 * TENSION_DRIVER);
  ecran.setCursor(56, 38);
  ecran.printf("Max %d", puissanceMax);

  // FREIN ou ROUE LIBRE : en noir sur fond blanc, pour qu'on le voie bien
  if (strlen(etat) > 0) {
    ecran.fillRect(54, 47, 74, 11, SSD1306_WHITE);
    ecran.setTextColor(SSD1306_BLACK);
    ecran.setCursor(57, 49);
    ecran.print(etat);
    ecran.setTextColor(SSD1306_WHITE);
  }
  ecran.display();
}

// ================= Programme principal =================
void setup() {
  Serial.begin(115200);
  pinMode(A_IN1, OUTPUT); pinMode(A_IN2, OUTPUT);
  pinMode(B_IN1, OUTPUT); pinMode(B_IN2, OUTPUT);
  moteur(A_IN1, A_IN2, 0);
  moteur(B_IN1, B_IN2, 0);
  demarrerEcran();
  BP32.setup(&onConnectedController, &onDisconnectedController);
  BP32.forgetBluetoothKeys();   // à mettre en commentaire une fois que tout marche
  Serial.println("Jakkumulateur prêt. Maintiens SHARE + PS pour coupler la manette.");
}

void loop() {
  BP32.update();
  int vA = 0, vB = 0;
  bool frein = false;
  etat = "";

  if (manette && manette->isConnected()) {
    if (vientDAppuyer(B_TRIANGLE, manette->y())) {
      modeChar = !modeChar;
      Serial.println(modeChar ? "△ Mode char : un joystick par moteur" : "△ Mode gâchettes : R2 avance, L2 recule");
    }
    uint8_t f = manette->dpad();
    if (vientDAppuyer(B_HAUT, f & DPAD_UP))  { puissanceMax = min(puissanceMax + 25, 255); Serial.printf("Puissance max : %d / 255\n", puissanceMax); }
    if (vientDAppuyer(B_BAS, f & DPAD_DOWN)) { puissanceMax = max(puissanceMax - 25, 75);  Serial.printf("Puissance max : %d / 255\n", puissanceMax); }

    if (manette->b()) {                                 // ○ maintenu : frein
      frein = true;
    } else if (!manette->r1()) {                        // R1 maintenu : roue libre (tout à 0)
      if (modeChar) {
        vA = versVitesse(-manette->axisY(), 512);       // joystick vers le haut = avant
        vB = versVitesse(-manette->axisRY(), 512);
      } else {
        vA = versVitesse(manette->throttle() - manette->brake(), 1020);
      }
    } else {
      etat = "ROUE LIBRE";
    }
  }

  if (frein) {
    freiner(A_IN1, A_IN2);
    freiner(B_IN1, B_IN2);
    if (sensPrecedent != 2) {                           // rouge, une seule fois
      manette->setColorLED(255, 0, 0);
      sensPrecedent = 2;
    }
    etat = "FREIN";
  } else {
    moteur(A_IN1, A_IN2, vA);
    moteur(B_IN1, B_IN2, vB);
    signaler(vA != 0 ? vA : vB);
  }
  vitesseA = vA;                                        // mémorisées pour l'écran
  vitesseB = vB;
  mettreAJourEcran();
  delay(15);
}
