#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <AsyncTCP.h>
#include <ESP32Servo.h>
#include <DNSServer.h>

// ================================================================
// CONFIGURATION DU ROBOT
// ================================================================
const char* ssid = "Robot_03";
const char* password = "robotsumo";

// Broches GPIO
const int PIN_SERVO_GAUCHE = 25;
const int PIN_SERVO_DROITE = 26;

// Servomoteurs
Servo servoGauche;
Servo servoDroite;

// Sécurité : Timeout de perte de signal (en millisecondes)
const unsigned long TIMEOUT_SIGNAL_MS = 1000; 
unsigned long dernierMessageRecu = 0;
bool clientConnecte = false;

// Configuration Réseau
IPAddress local_IP(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
DNSServer dnsServer;

int valThrottle = 0;
int valSteer = 0;
bool invertSens = false;
int trimGauche = 0;
int trimDroite = 0;

String titreRobot = "Robot Sumo";

// ================================================================
// INTERFACE WEB HTML + CSS + JS (PROGMEM)
// ================================================================
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>Commande Robot ESP32</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #121212;
      color: white;
      margin: 0;
      padding: 20px 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 15px;
    }
    h2 { margin: 2px 0; font-size: 1.1rem; color: #00adb5; margin-top: 5px; margin-bottom: 5px;}
    .status { font-size: 0.8rem; color: #888; margin-bottom: 2px; }
    
    .top-bar {
      display: flex;
      gap: 10px;
      margin-bottom: 5px;
    }

    .btn {
      padding: 8px 16px;
      font-size: 0.9rem;
      font-weight: bold;
      color: #fff;
      background-color: #222;
      border: 2px solid #00adb5;
      border-radius: 15px;
      cursor: pointer;
    }
    .btn.active {
      background-color: #00adb5;
      color: #121212;
    }

    .controls-container {
      display: flex;
      width: 100%;
      justify-content: space-around;
      align-items: center;
    }
    .joystick-box {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    .label { margin-top: 5px; font-weight: bold; color: #aaa; font-size: 0.85rem; }
    canvas {
      background: #1e1e1e;
      border: 3px solid #00adb5;
      border-radius: 12px;
      touch-action: none;
    }

    .trim-container {
      display: flex;
      flex-direction: column;
      align-items: center;
      background-color: #1a1a1a;
      border: 1px solid #333;
      padding: 8px 15px;
      border-radius: 10px;
      width: 80%;
      max-width: 320px;
    }
    .trim-title { font-size: 0.8rem; color: #00adb5; margin-bottom: 5px; font-weight: bold; }
    .trim-controls {
      display: flex;
      justify-content: space-between;
      width: 100%;
      gap: 15px;
    }
    .trim-group {
      display: flex;
      align-items: center;
      gap: 5px;
      font-size: 0.8rem;
    }
    .btn-trim {
      width: 26px;
      height: 26px;
      background-color: #333;
      border: 1px solid #00adb5;
      color: white;
      border-radius: 5px;
      font-weight: bold;
      cursor: pointer;
    }
  </style>
</head>
<body>

  <h2 id="titleRobot">Robot Sumo</h2>
  <div class="status" id="ws-status">Connexion en cours...</div>
  
  <div class="top-bar">
    <button id="btnInvert" class="btn" onclick="toggleInvert()">Inv. Servos : OFF</button>
  </div>

  <div class="controls-container">
    <div class="joystick-box">
      <canvas id="joyLeft" width="80" height="180"></canvas>
      <div class="label">AVANT / ARRIÈRE</div>
    </div>
    <div class="joystick-box">
      <canvas id="joyRight" width="180" height="80"></canvas>
      <div class="label">GAUCHE / DROITE</div>
    </div>
  </div>

  <div class="trim-container">
    <div class="trim-title">RÉGLAGE DU ZÉRO (TRIM)</div>
    <div class="trim-controls">
      <div class="trim-group">
        <span>G:</span>
        <button class="btn-trim" onclick="changeTrim('g', -1)">-</button>
        <span id="valTrimG">0</span>
        <button class="btn-trim" onclick="changeTrim('g', 1)">+</button>
      </div>
      <div class="trim-group">
        <span>D:</span>
        <button class="btn-trim" onclick="changeTrim('d', -1)">-</button>
        <span id="valTrimD">0</span>
        <button class="btn-trim" onclick="changeTrim('d', 1)">+</button>
      </div>
    </div>
  </div>

<script>
  let gateway = `ws://${window.location.hostname}/ws`;
  let websocket;
  let invertState = false;
  let trimG = 0;
  let trimD = 0;

  function initWebSocket() {
    websocket = new WebSocket(gateway);
    websocket.onopen = function() {
      document.getElementById('ws-status').innerText = 'Connecté';
      document.getElementById('ws-status').style.color = '#00ff88';
    };
    websocket.onclose = function() {
      document.getElementById('ws-status').innerText = 'Déconnecté - Reconnexion...';
      document.getElementById('ws-status').style.color = '#ff4444';
      setTimeout(initWebSocket, 1000);
    };
    websocket.onmessage = function(event) {
      try {
        let data = JSON.parse(event.data);
        if (data.title) {
          document.getElementById('titleRobot').innerText = data.title;
        }
      } catch(e) {}
    };
  }

  window.addEventListener('load', () => {
    initWebSocket();
    setupVerticalJoystick('joyLeft');
    setupHorizontalJoystick('joyRight');
  });

  let valThrottle = 0;
  let valSteer = 0;

  function sendDriveData() {
    if (websocket && websocket.readyState === WebSocket.OPEN) {
      websocket.send(JSON.stringify({ 
        t: valThrottle, 
        s: valSteer, 
        i: invertState,
        tg: trimG,
        td: trimD
      }));
    }
  }

  function toggleInvert() {
    invertState = !invertState;
    const btn = document.getElementById('btnInvert');
    btn.innerText = invertState ? "Inv. Servos : ON" : "Inv. Servos : OFF";
    btn.classList.toggle("active", invertState);
    sendDriveData();
  }

  function changeTrim(moteur, val) {
    if (moteur === 'g') {
      trimG = Math.min(20, Math.max(-20, trimG + val));
      document.getElementById('valTrimG').innerText = trimG;
    } else {
      trimD = Math.min(20, Math.max(-20, trimD + val));
      document.getElementById('valTrimD').innerText = trimD;
    }
    sendDriveData();
  }

  function setupVerticalJoystick(canvasId) {
    const canvas = document.getElementById(canvasId);
    const ctx = canvas.getContext('2d');
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const maxTravel = (canvas.height / 2) - 20;

    let active = false;
    let posY = centerY;

    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.beginPath();
      ctx.moveTo(centerX, 20);
      ctx.lineTo(centerX, canvas.height - 20);
      ctx.lineWidth = 6;
      ctx.strokeStyle = '#333';
      ctx.stroke();

      ctx.beginPath();
      ctx.arc(centerX, posY, 18, 0, Math.PI * 2);
      ctx.fillStyle = '#00adb5';
      ctx.fill();
    }

    function handleMove(clientY) {
      const rect = canvas.getBoundingClientRect();
      let dy = clientY - rect.top - centerY;
      dy = Math.max(-maxTravel, Math.min(maxTravel, dy));
      
      posY = centerY + dy;
      valThrottle = Math.round((-dy / maxTravel) * 100);

      sendDriveData();
      draw();
    }

    function reset() {
      active = false;
      posY = centerY;
      valThrottle = 0;
      sendDriveData();
      draw();
    }

    canvas.addEventListener('touchstart', (e) => { active = true; handleMove(e.touches[0].clientY); });
    canvas.addEventListener('touchmove', (e) => { if(active) handleMove(e.touches[0].clientY); });
    canvas.addEventListener('touchend', reset);

    canvas.addEventListener('mousedown', (e) => { active = true; handleMove(e.clientY); });
    window.addEventListener('mousemove', (e) => { if(active) handleMove(e.clientY); });
    window.addEventListener('mouseup', () => { if(active) reset(); });

    draw();
  }

  function setupHorizontalJoystick(canvasId) {
    const canvas = document.getElementById(canvasId);
    const ctx = canvas.getContext('2d');
    const centerX = canvas.width / 2;
    const centerY = canvas.height / 2;
    const maxTravel = (canvas.width / 2) - 20;

    let active = false;
    let posX = centerX;

    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.beginPath();
      ctx.moveTo(20, centerY);
      ctx.lineTo(canvas.width - 20, centerY);
      ctx.lineWidth = 6;
      ctx.strokeStyle = '#333';
      ctx.stroke();

      ctx.beginPath();
      ctx.arc(posX, centerY, 18, 0, Math.PI * 2);
      ctx.fillStyle = '#00adb5';
      ctx.fill();
    }

    function handleMove(clientX) {
      const rect = canvas.getBoundingClientRect();
      let dx = clientX - rect.left - centerX;
      dx = Math.max(-maxTravel, Math.min(maxTravel, dx));

      posX = centerX + dx;
      valSteer = Math.round((dx / maxTravel) * 100);

      sendDriveData();
      draw();
    }

    function reset() {
      active = false;
      posX = centerX;
      valSteer = 0;
      sendDriveData();
      draw();
    }

    canvas.addEventListener('touchstart', (e) => { active = true; handleMove(e.touches[0].clientX); });
    canvas.addEventListener('touchmove', (e) => { if(active) handleMove(e.touches[0].clientX); });
    canvas.addEventListener('touchend', reset);

    canvas.addEventListener('mousedown', (e) => { active = true; handleMove(e.clientX); });
    window.addEventListener('mousemove', (e) => { if(active) handleMove(e.clientX); });
    window.addEventListener('mouseup', () => { if(active) reset(); });

    draw();
  }
</script>
</body>
</html>
)rawliteral";

void appliquerVitesse(int throttle, int steer, bool invert, int trimG, int trimD) {
  if (invert) {
    throttle = -throttle;
  }

  int vitesseGauche = constrain(throttle + steer, -100, 100);
  int vitesseDroite = constrain(throttle - steer, -100, 100);

  int angleGauche = map(vitesseGauche, -100, 100, 0, 180) + trimG;
  int angleDroite = map(vitesseDroite, -100, 100, 180, 0) + trimD;

  angleGauche = constrain(angleGauche, 0, 180);
  angleDroite = constrain(angleDroite, 0, 180);

  servoGauche.write(angleGauche);
  servoDroite.write(angleDroite);
}

void arreterRobot() {
  valThrottle = 0;
  valSteer = 0;
  appliquerVitesse(0, 0, invertSens, trimGauche, trimDroite);
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
  if (type == WS_EVT_CONNECT) {
    clientConnecte = true;
    dernierMessageRecu = millis();
    
    // Envoi du nom du robot lors de la connexion
    String jsonTitle = "{\"title\":\"" + titreRobot + "\"}";
    client->text(jsonTitle);
  } 
  else if (type == WS_EVT_DISCONNECT) {
    clientConnecte = false;
    arreterRobot();
  } 
  else if (type == WS_EVT_DATA) {
    data[len] = 0;
    String message = (char*)data;

    int tIndex  = message.indexOf("\"t\":");
    int sIndex  = message.indexOf("\"s\":");
    int iIndex  = message.indexOf("\"i\":");
    int tgIndex = message.indexOf("\"tg\":");
    int tdIndex = message.indexOf("\"td\":");

    if (tIndex != -1 && sIndex != -1) {
      valThrottle = message.substring(tIndex + 4, message.indexOf(',', tIndex)).toInt();
      valSteer = message.substring(sIndex + 4, message.indexOf(',', sIndex)).toInt();

      if (iIndex != -1) {
        invertSens = message.substring(iIndex + 4, message.indexOf(',', iIndex)).startsWith("true");
      }

      if (tgIndex != -1) {
        trimGauche = message.substring(tgIndex + 5, message.indexOf(',', tgIndex)).toInt();
      }

      if (tdIndex != -1) {
        trimDroite = message.substring(tdIndex + 5, message.indexOf('}', tdIndex)).toInt();
      }

      appliquerVitesse(valThrottle, valSteer, invertSens, trimGauche, trimDroite);
      dernierMessageRecu = millis();
      clientConnecte = true;
    }
  }
}

void setup() {
  Serial.begin(115200);

  servoGauche.attach(PIN_SERVO_GAUCHE);
  servoDroite.attach(PIN_SERVO_DROITE);
  servoGauche.write(90);
  servoDroite.write(90);

  Serial.print("Nom du réseau Wi-Fi : ");
  Serial.println(ssid);

  // Configuration du Point d'Accès avec le SSID fixe
  WiFi.mode(WIFI_AP);
  WiFi.softAPConfig(local_IP, gateway, subnet);
  WiFi.softAP(ssid, password, 4);

  // Serveur DNS (Portail captif)
  dnsServer.start(53, "*", local_IP);

  ws.onEvent(onEvent);
  server.addHandler(&ws);

  // Envoi de la page sans parser de template
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });

  // Redirection portail captif
  server.onNotFound([](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });

  server.begin();
}

void loop() {
  dnsServer.processNextRequest();
  ws.cleanupClients();

  if (clientConnecte && (millis() - dernierMessageRecu > TIMEOUT_SIGNAL_MS)) {
    clientConnecte = false;
    arreterRobot();
  }
}