#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>

// ----------- WiFi -----------
static const char* WIFI_SSID = "ssid";
static const char* WIFI_PASS = "password";

// ----------- DHT -----------
static const int DHT_PIN = 10;     // GPIO for DHT DATA
static const int DHT_TYPE = DHT11;
DHT dht(DHT_PIN, DHT_TYPE);

// ----------- Server -----------
WebServer server(80);

// ----------- Reading interval -----------
static const unsigned long READ_MS = 2000;
unsigned long lastRead = 0;

// ----------- Latest values -----------
float latestH = NAN;
float latestT_F = NAN;

String htmlPage() {
  return R"HTML(
<!doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
  <title>ESP32-C3 DHT11</title>
  <style>
    body { font-family: system-ui, Arial; margin: 2rem; }
    .card { padding: 1rem 1.2rem; border: 1px solid #ddd; border-radius: 12px; max-width: 720px; }
    .val { font-size: 2rem; margin: 0.2rem 0; }
    .small { color: #555; font-size: 0.9rem; }
    .row { display: flex; gap: 2rem; flex-wrap: wrap; }
    .box { flex: 1; min-width: 240px; }
    canvas { width: 100%; height: 260px; background: #fafafa; border: 1px solid #eee; border-radius: 10px; }
    .legend { display: flex; gap: 1rem; align-items: center; margin-top: 0.5rem; }
    .dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; margin-right: 0.35rem; }
    .btnbar { margin-top: 0.8rem; display: flex; gap: 0.6rem; flex-wrap: wrap; }
    button { padding: 0.5rem 0.8rem; border-radius: 10px; border: 1px solid #ddd; background: #fff; cursor: pointer; }
  </style>
</head>
<body>
  <div class="card">
    <h2>ESP32 DHT</h2>
    <div class="small">Updates every 2 seconds • Last ~60 samples</div>

    <div class="row">
      <div class="box">
        <p><b>Temperature:</b></p>
        <p class="val" id="temp">--</p>
      </div>
      <div class="box">
        <p><b>Humidity:</b></p>
        <p class="val" id="hum">--</p>
      </div>
    </div>

    <canvas id="chart" width="680" height="260"></canvas>
    <div class="legend">
      <div><span class="dot" style="background:#e53935"></span>Temp (°F)</div>
      <div><span class="dot" style="background:#1e88e5"></span>Humidity (%)</div>
    </div>

    <div class="btnbar">
      <button onclick="clearData()">Clear chart</button>
    </div>
  </div>

<script>
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');

// Keep separate series; chart shares the x-axis (time)
const MAX_POINTS = 60;
let tempSeries = []; // {x, v}
let humSeries = [];  // {x, v}

function resizeForDPR() {
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  canvas.width = Math.round(rect.width * dpr);
  canvas.height = Math.round(rect.height * dpr);
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0); // draw in CSS pixels
}
window.addEventListener('resize', resizeForDPR);
resizeForDPR();

function draw() {
  const w = canvas.getBoundingClientRect().width;
  const h = canvas.getBoundingClientRect().height;

  ctx.clearRect(0, 0, w, h);

  // Background grid
  ctx.fillStyle = '#fff';
  ctx.fillRect(0, 0, w, h);

  ctx.strokeStyle = '#eee';
  ctx.lineWidth = 1;

  const gridLines = 5;
  for (let i = 1; i < gridLines; i++) {
    const y = (h * i) / gridLines;
    ctx.beginPath();
    ctx.moveTo(0, y);
    ctx.lineTo(w, y);
    ctx.stroke();
  }

  // If no data, stop
  if (tempSeries.length === 0 && humSeries.length === 0) return;

  // Compute ranges (ignore missing nulls)
  const allTemp = tempSeries.map(p => p.v).filter(v => Number.isFinite(v));
  const allHum = humSeries.map(p => p.v).filter(v => Number.isFinite(v));

  const tempMin = allTemp.length ? Math.min(...allTemp) : 0;
  const tempMax = allTemp.length ? Math.max(...allTemp) : 1;
  const humMin  = allHum.length  ? Math.min(...allHum)  : 0;
  const humMax  = allHum.length  ? Math.max(...allHum)  : 1;

  // Avoid flat-line divisions
  const tempSpan = (tempMax - tempMin) || 1;
  const humSpan  = (humMax - humMin)  || 1;

  // x scale: index across MAX_POINTS window
  const count = Math.max(tempSeries.length, humSeries.length);
  const xForIndex = (i) => {
    if (count <= 1) return 0;
    return (w * i) / (count - 1);
  };

  function plot(series, color, min, span) {
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.beginPath();

    for (let i = 0; i < series.length; i++) {
      const v = series[i].v;
      if (!Number.isFinite(v)) continue;

      const x = xForIndex(i);
      const y = h - ((v - min) / span) * (h - 20) - 10; // padding

      if (i === 0) ctx.moveTo(x, y);
      else ctx.lineTo(x, y);
    }
    ctx.stroke();
  }

  // Draw both series
  // (We scale each series to its own min/max so you always get a visible line.)
  plot(tempSeries, '#e53935', tempMin, tempSpan);
  plot(humSeries,  '#1e88e5', humMin,  humSpan);

  // Labels (min/max)
  ctx.fillStyle = '#333';
  ctx.font = '12px system-ui, Arial';
  ctx.fillText(`Temp range: ${tempMin.toFixed(1)}–${tempMax.toFixed(1)} °F`, 10, 16);
  ctx.fillText(`Hum range: ${humMin.toFixed(1)}–${humMax.toFixed(1)} %`, 10, 32);
}

function pushPoint(series, value) {
  if (Number.isFinite(value)) {
    series.push(value);
    while (series.length > MAX_POINTS) series.shift();
  } else {
    // Keep time alignment by pushing NaN placeholder only if you want strict alignment.
    // Here we just skip.
  }
}

function clearData() {
  tempSeries = [];
  humSeries = [];
  draw();
}

async function refresh() {
  try {
    const res = await fetch('/data?nocache=' + Date.now());
    const j = await res.json();

    // Update numbers
    if (j.t_f === null) document.getElementById('temp').textContent = '--';
    else document.getElementById('temp').textContent = j.t_f.toFixed(1) + ' °F';

    if (j.h === null) document.getElementById('hum').textContent = '--';
    else document.getElementById('hum').textContent = j.h.toFixed(1) + ' %';

    // Update series + chart
    if (j.t_f !== null) tempSeries.push({x: Date.now(), v: j.t_f});
    if (j.h   !== null) humSeries.push({x: Date.now(), v: j.h});

    while (tempSeries.length > MAX_POINTS) tempSeries.shift();
    while (humSeries.length > MAX_POINTS) humSeries.shift();

    draw();
  } catch (e) {
    // ignore fetch errors
  }
}

refresh();
setInterval(refresh, 2000);
</script>
</body>
</html>
)HTML";
}


void handleRoot() {
  server.send(200, "text/html", htmlPage());
}

void handleData() {
  // Convert NaN to null for easy JSON handling
  float h = latestH;
  float t = latestT_F;

  String json = "{";
  json += "\"h\": " + (isnan(h) ? String("null") : String(h, 1)) + ",";
  json += "\"t_f\": " + (isnan(t) ? String("null") : String(t, 1));
  json += "}";

  server.send(200, "application/json", json);
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("ESP32-C3 DHT11 starting...");

  dht.begin();

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);

  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected. IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.begin();
  Serial.println("HTTP server started.");
}

void loop() {
  // keep server responsive
  server.handleClient();

  unsigned long now = millis();
  if (now - lastRead < READ_MS) return;
  lastRead = now;

  float h = dht.readHumidity();
  float tC = dht.readTemperature(); // Celsius

  if (isnan(h) || isnan(tC)) {
    Serial.println("DHT read failed (check wiring/pull-up).");
    latestH = NAN;
    latestT_F = NAN;
    return;
  }

  latestH = h;
  latestT_F = dht.convertCtoF(tC);

  Serial.print("Humidity: ");
  Serial.print(latestH, 1);
  Serial.print(" %\tTemp: ");
  Serial.print(latestT_F, 1);
  Serial.println(" F");

  Serial.print("Connected. IP address: ");
  Serial.println(WiFi.localIP());
}
