#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Adafruit_NeoPixel.h>
#include <EEPROM.h>
#include <time.h>
#include <WiFiManager.h>

// ================= LED =================
#define PIN D4
#define NUMPIXELS 110
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

// ================= SERVER & NTP =================
ESP8266WebServer server(80);
String ntpServer = "de.pool.ntp.org";
String tzString = "CET-1CEST,M3.5.0/02,M10.5.0/03";

// ================= SETTINGS =================
struct Settings {
  int brightness;
  int nightBrightness;
  bool autoNight;
  int nightStart;
  int nightEnd;
  uint32_t color;
  uint32_t bgColor;
  bool backgroundMode;
  char ntpServer[64];
  char tzString[64];
  bool nightBackgroundDisabled; // Neu
} settings;

// ================= VAR =================
uint32_t currentColor = 0x00FFFF;
uint32_t lastColor = 0x00FFFF;
uint32_t bgColor = 0x000000;
bool backgroundMode = false;
bool nightBackgroundDisabled = false; // Neu
bool forceInstant = false;
bool updateNeeded = true;
int brightness = 120;
int nightBrightness = 10;
bool autoNight = true;
int nightStart = 22;
int nightEnd = 6;
uint32_t targetBuffer[NUMPIXELS];
uint32_t currentBuffer[NUMPIXELS];
int lastMinute = -1;
int lastH = -1;

// ================= EEPROM =================
void saveSettings(){
  settings.brightness = brightness;
  settings.nightBrightness = nightBrightness;
  settings.autoNight = autoNight;
  settings.nightStart = nightStart;
  settings.nightEnd = nightEnd;
  settings.color = currentColor;
  settings.bgColor = bgColor;
  settings.backgroundMode = backgroundMode;
  settings.nightBackgroundDisabled = nightBackgroundDisabled; // Neu
  strncpy(settings.ntpServer, ntpServer.c_str(), sizeof(settings.ntpServer));
  strncpy(settings.tzString, tzString.c_str(), sizeof(settings.tzString));
  EEPROM.put(0, settings);
  EEPROM.commit();
}

void loadSettings(){
  EEPROM.begin(512);
  EEPROM.get(0, settings);
  if(settings.brightness == 0 || settings.brightness == 255){ 
    brightness = 120;
    nightBrightness = 10;
    autoNight = true;
    nightStart = 22;
    nightEnd = 6;
    currentColor = 0x00FFFF;
    bgColor = 0x000000;
    backgroundMode = false;
    nightBackgroundDisabled = false;
    ntpServer = "de.pool.ntp.org";
    tzString = "CET-1CEST,M3.5.0/02,M10.5.0/03";
  } else {
    brightness = settings.brightness;
    nightBrightness = settings.nightBrightness;
    autoNight = settings.autoNight;
    nightStart = settings.nightStart;
    nightEnd = settings.nightEnd;
    currentColor = settings.color;
    bgColor = settings.bgColor;
    backgroundMode = settings.backgroundMode;
    nightBackgroundDisabled = settings.nightBackgroundDisabled;
    ntpServer = String(settings.ntpServer);
    tzString = String(settings.tzString);
  }
  configTime(tzString.c_str(), ntpServer.c_str());
}

// ================= HELFER =================
bool isNightTime(struct tm *t){
  if(!autoNight) return false;
  int h = t->tm_hour;
  if(nightStart > nightEnd){
    return (h >= nightStart || h < nightEnd);
  } else {
    return (h >= nightStart && h < nightEnd);
  }
}

void setRange(int a,int b){
  for(int i=a;i<=b;i++) targetBuffer[i]=currentColor;
}

uint32_t blend(uint32_t c1, uint32_t c2, float t){
  uint8_t r1=(c1>>16)&255, g1=(c1>>8)&255, b1=c1&255;
  uint8_t r2=(c2>>16)&255, g2=(c2>>8)&255, b2=c2&255;
  return pixels.Color(r1+(r2-r1)*t, g1+(g2-g1)*t, b1+(b2-b1)*t);
}

uint8_t gamma8(uint8_t x) {
  if (x == 0) return 0;
  // Verwende einen leicht angepassten Exponenten für den Nachtmodus
  float f = (float)x / 255.0;
  return (uint8_t)(pow(f, 2.0) * 255.0 + 0.5); 
}

