//Libreria per la gestione di NeoPixel
#include <Adafruit_NeoPixel.h>
#include <RTClib.h>
#include <SPI.h>
// Definizioni
//Pulsante On/Off
#define BTN_ON_OFF_PIN 7
//Pulsante per aumentare la luminosità
#define BTN_DIM_UP_PIN 3
//Pulsante per diminuire la luminosità
#define BTN_DIM_DOWN_PIN 4
//Pulsante per cambiare colore
#define BTN_COLOR_CHANGE_PIN 5
//Pulsante stato
#define BTN_STATE 6
//Pin Dati per NeoPixel
#define HOUR0_PIN 2
#define HOUR1_PIN 8
#define MIN0_PIN 9
#define MIN1_PIN 10
#define SEPARATOR_PIN 11
//Numero di LED della striscia
#define NUMPIXELS 28
#define SEPARATOR_PIXELS 2
//Fotoresistenza
#define PHOTORESISTOR_PIN A3
// ========== MAPPATURE LED PER DISPLAY 7 SEGMENTI (PROGMEM) ==========
// Queste mappature sono memorizzate nella Flash invece che nella RAM
const int ZERO[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 24, 25, 26, 27};
const int ZERO_SIZE = 24;
const int ONE[] PROGMEM = {4, 5, 6, 7, 8, 9, 10, 11};
const int ONE_SIZE = 8;
const int TWO[] PROGMEM = {0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27};
const int TWO_SIZE = 20;
const int THREE[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 21, 22, 23};
const int THREE_SIZE = 20;
const int FOUR[] PROGMEM = {4, 5, 6, 7, 8, 9, 10, 11, 16, 17, 18, 19, 20, 21, 22, 23};
const int FOUR_SIZE = 16;
const int FIVE[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
const int FIVE_SIZE = 20;
const int SIX[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
const int SIX_SIZE = 20;
const int SEVEN[] PROGMEM = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
const int SEVEN_SIZE = 12;
const int EIGHT[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27};
const int EIGHT_SIZE = 28;
const int NINE[] PROGMEM = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
const int NINE_SIZE = 24;
// ========== ARRAY DI PUNTATORI PER ACCESSO SEMPLIFICATO ==========
const int* const DIGIT_ARRAYS[] PROGMEM = {
    ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE
};
const int DIGIT_SIZES[] PROGMEM = {
    ZERO_SIZE, ONE_SIZE, TWO_SIZE, THREE_SIZE, FOUR_SIZE,
    FIVE_SIZE, SIX_SIZE, SEVEN_SIZE, EIGHT_SIZE, NINE_SIZE
};
// ========== FUNZIONI HELPER PER LEGGERE DA PROGMEM ==========
// Ottiene la dimensione dell'array per una cifra
int getDigitSize(int digit) {
    if (digit >= 0 && digit <= 9) {
        return pgm_read_word(&DIGIT_SIZES[digit]);
    }
    return ZERO_SIZE; // Default
}
//Istanzio oggetto NeoPixel
Adafruit_NeoPixel hour0 = Adafruit_NeoPixel(NUMPIXELS, HOUR0_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel hour1 = Adafruit_NeoPixel(NUMPIXELS, HOUR1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel minute0 = Adafruit_NeoPixel(NUMPIXELS, MIN0_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel minute1 = Adafruit_NeoPixel(NUMPIXELS, MIN1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel separator = Adafruit_NeoPixel(SEPARATOR_PIXELS, SEPARATOR_PIN, NEO_GRB + NEO_KHZ800);
//Definizione colori. I valori dei colori sono calcolati tenendo conton della luminosità
//La luminosità puà essere impostata su 5 valori, lo step fra l'uno e l'altro
//è pari a 51, partendo da 51 fino a 255, parto dalla metà che è pari a 153
//Valori validi per prima accensione dopo inseriento spina
struct Color {
  uint8_t r;
  uint8_t g;
  uint8_t b;
};
// Definizione dei colori base
const Color colorList[] = {
  {153, 153, 153},   // WHITE
  {153, 153, 138},   // WARM_WHITE
  {153, 0, 0},       // RED
  {0, 153, 0},       // GREEN
  {153, 153, 0},     // YELLOW
  {0, 76, 153}       // BLUE
};
const int numColors = sizeof(colorList) / sizeof(Color);
int currentColorIndex = 0;
// Stato del pulsante state
int state_ButtonState = 0;
// Ultimo stato del pulsante state
int state_LastButtonState = 0;
// State variable
static uint8_t state = 1;
//Variabile timer per pressione lunga
unsigned long t_pulsante = 0;
//Step di variazione della luminosità
unsigned int step = 51;
//Variabile di appoggio con il valore attuale dello step di luminosità
//per prima accensione dopo inserimento spina
unsigned int lum_corrente = 153;
//Variabile di buffer con il valore di luminosità attivo
//allo spegnimento della lampada
unsigned int buffer_luminosita = 0;
//Colore di partenza solo per prima accensione dopo inserimento spina
uint32_t colore = colorList[0];
//Variabile di buffer con il colore attivo allo spegnimento della lampada
//Colori bufferColore = EMPTY;
uint32_t bufferColore = hour0.Color(0, 0, 0);
//Ritardo per la funzione anti rimbalzo
int debounceDelay = 50;
//Variabile di verifica striscia On o Off
bool on = false;
//Variabili che contengono l'ora ed i minuti, vengono aggiornati dal modulo RTC
String ora = "00";
String minuti = "00";
//Create RTC_DS3231 instance
RTC_DS3231 rtc;
//Date & time variable
DateTime dateTime;
//Variables to store time characters to be printed
char strTime[7] = "    ";
String timeLine = String();
//Variabile controllo passaggio minuti
int lastMinute = -1;
//Variabile controllo passaggio secondi
int lastSecond = -1;
//Variabile booleana che indica se i separatori sono accesi o spenti
bool SwitchedOn = false;
void setup()
{
  hour0.begin();
  hour1.begin();
  minute0.begin();
  minute1.begin();
  separator.begin();
  Serial.begin(9600);
  //start I2C communication
  if (!rtc.begin()) {
    Serial.println("Impossibile trovare il modulo RTC");
    Serial.flush();
    while(1) delay(10);
  }
  //Da eliminare
  //rtc.adjust(DateTime(2025, 3, 23, 20, 30, 0));
  //getTime();
  //set initial Date-Time
  if (rtc.lostPower()) {
    Serial.println("Imposto l'ora....");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    rtc.adjust(DateTime(2025, 3, 23, 20, 50, 0));
    //rtc.adjust(strTime);
  }
  else Serial.println("Il modulo Rtc è in esecuzione....");
  pinMode(BTN_STATE, INPUT);
  Serial.println(luxmeter());
}
void loop()
{ 
  setState();
  
  switch (state)
  {
    case 1:
        getTime();
        Show();
      break;
    case 2:
        Dim_up();
        Dim_down();
        //getHour();
      break;
    case 3:
        Change_color();
        //getMinute();
      break;
    default:
      break;
  }
}
//========================== Funzioni =================================
//Funzione di gestione el tasto On/Off
void On_Off()
{
  //Se premo il pulsante On/Off e la striscia è spenta
  if (debounce(BTN_ON_OFF_PIN)==HIGH && !on) {
    //Memorizzo il valore millis
    t_pulsante = millis();
    //Verifico se il pulsante On/Off rimane premuto
    while(debounce(BTN_ON_OFF_PIN)==HIGH){
      //Se rimane premuto per il tempo prefissato
      if (millis() - t_pulsante >=1000) {
        //Controllo che il buffer colore non sia vuoto,
        //se non lo è, accendo i LED con quel valore
        if (bufferColore != hour0.Color(0, 0, 0)) {
			    ShowHour(ora, bufferColore);
          ShowMinute(minuti, bufferColore);
          //Assegno il flag di striscia accesa
          on = true;
        } else {
          //Assegno il colore tutti i LED della striscia
          bufferColore = colore;
          colore = bufferColore;
		      ShowHour(ora, bufferColore);
          ShowMinute(minuti, bufferColore);
          //Assegno il flag di striscia accesa
          on = true;
        }
      }
    }
  }
  //Se, invece, premo il pulsante On/Off e la striscia è accesa
  else if (debounce(BTN_ON_OFF_PIN)==HIGH && on) {
      //Salvo il valore di luminosità attivo
      buffer_luminosita = lum_corrente;
      //Salvo il colore attivo
      bufferColore = colore;
      //Spengo la striscia
      Clear_LED();
      //Assegno il flag di striscia spenta
      on = false;
  }
}
// Ottiene un LED specifico dall'array di una cifra
int getDigitLED(int digit, int index) {
    const int* array;
    switch(digit) {
        case 0: array = ZERO; break;
        case 1: array = ONE; break;
        case 2: array = TWO; break;
        case 3: array = THREE; break;
        case 4: array = FOUR; break;
        case 5: array = FIVE; break;
        case 6: array = SIX; break;
        case 7: array = SEVEN; break;
        case 8: array = EIGHT; break;
        case 9: array = NINE; break;
        default: array = ZERO; break;
    }
    return pgm_read_word(&array[index]);
}
//Funzione che mostra l'orario
void Show() {
  //Controllo che il buffer colore non sia vuoto,
  //se non lo è, accendo i LED con quel valore
  if (colore != hour0.Color(0, 0, 0)) {
    ShowHour(ora, colore);
    ShowMinute(minuti, colore);
  }
}
//Funzione di spegnimento della striscia
void Clear_LED() {
  //Assegno il colore tutti i LED della striscia
  for(int i = 0; i < NUMPIXELS; i++){
    hour0.setPixelColor(i, hour0.Color(0, 0, 0));
    hour1.setPixelColor(i, hour0.Color(0, 0, 0));
    minute0.setPixelColor(i, hour0.Color(0, 0, 0));
    minute1.setPixelColor(i, hour0.Color(0, 0, 0));
    //Spengo la striscia (assegnando colore 0,0,0 si spegne la striscia)
    hour0.show();
    hour1.show();
    minute0.show();
    minute1.show();
  }
}
void setState() {
  state_ButtonState = digitalRead(BTN_STATE);
  if(state_ButtonState != state_LastButtonState) {
    if(state_ButtonState == HIGH) {
      if(state == 4){
        state = 1;
      } else {
        state = state + 1;
      }
      Serial.println(state);
    }
    // Delay per evitare il bouncing
    delay(300);
  }
  state_LastButtonState = state_ButtonState;
}
uint32_t applyBrightness(Color base, uint8_t brightness) {
  return hour0.Color(
    map(base.r, 0, 255, 0, brightness),
    map(base.g, 0, 255, 0, brightness),
    map(base.b, 0, 255, 0, brightness)
  );
}
void updateClockDisplay() {
  uint32_t adjustedColor = applyBrightness(colorList[currentColorIndex], lum_corrente);
  ShowHour(ora, adjustedColor);
  ShowMinute(minuti, adjustedColor);
}
//Funzione per aumentare la luminosità
void Dim_up() {
  if (debounce(BTN_DIM_UP_PIN) == HIGH && lum_corrente < 255) {
    lum_corrente = min(255, lum_corrente + step);
    updateClockDisplay();
  }
}
//Funzione per diminuire la luminosità
void Dim_down() {
  if (debounce(BTN_DIM_DOWN_PIN) == HIGH && lum_corrente > 51) {
    lum_corrente = max(51, lum_corrente - step);
    updateClockDisplay();
  }
}
//Funzione per il cambio colore
void Change_color() {
  if (debounce(BTN_COLOR_CHANGE_PIN) == HIGH) {
    currentColorIndex = (currentColorIndex + 1) % numColors;
    updateClockDisplay();
  }
}
//Funzione antirimbalzo
boolean debounce(int pin) {
  boolean state;
  boolean previousState;
  previousState = digitalRead(pin);
  for(int counter=0; counter < debounceDelay; counter++) {
    delay(1);
    state = digitalRead(pin);
    if( state != previousState) {
      counter = 0;
      previousState = state; }
  }
  return state;
}