#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>
#include <Wire.h>
#include <EEPROM.h>
#include "HX711.h"

// --- Configuration Pins ---
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
#define TFT_BL 6    
#define TOUCH_INT 2
#define TOUCH_RST 3

// --- Objets ---
Adafruit_GC9A01A tft(TFT_CS, TFT_DC, TFT_RST);
HX711 scale;

// --- Structures ---
struct Profil {
  char nom[16];        // 15 car. + 1 pour le nul (\0)
  int poidsVide;
  int poidsTotal;
  uint16_t couleur;
};

struct Configuration {
  Profil profils[5];
  float calibration;
  long offset;
  int deadzone;
  int pIdx;
  int luxIdx;
  uint32_t magic;      // Pour vérifier si l'EEPROM est initialisée
} settings;

// --- Variables Globales ---
int charIdx = 0;
const char* alphabet = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789<";
int poidsEtalon = 1180;
int luxValeurs[] = {40, 100, 180, 255}; 

enum Etat { ACCUEIL, EDIT_NOM, EDIT_POIDS, EDIT_MAX, EDIT_DEADZONE, CALIB_ZERO, CALIB_POIDS, RESET_CONFIRM };
Etat mode = ACCUEIL;
float dernierP = -1;

// --- INITIALISATION ---
void setup() {
  Serial.begin(115200);
  Wire.begin();
  
  pinMode(TFT_BL, OUTPUT);
  chargerDonnees();
  analogWrite(TFT_BL, luxValeurs[settings.luxIdx]);

  tft.begin(); 
  tft.setRotation(0);
  
  // Reset Tactile
  pinMode(TOUCH_RST, OUTPUT); 
  digitalWrite(TOUCH_RST, LOW); delay(20);
  digitalWrite(TOUCH_RST, HIGH); delay(50);
  pinMode(TOUCH_INT, INPUT_PULLUP);

  // --- CORRECTION BALANCE ---
  scale.begin(4, 5);
  scale.set_scale(settings.calibration);
  scale.set_offset(settings.offset); 
  delay(1000);    // Laisse le temps à la tension de se stabiliser (CRUCIAL)

  
  tft.fillScreen(0);
  dessinerInterface();
}

// --- GESTION DES DONNÉES (EEPROM) ---
void chargerDonnees() {
  EEPROM.get(0, settings);
  
  // Vérifie si la mémoire est vide ou corrompue (Magic Number)
  if (settings.magic != 0xABCD1234 || isnan(settings.calibration)) {
    forceDefault();
  }
}

void forceDefault() {
  memset(&settings, 0, sizeof(settings));

  strncpy(settings.profils[0].nom, "Polymaker  1Kg", 15);
  settings.profils[0].poidsVide = 140;
  settings.profils[0].poidsTotal = 1000;
  settings.profils[0].couleur = 0x07FF; // Cyan

  strncpy(settings.profils[1].nom, "Polymaker 750g", 15);
  settings.profils[1].poidsVide = 125;
  settings.profils[0].poidsTotal = 750;
  settings.profils[1].couleur = 0xFFE0; // Jaune

  strncpy(settings.profils[2].nom, "Fiberon   500g", 15);
  settings.profils[2].poidsVide = 140;
  settings.profils[0].poidsTotal = 500;
  settings.profils[2].couleur = 0xFE19; // Rose

  strncpy(settings.profils[3].nom, "Vide.         ", 15);
  settings.profils[3].poidsVide = 125;
  settings.profils[0].poidsTotal = 1000;
  settings.profils[3].couleur = 0x07E0; // Vert

  strncpy(settings.profils[4].nom, "Vide.         ", 15);
  settings.profils[4].poidsVide = 140;
  settings.profils[0].poidsTotal = 1000;
  settings.profils[4].couleur = 0x5DFF; // Bleu

  settings.deadzone = 10;
  settings.calibration = 450.0;
  settings.pIdx = 0;
  settings.luxIdx = 3;
  settings.magic = 0xABCD1234; // Marqueur de validité
  
  EEPROM.put(0, settings);
}

void appliquerResetUsine() {
  tft.fillScreen(0);
  tft.setTextColor(0xF800); tft.setTextSize(2);
  tft.setCursor(70, 100); tft.print(F("NETTOYAGE"));
  
  // Effacement propre de l'EEPROM
  for (int i = 0 ; i < sizeof(settings) + 10 ; i++) {
    EEPROM.update(i, 0);
  }
  
  forceDefault();
  delay(1000);
  mode = ACCUEIL;
  tft.fillScreen(0);
  dessinerInterface();
}