uint32_t applyGammaBrightness(uint32_t c, float brightnessFactor) {
  // Wenn der Faktor extrem klein ist, erzwingen wir eine Mindesthelligkeit für aktive LEDs
  if (brightnessFactor < 0.01 && brightnessFactor > 0) brightnessFactor = 0.01;

  uint8_t r = (uint8_t)(((c >> 16) & 255) * brightnessFactor);
  uint8_t g = (uint8_t)(((c >> 8) & 255) * brightnessFactor);
  uint8_t b = (uint8_t)((c & 255) * brightnessFactor);

  return pixels.Color(gamma8(r), gamma8(g), gamma8(b));
}

int getBrightness(struct tm *t){
  return isNightTime(t) ? nightBrightness : brightness;
}

void showInstant(){
  time_t now = time(nullptr);
  struct tm *t = localtime(&now);
  float b = getBrightness(t) / 255.0;
  for(int i=0;i<NUMPIXELS;i++){
    pixels.setPixelColor(i, applyGammaBrightness(targetBuffer[i], b));
  }
  pixels.show();
}

void fadeToTargetSmooth(int steps = 30, int delayMs = 15) {
  time_t now = time(nullptr);
  struct tm *t = localtime(&now);
  float bTarget = getBrightness(t) / 255.0;
  
  // Im Nachtmodus faden wir langsamer (mehr Schritte), um das Springen zu kaschieren
  if (isNightTime(t)) {
    steps = 60; 
    delayMs = 25;
  }

  for (int s = 0; s <= steps; s++) {
    float tBlend = (float)s / steps;
    for (int i = 0; i < NUMPIXELS; i++) {
      uint32_t c = blend(currentBuffer[i], targetBuffer[i], tBlend);
      pixels.setPixelColor(i, applyGammaBrightness(c, bTarget));
    }
    pixels.show();
    yield(); // Verhindert WDT-Reset beim ESP8266
    delay(delayMs);
  }
}

// ================= WORTUHR LOGIK =================
void word_ES(){ setRange(0,1); } void word_IST(){ setRange(3,5); }
void word_FUENF_M(){ setRange(7,10); } void word_ZEHN_M(){ setRange(18,20); }
void word_ZWANZIG(){ setRange(11,17); } void word_VIERTEL(){ setRange(22,28); }
void word_VOR(){ setRange(30,32); } void word_NACH(){ setRange(40,43); }
void word_HALB(){ setRange(33,36); } void word_EINS(){ setRange(62,65); }
void word_EIN(){ setRange(62,64); } void word_ZWEI(){ setRange(55,58); }
void word_DREI(){ setRange(66,69); } void word_VIER(){ setRange(73,76); }
void word_FUENF_H(){ setRange(51,54); } void word_SECHS(){ setRange(83,87); }
void word_SIEBEN(){ setRange(88,93); } void word_ACHT(){ setRange(77,80); }
void word_NEUN(){ setRange(44,47); } void word_ZEHN_H(){ setRange(106,109); }
void word_ELF(){ setRange(103,105); } void word_ZWOELF(){ setRange(94,98); }
void word_UHR(){ setRange(99,101); }

String getTimeText(int h, int m){
  int m5=(m/5)*5; if(m5>=25) h++;
  String arr[]={"Zwölf","Eins","Zwei","Drei","Vier","Fünf","Sechs","Sieben","Acht","Neun","Zehn","Elf"};
  String hT = arr[h%12]; if(m5==0 && h%12==1) hT = "ein";
  String t = "Es ist ";
  switch(m5){
    case 0: t+= hT + " Uhr"; break;
    case 5: t+= "fünf nach " + arr[h%12]; break;
    case 10: t+= "zehn nach " + arr[h%12]; break;
    case 15: t+= "viertel nach " + arr[h%12]; break;
    case 20: t+= "zwanzig nach " + arr[h%12]; break;
    case 25: t+= "fünf vor halb " + arr[h%12]; break;
    case 30: t+= "halb " + arr[h%12]; break;
    case 35: t+= "fünf nach halb " + arr[h%12]; break;
    case 40: t+= "zwanzig vor " + arr[h%12]; break;
    case 45: t+= "viertel vor " + arr[h%12]; break;
    case 50: t+= "zehn vor " + arr[h%12]; break;
    case 55: t+= "fünf vor " + arr[h%12]; break;
  }
  return t;
}

