#include <Servo.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecureBearSSL.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

// ================================================================
//  OTA CONFIG
// ================================================================
#define FW_VERSION   18
#define GITEA_HOST   "https://git.nacho.myds.me"
#define GITEA_OWNER  "Natxo"
#define GITEA_REPO   "Arduino-Car"
#define GITEA_BRANCH "main"
#define GITEA_TOKEN  "d3b0b32eb0311c9b4ef569f22c8392c373ff3f9f"
// WiFi — mismas credenciales que usa el ESP32-CAM
#define WIFI_SSID    "Natxo"
#define WIFI_PASS    "Suprak1llm1ndS23S"
// ================================================================

// ==== Pines motores ====
const int ENA = D1;
const int ENB = D5;
const int IN1 = D6;
const int IN2 = D7;
const int IN3 = D2;
const int IN4 = D0;

// ==== Pines servos ====
const int SERVO_X_PIN = D3;
const int SERVO_Y_PIN = D4;

// ==== Pines buzzer ====
const int buzzerPin = D8;

Servo servoX;
Servo servoY;

int currentSpeed = 150;
int posX = 90;
int posY = 90;

// ── OTA: comprueba si hay firmware nuevo y actualiza si hace falta ──
static void checkOTA() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  int tries = 0;
  while (WiFi.status() != WL_CONNECTED && tries < 20) {
    delay(500);
    tries++;
  }

  if (WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    WiFi.mode(WIFI_OFF);
    return;   // sin WiFi, arranque normal
  }

  String base  = String(GITEA_HOST) + "/" + GITEA_OWNER + "/" + GITEA_REPO
               + "/raw/branch/" + GITEA_BRANCH + "/firmware/esp8266/";
  String token = "?token=" + String(GITEA_TOKEN);

  // ── Bloque de scope: libera el cliente BearSSL antes de la descarga ──
  // BearSSL ocupa ~20KB de heap. Si los dos clientes coexisten el heap
  // se agota y ESPhttpUpdate falla silenciosamente.
  // Scope block: destruye el cliente BearSSL antes de crear el de descarga
  // Libera ~20KB de heap (BearSSL con buffers por defecto) para la descarga
  int remoteVer = 0;
  {
    BearSSL::WiFiClientSecure client;
    client.setInsecure();
    HTTPClient http;
    http.begin(client, base + "version.txt" + token);
    http.setTimeout(10000);
    int code = http.GET();
    if (code == HTTP_CODE_OK) {
      String body = http.getString();
      body.trim();
      remoteVer = body.toInt();
    }
    http.end();
  }  // client destruido aquí — heap recuperado

  if (remoteVer > FW_VERSION) {
    // 4 pitidos = ESP8266 actualizando
    pinMode(buzzerPin, OUTPUT);
    for (int i = 0; i < 4; i++) {
      digitalWrite(buzzerPin, HIGH);
      delay(150);
      digitalWrite(buzzerPin, LOW);
      delay(150);
    }
    delay(300);

    // Fuerza pines de motor a LOW antes del flash para minimizar glitch de GPIO
    analogWrite(ENA, 0); analogWrite(ENB, 0);
    digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
    digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);

    BearSSL::WiFiClientSecure updateClient;
    updateClient.setInsecure();
    ESPhttpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
    ESPhttpUpdate.rebootOnUpdate(true);
    ESPhttpUpdate.update(updateClient, base + "firmware.bin" + token);
    // Si llega aquí, el update falló — continúa arranque normal
  }

  WiFi.disconnect();
  WiFi.mode(WIFI_OFF);   // apaga radio para ahorrar energía
}

bool espReady = false;