// --- BOUCLE PRINCIPALE ---
void loop() {
  
  if (digitalRead(TOUCH_INT) == LOW) {
    Wire.beginTransmission(0x15);
    Wire.write(0x03);
    if (Wire.endTransmission() == 0) {
      Wire.requestFrom(0x15, 4);
      int tx = ((Wire.read() & 0x0F) << 8) | Wire.read();
      int ty = ((Wire.read() & 0x0F) << 8) | Wire.read();
      
      if (tx > 0 || ty > 0) {
        traiterTactile(tx, ty);
        delay(150); 
      }
    }
  }

  // Lecture Balance
  if (mode == ACCUEIL) {
    static unsigned long tP = 0;
    if (millis() - tP > 1000) { // Une lecture par seconde suffit
      float pRaw = scale.get_units(1) - settings.profils[settings.pIdx].poidsVide;
      float p = (pRaw < 0) ? 0 : pRaw;
      
      if (abs(p - dernierP) > settings.deadzone) { 
        dernierP = p; 
        actualiserPoids(dernierP); 
      }
      tP = millis();
    }
  }
}

void changerMode(Etat nouveauMode) {
  mode = nouveauMode;
  tft.fillScreen(0);
  dessinerInterface();
}

void traiterTactile(int x, int y) {
  bool refresh = false;

  switch (mode) {
    case ACCUEIL:
      if (y > 165)      { charIdx = 0; changerMode(EDIT_NOM); }
      else if (y < 60)  { changerMode(CALIB_ZERO); }
      else if (x < 100) { 
        settings.pIdx = (settings.pIdx > 0) ? settings.pIdx - 1 : 4; 
        EEPROM.put(0, settings); // On sauve tout le bloc
        changerMode(ACCUEIL); 
      }
      else if (x > 140) { 
        settings.pIdx = (settings.pIdx + 1) % 5; 
        EEPROM.put(0, settings); 
        changerMode(ACCUEIL); 
      }
      else if (x > 100 && x < 140) { scale.tare(); }
      break;

    case CALIB_ZERO:
      if (y < 50) { changerMode(RESET_CONFIRM); }
      else if (y > 55 && y < 95) { // LUX
        if (x < 110 && settings.luxIdx > 0) settings.luxIdx--;
        else if (x > 130 && settings.luxIdx < 3) settings.luxIdx++;
        analogWrite(TFT_BL, luxValeurs[settings.luxIdx]);
        EEPROM.put(0, settings);
        refresh = true;
      }
      else if (x < 80 && y > 80 && y < 160) { changerMode(ACCUEIL); }
      else if (y > 180) { 
    // CETTE PARTIE EST LA PLUS IMPORTANTE :
    scale.tare(30); // On fait le zéro (plateau VIDE obligatoirement)
    settings.offset = scale.get_offset(); // On enregistre la valeur brute du plateau vide
    EEPROM.put(0, settings); 
    changerMode(CALIB_POIDS); 
  }
      break;

        case EDIT_NOM:
      // 1. BOUTON VALIDER (Tout en bas)
      if (y > 180) { 
        changerMode(EDIT_POIDS); 
        delay(200); // Anti-rebond
      } 
      
      // 2. ZONE DE NAVIGATION EN HAUT (Boutons -, SUIV, +)
      else if (y > 35 && y < 75) {
        // --- BOUTON MOINS (-) ---
        if (x < 90) { 
          int pos = 0;
          char actuel = settings.profils[settings.pIdx].nom[charIdx];
          while(alphabet[pos] != actuel && pos < 63) pos++;
          pos = (pos > 0) ? pos - 1 : 63;
          settings.profils[settings.pIdx].nom[charIdx] = alphabet[pos];
        } 
        // --- BOUTON PLUS (+) ---
        else if (x > 150) { 
          int pos = 0;
          char actuel = settings.profils[settings.pIdx].nom[charIdx];
          while(alphabet[pos] != actuel && pos < 63) pos++;
          pos = (pos + 1) % 64;
          settings.profils[settings.pIdx].nom[charIdx] = alphabet[pos];
        } 
        // --- BOUTON SUIV (Entre 90 et 150) ---
        else { 
          charIdx = (charIdx + 1) % 14; 
        }
        refresh = true;
        delay(150); // Empêche le défilement trop rapide
      }
      
      // 3. SÉLECTION DIRECTE (Toucher le nom au centre)
      else if (y > 90 && y < 140) { 
        // On recalcule l'index selon le nouveau centrage du nom (Taille 2)
        // xStart était 120 - (14 * 6) = 36
        int nIdx = (x - 36) / 12; 
        if (nIdx >= 0 && nIdx < 14) charIdx = nIdx; 
        refresh = true; 
        delay(150);
      }
      break;

    case EDIT_POIDS:
      if (y > 185) { changerMode(EDIT_MAX); }
      else if (x < 80)  { settings.profils[settings.pIdx].poidsVide = max(0, settings.profils[settings.pIdx].poidsVide - 1); refresh = true; }
      else if (x > 160) { settings.profils[settings.pIdx].poidsVide += 1; refresh = true; }
      break;

     case EDIT_MAX:
      if (y > 185) { changerMode(EDIT_DEADZONE); } // Puis vers la Deadzone
      else if (x < 80)  { settings.profils[settings.pIdx].poidsTotal = max(50, settings.profils[settings.pIdx].poidsTotal - 50); refresh = true; }
      else if (x > 160) { settings.profils[settings.pIdx].poidsTotal += 50; refresh = true; }
      break;  

    case EDIT_DEADZONE:
      if (y > 185) { EEPROM.put(0, settings); changerMode(ACCUEIL); }
      else if (x < 80)  { if(settings.deadzone > 0) settings.deadzone--; refresh = true; }
      else if (x > 160) { if(settings.deadzone < 200) settings.deadzone++; refresh = true; }
      break;

    case CALIB_POIDS:
  if (x < 80)       { poidsEtalon -= 1; refresh = true; }
  else if (x > 160)  { poidsEtalon += 1; refresh = true; }
  else if (y > 180) {
    // Calcul de la nouvelle calibration
    settings.calibration = (float)scale.get_value(10) / (float)poidsEtalon;
    settings.magic = 0xABCD1234; 
    scale.set_scale(settings.calibration);
    EEPROM.put(0, settings);
    changerMode(ACCUEIL);
  }
  break;

      
    case RESET_CONFIRM:
      if (x > 120) appliquerResetUsine();
      else changerMode(CALIB_ZERO);
      break;
  }

  if (refresh) dessinerInterface();
}

