/*
 * ESP32 wiring (consigliato):
 * DIN -> GPIO23
 * CS  -> GPIO5
 * CLK -> GPIO18
 *
 * Evita GPIO34..39 (input-only) e i pin di boot (0,2,12,15).
 */
#include "Arduino.h"
#if defined(ARDUINO_ARCH_ESP32)
  #include <WiFi.h>
  #include <WebServer.h>
  #define WIFI_HOST_SET(host) WiFi.setHostname(host)
#else
  #include <ESP8266WiFi.h>
  #include <ESP8266WebServer.h>
  #define WIFI_HOST_SET(host) WiFi.hostname(host)
#endif
#include <DNSServer.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <time.h>
// ------- PROTOTIPI -------
void saveConfigCallback();
// ------- DISPLAY / MAX7219 -------
#define MAX_DIGITS 16
byte dig[MAX_DIGITS] = {0};
byte digold[MAX_DIGITS] = {0};
byte digtrans[MAX_DIGITS] = {0};
bool is12HFormat = false;
bool isPM = false;
int updCnt = 0;
int dots = 0;
long dotTime = 0;
long clkTime = 0;
int dx = 0;
int dy = 0;
byte del = 0;
int h, m, s;
int day, month, year, dayOfWeek;
int adjustedHour;
String clockHostname = "NTP-Clock";
WiFiManager wifiManager;
// Numero di moduli MAX7219 a 8x8
#define NUM_MAX 4
// --- PIN: scegli pin sicuri per ESP32 ---
#if defined(ARDUINO_ARCH_ESP32)
  #define DIN_PIN 23
  #define CS_PIN  5
  #define CLK_PIN 18
#else
  // mappatura originale ESP8266: D7=13, D6=12, D5=14
  #define DIN_PIN 13
  #define CS_PIN  12
  #define CLK_PIN 14
