#include <Arduino.h>
#include <TM1637Display.h>   // avishorp/TM1637 -> TM1637Display.h
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Keypad.h>
// ====================== PINOUT ======================
#define TM1_CLK 6
#define TM1_DIO 7
#define TM2_CLK 8
#define TM2_DIO 9
// MAX7219 (8x32 = 4 moduli 8x8 in cascata)
#define MAX_DIN 11
#define MAX_CLK 10
#define MAX_CS  12
// Se il testo è rovesciato/specchiato prova ICSTATION_HW
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
// Tastiera 4x4: R1..R4, C1..C4
const byte ROWS = 4, COLS = 4;
byte rowPins[ROWS] = {13, 14, 15, 16};      // righe
byte colPins[COLS] = {17, 18, 21, 20};      // colonne (QUI 20 al posto di 22)
char keys[ROWS][COLS] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};
// ====================== OGGETTI ======================
TM1637Display dispP1(TM1_CLK, TM1_DIO);
TM1637Display dispP2(TM2_CLK, TM2_DIO);
// Parola con SPI "software" sui pin definiti sopra
MD_Parola parola = MD_Parola(HARDWARE_TYPE, MAX_DIN, MAX_CLK, MAX_CS, MAX_DEVICES);
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// ====================== STATI & MODE ======================
enum GameMode : uint8_t { MODE_NONE=0, MODE_501, MODE_301, MODE_HISCORE };
enum Phase : uint8_t    { PH_MENU=0, PH_ASK_PLAYERS, PH_RUNNING, PH_GAME_OVER };
Phase    phase = PH_MENU;
GameMode gameMode = MODE_NONE;
int numPlayers = 0;            // 1 o 2
bool player1Turn = true;       // chi sta giocando adesso
bool startP1_501   = true;     // alterna chi parte ad ogni partita 501
bool startP1_301   = true;     // alterna chi parte ad ogni partita 301
bool startP1_HIS   = true;     // alterna chi parte ad ogni partita hiscore
// Punteggi/turni
int scoreP1 = 0, scoreP2 = 0;  // x01 = residuo; HiScore = totale
int startScore = 0;            // 501 o 301
int inputScore = 0;
// Hi-Score: 7 turni per giocatore
const int HISCORE_TURNS = 7;
int turnsP1 = 0, turnsP2 = 0;  // turni già giocati da ciascun giocatore
// Undo (1 livello)
struct Move {
  bool valid;
  GameMode mode;
  bool p1TurnBefore;  // chi aveva il turno prima dell'applicazione
  int prevScoreP1;
  int prevScoreP2;
  int prevTurnsP1;
  int prevTurnsP2;
  int applied;        // punti applicati
};
Move lastMove = {false, MODE_NONE, true, 0, 0, 0, 0, 0};
// ====================== UTILITY: MAX7219 ======================
void showMessage(const String& msg, uint16_t speed = 40, uint8_t pause = 0) {
  parola.displayClear();
  parola.displayReset();
  parola.displayText((char*)msg.c_str(), PA_CENTER, speed, pause, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
  while (!parola.displayAnimate()) { /* anima finché finito */ }
}
void showStatic(const String& msg) {
  parola.displayClear();
  parola.displayReset();
  parola.displayText((char*)msg.c_str(), PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
  parola.displayAnimate(); // applica frame
}
void showTurnBanner() {
  // Per HiScore mostriamo anche Tn/7
  if (gameMode == MODE_HISCORE) {
    int tNow = player1Turn ? (turnsP1+1) : (turnsP2+1);
    tNow = constrain(tNow, 1, HISCORE_TURNS);
    String who = player1Turn ? "P1 " : "P2 ";
    String msg = who + "T" + String(tNow) + "/" + String(HISCORE_TURNS);
    showStatic(msg);
  } else {
    showStatic(player1Turn ? "P1" : "P2");
  }
}
// ====================== UTILITY: TM1637 ======================
void show4(TM1637Display &d, int v) {
  // Visualizza valore intero, blank leading zeros
  d.showNumberDecEx(constrain(v, 0, 9999), 0, true, 4, 0);
}
void update7seg() {
  show4(dispP1, scoreP1);
  if (numPlayers >= 2) show4(dispP2, scoreP2);
  else                 show4(dispP2, 0);
}
// ====================== RESET & MENU ======================
void toMenu() {
  phase = PH_MENU;
  gameMode = MODE_NONE;
  numPlayers = 0;
  inputScore = 0;
  turnsP1 = turnsP2 = 0;
  scoreP1 = scoreP2 = 0;
  lastMove.valid = false;
  update7seg();
  showMessage("SELEZIONA GIOCO  A=501  B=301  C=HIS");
  delay(200);
  showStatic("A B C");
}
// ====================== AVVIO GIOCO ======================
void askPlayers() {
  phase = PH_ASK_PLAYERS;
  inputScore = 0;
  showMessage("1 O 2 GIOC?");
  showStatic("1 o 2");
}
void startGame(GameMode mode, bool p1Starts) {
  gameMode = mode;
  player1Turn = p1Starts;
  inputScore = 0;
  lastMove.valid = false;
  if (mode == MODE_501 || mode == MODE_301) {
    startScore = (mode == MODE_501) ? 501 : 301;
    scoreP1 = startScore;
    scoreP2 = startScore;
    turnsP1 = turnsP2 = 0; // non usati nei x01 ma azzeriamo
    update7seg();
    showMessage((mode == MODE_501) ? "501" : "301");
  } else { // Hi-Score
    scoreP1 = scoreP2 = 0;
    turnsP1 = turnsP2 = 0;
    update7seg();
    showMessage("HI SCORE 7 TURNI");
  }
  delay(200);
  phase = PH_RUNNING;
  showTurnBanner();
}
// ====================== APPLICA PUNTEGGIO ======================
void applyScore(int pts) {
  if (pts <= 0 || pts > 180) { showMessage("ERR"); return; }
  // Salva stato per UNDO
  lastMove = {
    true,
    gameMode,
    player1Turn,
    scoreP1, scoreP2,
    turnsP1, turnsP2,
    pts
  };
  if (gameMode == MODE_501 || gameMode == MODE_301) {
    int &s = player1Turn ? scoreP1 : scoreP2;
    int newS = s - pts;
    if (newS < 0) { // BUST: nessuna variazione, cambio turno (se 2 giocatori)
      lastMove.valid = false;  // non è un "punteggio inserito"
      showMessage("BUST");
      if (numPlayers >= 2) {
        player1Turn = !player1Turn;
      }
      showTurnBanner();
      return;
    }
    s = newS;
    update7seg();
    if (newS == 0) {
      showMessage(player1Turn ? "P1 WIN" : "P2 WIN", 40, 0);
      phase = PH_GAME_OVER;
      delay(400);
      showStatic("WIN");
      return;
    }
    // Cambio turno solo se 2 giocatori
    if (numPlayers >= 2) player1Turn = !player1Turn;
    showStatic(String(pts));
    delay(120);
    showTurnBanner();
    return;
  }
  // ====== Hi-Score ======
  if (gameMode == MODE_HISCORE) {
    if (player1Turn) {
      if (turnsP1 >= HISCORE_TURNS) { showMessage("P1 FINITO"); return; }
      scoreP1 += pts;
      turnsP1++;
    } else {
      if (turnsP2 >= HISCORE_TURNS) { showMessage("P2 FINITO"); return; }
      scoreP2 += pts;
      turnsP2++;
    }
    update7seg();
    showStatic(String(pts));
    delay(120);
    // Verifica fine gara
    bool p1done = (turnsP1 >= HISCORE_TURNS);
    bool p2done = (numPlayers == 1) ? true : (turnsP2 >= HISCORE_TURNS);
    if (p1done && p2done) {
      // Fine — determina vincitore o pareggio
      if (scoreP1 > scoreP2)      showMessage("WINNER P1");
      else if (scoreP2 > scoreP1) showMessage("WINNER P2");
      else                        showMessage("PAREGGIO");
      phase = PH_GAME_OVER;
      delay(400);
      String finalMsg = "P1 " + String(scoreP1) + "  P2 " + String(scoreP2);
      showMessage(finalMsg);
      delay(400);
      showStatic("FINE");
      return;
    }
    // Passa turno se 2 giocatori e l'altro non ha finito
    if (numPlayers >= 2) {
      // Se l'altro è già finito, resta allo stesso
      bool otherDone = player1Turn ? (turnsP2 >= HISCORE_TURNS) : (turnsP1 >= HISCORE_TURNS);
      if (!otherDone) player1Turn = !player1Turn;
    }
    showTurnBanner();
  }
}
// ====================== UNDO ======================
void undoLast() {
  if (!lastMove.valid) { showMessage("NO UNDO"); return; }
  // Ripristina
  gameMode   = lastMove.mode;  // (di fatto invariato)
  scoreP1    = lastMove.prevScoreP1;
  scoreP2    = lastMove.prevScoreP2;
  turnsP1    = lastMove.prevTurnsP1;
  turnsP2    = lastMove.prevTurnsP2;
  player1Turn= lastMove.p1TurnBefore;
  update7seg();
  showMessage("UNDO " + String(lastMove.applied));
  lastMove.valid = false;
  delay(150);
  showTurnBanner();
}
// ====================== SETUP / LOOP ======================
void setup() {
  // TM1637Display
  dispP1.setBrightness(5);  // 0..7
  dispP2.setBrightness(5);
  // MAX7219 / Parola
  parola.begin();
  parola.setZone(0, 0, MAX_DEVICES - 1); // unica zona su 4 moduli
  parola.setIntensity(8);                // 0..15
  parola.displayClear();
  // Se la matrice appare capovolta/specchiata, puoi abilitare questi:
  // parola.setZoneEffect(0, PA_FLIP_UD, true); // capovolto su↔giù
  // parola.setZoneEffect(0, PA_FLIP_LR, true); // specchiato sx↔dx
  Serial.begin(115200);
  delay(100);
  toMenu();
}
void loop() {
  parola.displayAnimate();
  char key = keypad.getKey();
  if (!key) return;
  // Input numerico: accumula (limite 180)
  if (key >= '0' && key <= '9') {
    int next = inputScore * 10 + (key - '0');
    if (next <= 180) {
      inputScore = next;
      // Mostra input sul MAX (senza bloccare lo stato)
      showStatic(String(inputScore));
    } else {
      showMessage("MAX 180");
    }
    return;
  }
  switch (key) {
    // ========= SELEZIONE GIOCO (A/B/C) =========
    case 'A': { // 501
      if (phase == PH_MENU || phase == PH_GAME_OVER) {
        bool thisStart = startP1_501;  // usa chi deve partire ORA...
        startP1_501 = !startP1_501;    // ...e alterna per la prossima volta
        gameMode = MODE_501;
        askPlayers();
        // memorizziamo chi partirà quando avvieremo il gioco (dopo 1/2)
        player1Turn = thisStart;
        showMessage(thisStart ? "501 PART P1" : "501 PART P2");
      }
    } break;
    case 'B': { // 301
      if (phase == PH_MENU || phase == PH_GAME_OVER) {
        bool thisStart = startP1_301;
        startP1_301 = !startP1_301;
        gameMode = MODE_301;
        askPlayers();
        player1Turn = thisStart;
        showMessage(thisStart ? "301 PART P1" : "301 PART P2");
      }
    } break;
    case 'C': { // Hi-Score
      if (phase == PH_MENU || phase == PH_GAME_OVER) {
        bool thisStart = startP1_HIS;
        startP1_HIS = !startP1_HIS;
        gameMode = MODE_HISCORE;
        askPlayers();
        player1Turn = thisStart;
        showMessage(thisStart ? "HIS PART P1" : "HIS PART P2");
      }
    } break;
    // ========= RESET TOTALE =========
    case 'D':
      toMenu();
      break;
    // ========= CONFERMA PUNTEGGIO =========
    case '#':
      if (phase == PH_ASK_PLAYERS) {
        // qui # NON fa nulla: serve '1' o '2'
        showMessage("SCEGLI 1 O 2");
        break;
      }
      if (phase == PH_RUNNING) {
        if (inputScore > 0) {
          applyScore(inputScore);
          inputScore = 0;
        } else {
          showMessage("NO DATA");
        }
      }
      break;
    // ========= UNDO =========
    case '*':
      if (phase == PH_RUNNING || phase == PH_GAME_OVER) {
        undoLast();
        inputScore = 0;
      }
      break;
    // ========= SCELTA NUMERO GIOCATORI (in PH_ASK_PLAYERS) =========
    case '1':
    case '2':
      if (phase == PH_ASK_PLAYERS) {
        numPlayers = (key == '1') ? 1 : 2;
        // Avvia il gioco scelto: usa player1Turn già fissato al momento della scelta modalità
        startGame(gameMode, player1Turn);
      }
      break;
    default:
      break;
  }
}