void dessinerBouton(int x, int y, int w, int h, const char* label, uint16_t couleur) {
  tft.fillRoundRect(x, y, w, h, 8, couleur);
  tft.setTextColor(0); // Texte noir sur bouton coloré
  tft.setTextSize(2);
  // Calcul approximatif pour centrer le texte
  tft.setCursor(x + (w/4), y + (h/4)); 
  tft.print(label);
}
// 1. Fonction utilitaire pour dessiner les boutons (à placer AVANT dessinerInterface)
void btn(int x, int y, int w, int h, const char* txt, uint16_t col, int sz = 2) {
  tft.fillRoundRect(x, y, w, h, 8, col);
  tft.setTextColor(0); // Texte noir sur les boutons
  tft.setTextSize(sz);
  tft.setCursor(x + (w/6), y + (h/4) + 2);
  tft.print(txt);
}
void btnBobine(int16_t x, int16_t y, int16_t taille, uint16_t couleurFil) {
  int cx = x + (taille / 2);
  int cy = y + (taille / 2);
  int rayonMax = taille / 2;

  uint16_t marron = 0x9244;
  uint16_t noir   = 0x0000;
  uint16_t gris   = 0x3186;

  // 1. LE FILAMENT (Utilise maintenant couleurFil)
  tft.fillCircle(cx, cy, rayonMax - 5, couleurFil); 

  // --- EFFET DE SPIRES ---
  for (int r = 14; r < (rayonMax - 5); r += 3) {
    tft.drawCircle(cx, cy, r, noir);
  }

  // 2. LA STRUCTURE (MARRON)
  for(int i=0; i<6; i++) {
    tft.drawCircle(cx, cy, rayonMax - i, marron);
  }
  tft.fillCircle(cx, cy, 14, marron);
  
  // RAYONS
  for (int angle = 0; angle < 360; angle += 60) {
    float rad = angle * 0.01745;
    for(float d = -0.15; d <= 0.15; d += 0.03) {
      int x1 = cx + cos(rad + d) * 12;
      int y1 = cy + sin(rad + d) * 12;
      int x2 = cx + cos(rad + d) * (rayonMax - 5);
      int y2 = cy + sin(rad + d) * (rayonMax - 5);
      tft.drawLine(x1, y1, x2, y2, marron);
    }
  }

  // 3. LE TROU CENTRAL
  tft.fillCircle(cx, cy, 6, gris); 
  tft.drawCircle(cx, cy, 6, noir); 
}