#endif
#include "max7219.h"
#include "fonts.h"
long lastDataDisplayTime = 0;
bool showingTime = true;
// ============================= FONT/SCROLL =============================
int showChar(char ch, const uint8_t* data) {
  int len = pgm_read_byte(data);
  int i, w = pgm_read_byte(data + 1 + ch * len);
  for (i = 0; i < w; i++)
    scr[NUM_MAX * 8 + i] = pgm_read_byte(data + 1 + ch * len + 1 + i);
  scr[NUM_MAX * 8 + i] = 0;
  return w;
}
void printCharWithShift(unsigned char c, int shiftDelay) {
  if (c < ' ' || c > '~' + 25) return;
  c -= 32;
  int w = showChar(c, font);
  for (int i = 0; i < w + 1; i++) {
    delay(shiftDelay);
    scrollLeft();
    refreshAll();
  }
}
void printStringWithShift(const char* s, int shiftDelay) {
  while (*s) {
    printCharWithShift(*s, shiftDelay);
    s++;
  }
}
// ============================= ORA LOCALE (SNTP) =============================
// Timezone per Italia con cambio automatico:
// CET-1CEST,M3.5.0/2,M10.5.0/3
//   - CET base UTC+1
//   - CEST da ultima domenica di marzo 02:00
//   - a ultima domenica di ottobre 03:00
static const char* TZ_ROME = "CET-1CEST,M3.5.0/2,M10.5.0/3";
bool syncTimeOnce() {
  // avvia SNTP con TZ
#if defined(ARDUINO_ARCH_ESP32)
  configTzTime(TZ_ROME, "pool.ntp.org", "time.nist.gov");
#else
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  setenv("TZ", TZ_ROME, 1);
  tzset();
#endif
  // aspetta che arrivi l’ora
  for (int i = 0; i < 50; i++) {
    time_t now = time(nullptr);
    if (now > 1700000000) { // ~2023-11-14: tempo valido
      return true;
    }
    delay(200);
  }
  return false;
}
void readNow() {
  time_t now = time(nullptr);
  struct tm tmNow;
#if defined(ARDUINO_ARCH_ESP32)
  localtime_r(&now, &tmNow);
#else
  tmNow = *localtime(&now);
#endif
  h = tmNow.tm_hour;
  m = tmNow.tm_min;
  s = tmNow.tm_sec;
  year  = tmNow.tm_year + 1900;
  month = tmNow.tm_mon + 1;
  day   = tmNow.tm_mday;
  dayOfWeek = tmNow.tm_wday; // 0=dom
}
// ============================= INTENSITÀ =============================
void setIntensity() {
  int intensityHour = h;
  if (intensityHour >= 22 || intensityHour < 6) {
    sendCmdAll(CMD_INTENSITY, 0);
  } else if ((intensityHour >= 7 && intensityHour <= 10) || (intensityHour >= 16 && intensityHour < 18)) {
    sendCmdAll(CMD_INTENSITY, 3);
  } else if (intensityHour >= 11 && intensityHour <= 15) {
    sendCmdAll(CMD_INTENSITY, 5);
  } else if (intensityHour >= 19 && intensityHour <= 22) {
    sendCmdAll(CMD_INTENSITY, 2);
  }
}
// ============================= RENDER OROLOGIO =============================
void showDigit(char ch, int col, const uint8_t* data) {
  if (dy < -8 || dy > 8) return;
  int len = pgm_read_byte(data);
  int w = pgm_read_byte(data + 1 + ch * len);
  int offset = (8 - w) / 2;
  col += dx + offset;
  for (int i = 0; i < w; i++)
    if (col + i >= 0 && col + i < 8 * NUM_MAX) {
      byte v = pgm_read_byte(data + 1 + ch * len + 1 + i);
      if (!dy) scr[col + i] = v;
      else scr[col + i] |= dy > 0 ? v >> dy : v << -dy;
    }
}
void setCol(int col, byte v) {
  if (dy < -8 || dy > 8) return;
  col += dx;
  if (col >= 0 && col < 8 * NUM_MAX)
    if (!dy) scr[col] = v;
    else scr[col] |= dy > 0 ? v >> dy : v << -dy;
}
void showAnimClock() {
  byte digPos[6] = { 0, 7, 17, 24, 34, 42 };
  int digHt = 12;
  int num = 6;
  if (del == 0) {
    del = digHt;
    for (int i = 0; i < num; i++) digold[i] = dig[i];
    dig[0] = h / 10;
    dig[1] = h % 10;
    dig[2] = m / 10;
    dig[3] = m % 10;
    dig[4] = s / 10;
    dig[5] = s % 10;
    for (int i = 0; i < num; i++) digtrans[i] = (dig[i] == digold[i]) ? 0 : digHt;
  } else {
    del--;
  }
  clr();
  for (int i = 0; i < num; i++) {
    if (digtrans[i] == 0) {
      dy = 0;
      showDigit(dig[i], digPos[i], dig6x8);
    } else {
      dy = digHt - digtrans[i];
      showDigit(digold[i], digPos[i], dig6x8);
      dy = -digtrans[i];
      showDigit(dig[i], digPos[i], dig6x8);
      digtrans[i]--;
    }
  }
  dy = 0;
  setCol(15, dots ? B01100110 : 0);
  setCol(16, dots ? B01100110 : 0);
  setCol(32, dots ? B01100110 : 0);
  setCol(33, dots ? B01100110 : 0);
  refreshAll();
  delay(30);
}
void displayDate() {
  byte digPos[4] = { 0, 7, 17, 24 };
  int digHt = 12;
  int num = 4;
  del = digHt;
  for (int i = 0; i < num; i++) digold[i] = dig[i];
  dig[0] = day / 10;
  dig[1] = day % 10;
  dig[2] = month / 10;
  dig[3] = month % 10;
  for (int i = 0; i < num; i++) digtrans[i] = (dig[i] == digold[i]) ? 0 : digHt;
  clr();
  for (int i = 0; i < num; i++) {
    if (digtrans[i] == 0) {
      dy = 0;
      showDigit(dig[i], digPos[i], dig6x8);
    } else {
      dy = digHt - digtrans[i];
      showDigit(digold[i], digPos[i], dig6x8);
      dy = -digtrans[i];
      showDigit(dig[i], digPos[i], dig6x8);
      digtrans[i]--;
    }
  }
  dy = 0;
  setCol(15, dots ? B00111100 : 0);
  setCol(16, dots ? B00111100 : 0);
  refreshAll();
  delay(30);
}
// ============================= SETUP/LOOP =============================
void setup() {
  Serial.begin(115200);
  EEPROM.begin(sizeof(float));
  wifiManager.setSaveParamsCallback(saveConfigCallback);
  WiFi.mode(WIFI_STA);
  WIFI_HOST_SET(clockHostname.c_str());
  initMAX7219();
  sendCmdAll(CMD_SHUTDOWN, 1);
  sendCmdAll(CMD_INTENSITY, 0);
  Serial.print("Connecting WiFi ");
  if (!wifiManager.autoConnect("NTP Clock Setup")) {
    Serial.println("Failed to connect and hit timeout");
    delay(3000);
    ESP.restart();
  }
  printStringWithShift("Connecting", 15);
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("MyIP: ");
  Serial.println(WiFi.localIP());
  String ipMsg = String("  MyIP: ") + WiFi.localIP().toString();
  printStringWithShift(ipMsg.c_str(), 15);
  delay(800);
  // Sincronizza ora con TZ Europa/Roma
  printStringWithShift("  Setting Time...", 15);
  if (!syncTimeOnce()) {
    printStringWithShift("  NTP FAIL", 20);
  }
  readNow(); // prima lettura
  clkTime = millis();
  lastDataDisplayTime = millis();
}
void saveConfigCallback() {
  EEPROM.commit();
}
void loop() {
  // aggiorna indicatore due punti
  if (millis() - dotTime > 500) {
    dotTime = millis();
    dots = !dots;
  }
  // ogni 20s "tick" fittizio per la tua logica di refresh
  if (millis() - clkTime > 20000) {
    clkTime = millis();
  }
  // leggi tempo corrente (locale con DST auto)
  readNow();
  setIntensity();
  // alterna ora/data
  if (millis() - lastDataDisplayTime >= 30000) {       // passa a data
    showingTime = false;
    lastDataDisplayTime = millis();
  } else if (millis() - lastDataDisplayTime >= 4000 && !showingTime) { // torna all'ora dopo 4s
    showingTime = true;
  }
  if (showingTime) {
    showAnimClock();
  } else {
    displayDate();
  }
}