void showTimeWords(int h, int m){
  time_t now = time(nullptr);
  struct tm *t = localtime(&now);
  
  for(int i=0;i<NUMPIXELS;i++) currentBuffer[i] = pixels.getPixelColor(i);
  
  // HG Logik mit Nacht-Aus-Option
  uint32_t activeBg = 0;
  if(backgroundMode) {
    if(nightBackgroundDisabled && isNightTime(t)) {
      activeBg = 0;
    } else {
      activeBg = bgColor;
    }
  }
  for(int i=0;i<NUMPIXELS;i++) targetBuffer[i] = activeBg;
  
  word_ES(); word_IST();
  int m5 = (m/5)*5; bool full = (m5 == 0); if(m5 >= 25) h++;
  switch(m5){
    case 0:  word_UHR(); break;
    case 5:  word_FUENF_M(); word_NACH(); break;
    case 10: word_ZEHN_M(); word_NACH(); break;
    case 15: word_VIERTEL(); word_NACH(); break;
    case 20: word_ZWANZIG(); word_NACH(); break;
    case 25: word_FUENF_M(); word_VOR(); word_HALB(); break;
    case 30: word_HALB(); break;
    case 35: word_FUENF_M(); word_NACH(); word_HALB(); break;
    case 40: word_ZWANZIG(); word_VOR(); break;
    case 45: word_VIERTEL(); word_VOR(); break;
    case 50: word_ZEHN_M(); word_VOR(); break;
    case 55: word_FUENF_M(); word_VOR(); break;
  }
  int hh = h % 12;
  if(hh == 1 && full) word_EIN();
  else if(hh == 1) word_EINS(); else if(hh == 2) word_ZWEI(); else if(hh == 3) word_DREI();
  else if(hh == 4) word_VIER(); else if(hh == 5) word_FUENF_H(); else if(hh == 6) word_SECHS();
  else if(hh == 7) word_SIEBEN(); else if(hh == 8) word_ACHT(); else if(hh == 9) word_NEUN();
  else if(hh == 10) word_ZEHN_H(); else if(hh == 11) word_ELF(); else word_ZWOELF();

  if(forceInstant){ showInstant(); forceInstant = false; }
  else { fadeToTargetSmooth(20, 10); }
}

// ================= WEB HANDLERS =================
void handleStatus() {
  time_t now = time(nullptr);
  struct tm *t = localtime(&now);
  char hexCol[10], hexBg[10];
  snprintf(hexCol, sizeof(hexCol), "#%06X", currentColor);
  snprintf(hexBg, sizeof(hexBg), "#%06X", bgColor);
  String ntpStat = (now > 100000) ? "OK" : "SYNC...";
  String json = "{";
  json += "\"txt\":\"" + getTimeText(t->tm_hour, t->tm_min) + "\",";
  json += "\"ip\":\"" + WiFi.localIP().toString() + "\",";
  json += "\"ntp\":\"" + ntpStat + "\",";
  json += "\"ntpServer\":\"" + ntpServer + "\",";
  json += "\"tzString\":\"" + tzString + "\",";
  json += "\"brightness\":" + String(brightness) + ",";
  json += "\"nightBrightness\":" + String(nightBrightness) + ",";
  json += "\"color\":\"" + String(hexCol) + "\",";
  json += "\"bgColor\":\"" + String(hexBg) + "\",";
  json += "\"night\":" + String(autoNight ? 1 : 0) + ",";
  json += "\"nightStart\":" + String(nightStart) + ",";
  json += "\"nightEnd\":" + String(nightEnd) + ",";
  json += "\"bg\":" + String(backgroundMode ? 1 : 0) + ",";
  json += "\"nightBgOff\":" + String(nightBackgroundDisabled ? 1 : 0);
  json += "}";
  server.send(200, "application/json", json);
}

