#include <Wire.h>

// Controlador LCD I2C integrado: no requiere descargar bibliotecas.
// Conexion PCF8574 habitual: RS=P0, EN=P2 y luz=P3.
class LiquidCrystal_PCF8574 : public Print {
 public:
  LiquidCrystal_PCF8574(uint8_t address) : address_(address), backlight_(0x08) {}
  void begin(uint8_t columns, uint8_t rows) {
    (void)columns; (void)rows;
    delay(50);
    write4Bits(0x30); delayMicroseconds(4500);
    write4Bits(0x30); delayMicroseconds(4500);
    write4Bits(0x30); delayMicroseconds(150);
    write4Bits(0x20);
    command(0x28);
    command(0x0C);
    command(0x06);
    clear();
  }
  void setBacklight(uint8_t value) {
    backlight_ = value ? 0x08 : 0x00;
    expanderWrite(0);
  }
  void display() { command(0x0C); }
  void clear() {
    command(0x01);
    delayMicroseconds(2000);
  }
  void setCursor(uint8_t column, uint8_t row) {
    const uint8_t offsets[] = {0x00, 0x40};
    command(0x80 | (column + offsets[row > 0 ? 1 : 0]));
  }
  virtual size_t write(uint8_t value) {
    send(value, 0x01);
    return 1;
  }
  using Print::write;

 private:
  uint8_t address_;
  uint8_t backlight_;
  void command(uint8_t value) { send(value, 0x00); }
  void send(uint8_t value, uint8_t mode) {
    write4Bits((value & 0xF0) | mode);
    write4Bits(((value << 4) & 0xF0) | mode);
  }
  void write4Bits(uint8_t value) {
    expanderWrite(value);
    pulseEnable(value);
  }
  void expanderWrite(uint8_t value) {
    Wire.beginTransmission(address_);
    Wire.write(value | backlight_);
    Wire.endTransmission();
  }
  void pulseEnable(uint8_t value) {
    expanderWrite(value | 0x04);
    delayMicroseconds(1);
    expanderWrite(value & ~0x04);
    delayMicroseconds(50);
  }
};

LiquidCrystal_PCF8574 lcd(0x27);

const int sensorHumedad = A0;
const int botonManual = 2;
const int reles[] = {7, 8, 9};
const int RELAY_ON = LOW;
const int RELAY_OFF = HIGH;
const int limiteSeco = 550;

const unsigned long intervaloLectura = 2000;
const unsigned long intervaloEntreSecuencias = 60000;
const unsigned long duracionBombas[] = {30000, 30000, 5000};
const unsigned long pausaEntreBombas = 1000;

int humedadActual = 0;
int bombaActiva = 0;
int siguienteBomba = 0;
bool tierraSeca = false;
bool secuenciaActiva = false;
bool primeraSecuencia = true;
const char* origenSecuencia = "ESPERA";

unsigned long ultimaLectura = 0;
unsigned long ultimaSecuencia = 0;
unsigned long inicioEtapa = 0;
unsigned long ultimaTelemetria = 0;
unsigned long ultimoBoton = 0;

void apagarBombas() {
  for (int i = 0; i < 3; i++) digitalWrite(reles[i], RELAY_OFF);
}

unsigned long segundosRestantes() {
  if (!bombaActiva) return 0;
  unsigned long duracionActual = duracionBombas[bombaActiva - 1];
  unsigned long pasado = millis() - inicioEtapa;
  return pasado >= duracionActual ? 0 : (duracionActual - pasado + 999) / 1000;
}

// El dashboard interpreta una línea JSON completa por cada actualización.
void enviarEstado(const char* evento = "") {
  Serial.print(F("{\"humedad\":")); Serial.print(humedadActual);
  Serial.print(F(",\"seca\":")); Serial.print(tierraSeca ? F("true") : F("false"));
  Serial.print(F(",\"secuencia\":")); Serial.print(secuenciaActiva ? F("true") : F("false"));
  Serial.print(F(",\"bomba\":")); Serial.print(bombaActiva);
  Serial.print(F(",\"restante\":")); Serial.print(segundosRestantes());
  Serial.print(F(",\"origen\":\"")); Serial.print(origenSecuencia);
  Serial.print(F("\",\"evento\":\"")); Serial.print(evento);
  Serial.println(F("\"}"));
}