void waitForReady() {
  delay(300);
  while (Serial.available()) Serial.read();
  unsigned long timeout = millis() + 90000UL;
  String buf = "";
  while (millis() < timeout) {
    while (Serial.available()) {
      char c = Serial.read();
      if (c == '\n') {
        buf.trim();
        if (buf == "GO") {
          Serial.print("OK\n");
          delay(50);
          while (Serial.available()) Serial.read();
          espReady = true;
          // 2 pitidos: conexión entre placas lista
          for (int i = 0; i < 2; i++) {
            digitalWrite(buzzerPin, HIGH); delay(180);
            digitalWrite(buzzerPin, LOW);  delay(180);
          }
          return;
        }
        buf = "";
      } else if (c != '\r' && buf.length() < 8) {
        buf += c;
      }
    }
    delay(10);
  }
  espReady = true;  // timeout: arranca igualmente
}

void setup() {
  // 1. Pines y parada inmediata
  pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
  pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  stopMotors();

  // 2. Serial temprano (el ESP32 puede mandar el pitido de OTA)
  Serial.begin(9600);

  // 3. OTA via WiFi
  checkOTA();

  // 4. Resto de periféricos
  analogWriteRange(255);
  analogWriteFreq(1000);
  servoX.attach(SERVO_X_PIN);
  servoY.attach(SERVO_Y_PIN);
  pinMode(buzzerPin, OUTPUT);
  servoX.write(posX);
  servoY.write(posY);

  // 5. Esperar "GO\n" del ESP32-CAM antes de procesar comandos
  waitForReady();
}

// Watchdog ligero en ESP8266: si no llega comando de movimiento en 1.5s → para
// Evita que el coche siga moviéndose si el ESP32 se cuelga o reinicia
unsigned long lastMoveMs = 0;
bool motorRunning = false;

void loop() {
  // Watchdog: sin comandos durante 1.5s → para motores
  if (motorRunning && (millis() - lastMoveMs > 1500)) {
    stopMotors();
    motorRunning = false;
  }

  if (Serial.available()) {
    char c = Serial.read();

    // Reinicio del ESP32-CAM: manda "GO\n" de nuevo
    if (c == 'G') {
      espReady = false;
      stopMotors();
      motorRunning = false;
      Serial.readStringUntil('\n');
      Serial.print("OK\n");
      delay(50);
      while (Serial.available()) Serial.read();
      espReady = true;
      return;
    }

    // Ignorar todo hasta que el handshake esté completo
    if (!espReady) return;

    if      (c == 'F') { forward();    lastMoveMs = millis(); motorRunning = true; }
    else if (c == 'B') { backward();   lastMoveMs = millis(); motorRunning = true; }
    else if (c == 'L') { left();       lastMoveMs = millis(); motorRunning = true; }
    else if (c == 'R') { right();      lastMoveMs = millis(); motorRunning = true; }
    else if (c == 'S') { stopMotors(); motorRunning = false; }

    else if (c == 'V') {
      int val = Serial.parseInt();
      if (val >= 0 && val <= 255) currentSpeed = val;
    }
    else if (c == 'X') {
      int val = Serial.parseInt();
      if (val >= 0 && val <= 180) { posX = val; servoX.write(posX); }
    }
    else if (c == 'Y') {
      int val = Serial.parseInt();
      if (val >= 0 && val <= 180) { posY = val; servoY.write(posY); }
    }
    else if (c == 'H') {
      int val = Serial.parseInt();
      digitalWrite(buzzerPin, val > 0 ? HIGH : LOW);
    }
  }
}

// ==== Funciones de movimiento ====
void forward() {
  analogWrite(ENA, currentSpeed);
  analogWrite(ENB, currentSpeed);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH);
}

void backward() {
  analogWrite(ENA, currentSpeed);
  analogWrite(ENB, currentSpeed);
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

void left() {
  analogWrite(ENA, currentSpeed);
  analogWrite(ENB, currentSpeed);
  digitalWrite(IN1, LOW);  digitalWrite(IN2, HIGH);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
}

void right() {
  analogWrite(ENA, currentSpeed);
  analogWrite(ENB, currentSpeed);
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW);  digitalWrite(IN4, HIGH);
}

void stopMotors() {
  analogWrite(ENA, 0);
  analogWrite(ENB, 0);
  digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
  digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
}