// 2. La fonction d'interface allégée
void dessinerInterface() {
  uint16_t gris = 0x8410;
  uint16_t cyan = 0x07FF;
  
  // Texte blanc sur fond noir par défaut (plus rapide)
  tft.setTextColor(gris, 0); 

    if (mode == ACCUEIL) {
    tft.setTextSize(1); tft.setCursor(95, 35); tft.print(F("CALIBRER"));
    
    // --- AFFICHAGE DU NOM EN GROS ---
    tft.setTextSize(2); // Taille augmentée à 2
    tft.setTextColor(0xFFFF, 1); // Rose/Magenta sur fond noir
    
    // Calcul pour centrer le texte dynamiquement
    int len = strlen(settings.profils[settings.pIdx].nom);
    // En taille 2, chaque caractère fait environ 12 pixels de large
    int xPos = 120 - (len * 6); 
    
    tft.setCursor(xPos, 65); 
    tft.print(settings.profils[settings.pIdx].nom);
    // --------------------------------
    
    // Flèches de navigation
    tft.setTextColor(cyan, 0); tft.setTextSize(2);
    tft.setCursor(15, 115); tft.print(F("<<")); 
    tft.setCursor(200, 115); tft.print(F(">>"));

    // Un bouton de 60x60 pixels, bien visible
    btnBobine(90, 160, 60, settings.profils[settings.pIdx].couleur);

    actualiserPoids(dernierP);
  } 

  else if (mode == CALIB_ZERO) {
    btn(95, 15, 50, 22, "RESET", 0xF800, 1);
    
    tft.setTextSize(2); 
    tft.setTextColor(cyan, 0); tft.setCursor(80, 65); tft.print(F("-"));
    tft.setTextColor(0xFFFF, 0); tft.setCursor(105, 70); tft.print(F("LUX"));
    tft.setTextColor(cyan, 0); tft.setCursor(150, 65); tft.print(F("+"));
    
    tft.setCursor(20, 115); tft.print(F("<<<")); 
    tft.setTextColor(0xFFFF, 0); tft.setCursor(95, 130); tft.print(F("Vide?"));
    
    btn(85, 170, 70, 38, "Oui", 0x07E0);
  }

  else if (mode == RESET_CONFIRM) {
    tft.setTextSize(2); tft.setTextColor(0xF800, 0);
    tft.setCursor(40, 80); tft.print(F("EFFACER TOUT?"));
    btn(35, 140, 80, 45, "NON", 0xF800);
    btn(125, 140, 80, 45, "OUI", 0x07E0);
  }

     else if (mode == EDIT_NOM) {
    tft.setTextSize(1); tft.setTextColor(0x8410, 0); // Gris
    tft.setCursor(85, 20); tft.print(F("EDITION NOM"));
    
    // --- CONTRÔLES EN HAUT (Navigation Alphabet) ---
    tft.setTextSize(3); tft.setTextColor(cyan, 0);
    tft.setCursor(60, 45);  tft.print(F("-")); 
    
    // Bouton OK/SUIV au centre
    tft.drawRoundRect(100, 42, 40, 25, 5, 0x07E0);
    tft.setTextSize(1); tft.setTextColor(0x07E0, 0);
    tft.setCursor(110, 50); tft.print(F("SUIV"));

    tft.setTextSize(3); tft.setTextColor(cyan, 0);
    tft.setCursor(165, 45); tft.print(F("+")); 

    // --- AFFICHAGE DU NOM AU CENTRE (Taille 2 pour tout le monde) ---
    int len = 14; 
    // En Taille 2, chaque caractère fait 12px de large. 14 * 12 = 168px.
    // Cela rentre parfaitement dans les 240px de l'écran rond.
    int xStart = 120 - (len * 6); 

    for(int i = 0; i < len; i++) {
      int xP = xStart + (i * 12);
      char c = settings.profils[settings.pIdx].nom[i];
      
      tft.setTextSize(2); // TAILLE 2 POUR TOUT LE NOM
      
      if (i == charIdx) {
        tft.setTextColor(cyan, 0);
        tft.setCursor(xP, 105); 
        tft.print(c <= ' ' ? '_' : c);
        // Soulignage épais pour bien voir la lettre active
        tft.drawFastHLine(xP, 122, 10, cyan); 
      } else {
        tft.setTextColor(0xFFFF, 0);
        tft.setCursor(xP, 105);
        tft.print(c <= ' ' ? '_' : c);
      }
    }
    btn(60, 170, 130, 30, "VALIDER", gris);
  }

  else if (mode == EDIT_POIDS || mode == EDIT_MAX || mode == EDIT_DEADZONE || mode == CALIB_POIDS) {
    tft.setTextSize(1); tft.setTextColor(0xFFFF, 0);
    
    int val; 
    const char* lab;

    // 1. Définition des valeurs selon le mode
    if (mode == EDIT_POIDS) { 
      val = settings.profils[settings.pIdx].poidsVide; 
      lab = "POIDS VIDE (g)"; 
    }
    else if (mode == EDIT_MAX) { 
      val = settings.profils[settings.pIdx].poidsTotal; 
      lab = "CAPACITE (g)"; 
    }
    else if (mode == EDIT_DEADZONE) { 
      val = settings.deadzone; 
      lab = "DEADZONE (g)"; 
    }
    else { 
      val = poidsEtalon; 
      lab = "POIDS ETALON"; 
    }

    // 2. NETTOYAGE COMPLET DE LA ZONE (Crucial pour les chiffres qui diminuent)
    // On efface un large rectangle noir avant de réécrire
    tft.fillRect(60, 95, 120, 50, 0); 

    // 3. Affichage du titre (lab)
    tft.setTextSize(2); 
    tft.setTextColor(0xFFFF, 0);
    int xCentrageLabel = 120 - (strlen(lab) * 6);
    tft.setCursor(xCentrageLabel, 50); 
    tft.print(lab);
    
    // 4. Affichage de la valeur numérique
    tft.setTextSize(5); 
    tft.setTextColor(cyan, 0);
    String s = String(val);
    int xCentrageValeur = 120 - (s.length() * 15); 
    tft.setCursor(xCentrageValeur, 100); 
    tft.print(s); 
    
    // 5. Boutons et consignes (le reste ne change pas)
    tft.setTextSize(3); tft.setTextColor(cyan, 0);
    tft.setCursor(25, 110); tft.print(F("-")); 
    tft.setCursor(195, 110); tft.print(F("+"));
    
    btn(60, 175, 120, 30, (mode == EDIT_DEADZONE) ? "TERMINER" : "VALIDER", 0x8410);
  }

}