void handleRoot(){
  String html = R"rawliteral(
  <html><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1'>
  <style>
    body{font-family:Arial,sans-serif; background:#fff; margin:0; color:#333; display:flex; flex-direction:column; align-items:center}
    .card{width:90%; max-width:400px; margin:10px; padding:15px; border-radius:12px; background:#f2f2f2; box-shadow: 0 2px 5px rgba(0,0,0,0.1)}
    .big{font-size:26px; text-align:center; font-weight:bold; color:#0055ff}
    input[type='range'], input[type='color'], input[type='text']{width:100%; margin-bottom:10px}
    button{width:100%; padding:12px; border-radius:8px; background:#0055ff; color:#fff; border:none; cursor:pointer}
    label{font-weight:bold; display:block; margin-bottom:5px}
  </style></head><body>
  <div class='card big' id='txt'>Lade...</div>
  <div class='card' id='status'>Verbinde...</div>
  <div class='card'><label>Farbe Worte</label><div>&nbsp;</div><input type='color' id='col' onchange="f('/color?c='+this.value.substring(1))">
    <label>Helligkeit</label><input type='range' id='br' min='0' max='255' oninput="f('/brightness?val='+this.value)"></div>
    <div class='card'><input type='checkbox' id='bg' onchange="f('/bg?val='+(this.checked?1:0))"> <b>Hintergrund</b><br><div>&nbsp;</div>
    <input type='color' id='bgcol' onchange="f('/bgcolor?c='+this.value.substring(1))"></div>
  <div class='card'><input type='checkbox' id='night' onchange="f('/night?val='+(this.checked?1:0))"> <b>Nachtmodus</b><br> <div>&nbsp;</div>
    Von: <input type='number' id='nS' style='width:50px' oninput="f('/nightStart?val='+this.value)"> Uhr 
    Bis: <input type='number' id='nE' style='width:50px' oninput="f('/nightEnd?val='+this.value)"> Uhr<br><div>&nbsp;</div>
    <label>Nacht-Helligkeit</label><input type='range' id='nbr' min='0' max='255' oninput="f('/nightBrightness?val='+this.value)">
    <br><input type='checkbox' id='nightBgOff' onchange="f('/nightBgOff?val='+(this.checked?1:0))"> HG in Nacht aus</div>
  
  <div class='card'><label>NTP Server:</label><input type='text' id='ntpS' onchange="f('/setNTP?val='+this.value)">
    <label>Zeitzone:</label><input type='text' id='tzS' onchange="f('/setTZ?val='+this.value)"></div>
  <div class='card'><button onclick="f('/save');alert('Gespeichert!')">EEPROM Speichern</button></div>
  <script>
    function f(u){fetch(u)}
    function u(){fetch('/status').then(r=>r.json()).then(d=>{
      document.getElementById('txt').innerText=d.txt;
      document.getElementById('status').innerText="IP: "+d.ip+" | NTP: "+d.ntp;
      const s=(i,v,c=false)=>{const e=document.getElementById(i);if(e&&document.activeElement!==e){if(c)e.checked=(v==1);else e.value=v;}};
      s('col',d.color);s('br',d.brightness);s('night',d.night,true);s('nS',d.nightStart);s('nE',d.nightEnd);
      s('nbr',d.nightBrightness);s('bg',d.bg,true);s('bgcol',d.bgColor);s('ntpS',d.ntpServer);s('tzS',d.tzString);
      s('nightBgOff',d.nightBgOff,true);
    })}
    setInterval(u, 2500); u();
  </script></body></html>)rawliteral";
  server.send(200, "text/html", html);
}

void handleColor(){ if(server.hasArg("c")){ currentColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant=true; showTimeWords(lastH, lastMinute); } server.send(200); }
void handleBGColor(){ if(server.hasArg("c")){ bgColor=strtoul(server.arg("c").c_str(),NULL,16); forceInstant=true; updateNeeded=true; } server.send(200); }
void handleBrightness(){ brightness=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNight(){ autoNight=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNightStart(){ nightStart=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNightEnd(){ nightEnd=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNightBrightness(){ nightBrightness=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNightBgOff(){ nightBackgroundDisabled=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleBG(){ backgroundMode=server.arg("val").toInt(); forceInstant=true; updateNeeded=true; server.send(200); }
void handleNTP(){ ntpServer=server.arg("val"); configTime(tzString.c_str(), ntpServer.c_str()); server.send(200); }
void handleTZ(){ tzString=server.arg("val"); configTime(tzString.c_str(), ntpServer.c_str()); server.send(200); }
void handleSave(){ saveSettings(); server.send(200); }

// ================= SETUP & LOOP =================
void setup(){
  Serial.begin(115200);
  WiFiManager wm; wm.autoConnect("Wortuhr");
  loadSettings();
  server.on("/", handleRoot);
  server.on("/status", handleStatus);
  server.on("/color", handleColor);
  server.on("/bgcolor", handleBGColor);
  server.on("/brightness", handleBrightness);
  server.on("/night", handleNight);
  server.on("/nightStart", handleNightStart);
  server.on("/nightEnd", handleNightEnd);
  server.on("/nightBrightness", handleNightBrightness);
  server.on("/nightBgOff", handleNightBgOff);
  server.on("/bg", handleBG);
  server.on("/setNTP", handleNTP);
  server.on("/setTZ", handleTZ);
  server.on("/save", handleSave);
  server.begin();
  pixels.begin();
}

void loop(){
  server.handleClient();

  time_t now = time(nullptr);
  struct tm *t = localtime(&now);

  if(t) {
    int currentM5 = (t->tm_min / 5) * 5;
    if(currentM5 != lastMinute || t->tm_hour != lastH){
      lastMinute = currentM5;
      lastH = t->tm_hour;
      updateNeeded = true;
    }
  }

  if(updateNeeded){
    showTimeWords(lastH, (t ? t->tm_min : 0));
    updateNeeded = false;
  }
}