void mostrarEstadoLCD() {
  lcd.clear();
  if (bombaActiva) {
    lcd.print(F("FUNDANZA R")); lcd.print(bombaActiva);
    lcd.setCursor(0, 1); lcd.print(F("FALTAN "));
    lcd.print(segundosRestantes()); lcd.print(F(" S"));
  } else {
    lcd.print(F("HUMEDAD: ")); lcd.print(humedadActual);
    lcd.setCursor(0, 1); lcd.print(tierraSeca ? F("TIERRA SECA") : F("TIERRA HUMEDA"));
  }
}

void activarBomba(int numero) {
  apagarBombas();
  bombaActiva = numero;
  digitalWrite(reles[numero - 1], RELAY_ON);
  inicioEtapa = millis();
  char evento[24];
  snprintf(evento, sizeof(evento), "Riego %d activado", numero);
  enviarEstado(evento);
  mostrarEstadoLCD();
}

void iniciarSecuencia(const char* origen) {
  secuenciaActiva = true;
  origenSecuencia = origen;
  siguienteBomba = 0;
  activarBomba(1);
}

void controlarSecuencia() {
  if (!secuenciaActiva) return;
  unsigned long ahora = millis();

  if (bombaActiva && ahora - inicioEtapa >= duracionBombas[bombaActiva - 1]) {
    apagarBombas();
    siguienteBomba = bombaActiva + 1;
    bombaActiva = 0;
    inicioEtapa = ahora;
    enviarEstado("Etapa finalizada");
  } else if (!bombaActiva && siguienteBomba > 0 && ahora - inicioEtapa >= pausaEntreBombas) {
    if (siguienteBomba <= 3) {
      activarBomba(siguienteBomba);
      siguienteBomba = 0;
    } else {
      secuenciaActiva = false;
      siguienteBomba = 0;
      origenSecuencia = "ESPERA";
      ultimaSecuencia = ahora;
      enviarEstado("Secuencia completada");
      mostrarEstadoLCD();
    }
  }

  if (ahora - ultimaTelemetria >= 1000) {
    ultimaTelemetria = ahora;
    enviarEstado();
    mostrarEstadoLCD();
  }
}

void setup() {
  Serial.begin(9600);
  Wire.begin();
  pinMode(sensorHumedad, INPUT);
  pinMode(botonManual, INPUT_PULLUP);
  for (int i = 0; i < 3; i++) pinMode(reles[i], OUTPUT);
  apagarBombas();

  lcd.begin(16, 2); lcd.setBacklight(255); lcd.display();
  lcd.clear(); lcd.print(F("AQUA CAFE 10B"));
  lcd.setCursor(0, 1); lcd.print(F("FUNDANZA")); delay(2500);

  humedadActual = analogRead(sensorHumedad);
  tierraSeca = humedadActual > limiteSeco;
  mostrarEstadoLCD();
  enviarEstado("Sistema listo");
}

void loop() {
  unsigned long ahora = millis();

  // Orden enviada desde el botón "Iniciar riego manual" del dashboard.
  if (Serial.available() > 0) {
    String comando = Serial.readStringUntil('\n');
    comando.trim();
    if (comando == "START") {
      if (!secuenciaActiva) {
        primeraSecuencia = false;
        iniciarSecuencia("DASHBOARD");
        enviarEstado("Secuencia manual iniciada");
      } else {
        enviarEstado("Sistema ocupado");
      }
    }
  }

  if (ahora - ultimaLectura >= intervaloLectura) {
    ultimaLectura = ahora;
    humedadActual = analogRead(sensorHumedad);
    tierraSeca = humedadActual > limiteSeco;
    enviarEstado();
    if (!secuenciaActiva && tierraSeca && (primeraSecuencia || ahora - ultimaSecuencia >= intervaloEntreSecuencias)) {
      primeraSecuencia = false;
      iniciarSecuencia("SENSOR");
    }
  }

  if (digitalRead(botonManual) == LOW && ahora - ultimoBoton > 700) {
    ultimoBoton = ahora;
    if (!secuenciaActiva) {
      primeraSecuencia = false;
      iniciarSecuencia("PULSADOR");
    }
  }
  controlarSecuencia();
}