void actualiserPoids(float p) {
  // 1. RÉCUPÉRATION DES PARAMÈTRES DU PROFIL
  int pMax = settings.profils[settings.pIdx].poidsTotal;
  if (pMax <= 0) pMax = 1000; // Sécurité anti-division par zéro

  // 2. CALCUL DE LA COULEUR (selon le pourcentage restant)
  float pourcentage = (p / (float)pMax) * 100.0;
  uint16_t col;
  if (pourcentage > 30)      col = 0x07E0; // Vert (OK)
  else if (pourcentage > 10) col = 0xFFE0; // Jaune (Attention)
  else                       col = 0xF800; // Rouge (Critique)

  // 3. AFFICHAGE DU TEXTE AU CENTRE
  // On efface largement la zone centrale pour éviter les "digits fantômes"
  tft.fillRect(40, 95, 160, 45, 0); 

  String s = String((int)p);
  // Calcul du centrage : Taille 4 (24px de large) + unité "g" (12px)
  int largeurTotale = (s.length() * 24) + 15; 
  int xStart = 120 - (largeurTotale / 2);

  tft.setTextSize(4); 
  tft.setTextColor(col);
  tft.setCursor(xStart, 100); 
  tft.print(s);
  
  tft.setTextSize(2); 
  tft.print(F("g"));

  // 4. ANNEAU DE PROGRESSION (LED VIRTUELLES)
  // On mappe le poids actuel sur 360 degrés
  int a = map(constrain((int)p, 0, pMax), 0, pMax, 0, 360);
  
  static int dernierA = -1; 
  // Rafraîchissement de l'anneau uniquement si le poids change d'au moins 1 "LED" (12°)
  if (abs(a - dernierA) >= 12) { 
    for (int i = 0; i < 360; i += 12) {
      // Angle en radians (-90° pour commencer en haut)
      float rad = (i - 90) * 0.01745; 
      int xLed = 120 + cos(rad) * 110;
      int yLed = 120 + sin(rad) * 110;

      // Détermine si la "LED" est active ou éteinte (gris sombre)
      uint16_t couleurPoint = (i <= a) ? col : 0x2104; 
      tft.fillCircle(xLed, yLed, 4, couleurPoint);
    }
    dernierA = a; // Mémorise l'état pour le prochain passage
  }
}

