/*
  ESP32 Tetris su MAX7219 8x32 verticale + joystick
  Librerie richieste:
    - MD_MAX72xx (MajicDesigns)
    - MD_MAXPanel (MajicDesigns)
*/
#include <MD_MAX72xx.h>
#include <MD_MAXPanel.h>
#include <SPI.h>
// ====== PIN DISPLAY ======
#define DATA_PIN 23   // DIN
#define CLK_PIN  18   // CLK
#define CS_PIN   5    // CS/LOAD
// Tipo di modulo (controlla il tuo hardware!)
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
// 8x32 => 4 moduli 8x8 in cascata (1 colonna x 4 righe)
#define MAX_DEVICES 4
// ====== JOYSTICK ======
#define VRX_PIN 34
#define VRY_PIN 35
#define SW_PIN  27
const int ADC_CENTER = 2048;
const int DEADZONE   = 600;
const int TH_LEFT    = ADC_CENTER - DEADZONE;
const int TH_RIGHT   = ADC_CENTER + DEADZONE;
const int TH_UP      = ADC_CENTER - DEADZONE;
const int TH_DOWN    = ADC_CENTER + DEADZONE;
// ====== DISPLAY OBJECT ======
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
MD_MAXPanel panel(&mx, 1, 4); // 1 colonna x 4 moduli = 8x32
const int WIDTH  = 8;
const int HEIGHT = 32;
// ====== GIOCO ======
bool field[WIDTH][HEIGHT];
const uint16_t PIECES[7][4] = {
  {0x0F00, 0x2222, 0x00F0, 0x4444}, // I
  {0x8E00, 0x6440, 0x0E20, 0x44C0}, // J
  {0x2E00, 0x4460, 0x0E80, 0xC440}, // L
  {0x6600, 0x6600, 0x6600, 0x6600}, // O
  {0x6C00, 0x4620, 0x06C0, 0x8C40}, // S
  {0x4E00, 0x4640, 0x0E40, 0x4C40}, // T
  {0xC600, 0x2640, 0x0C60, 0x4C80}  // Z
};
int curPiece, curRot, curX, curY;
bool gameOver = false;
unsigned long lastFall = 0;
unsigned long fallInterval = 450;
// ====== FUNZIONI DI SUPPORTO ======
bool getMaskBit(uint16_t mask, int r, int c) {
  return (mask >> (15 - (r * 4 + c))) & 1;
}
bool collide(int x, int y, int p, int r) {
  uint16_t m = PIECES[p][r];
  for (int rr = 0; rr < 4; rr++) {
    for (int cc = 0; cc < 4; cc++) {
      if (getMaskBit(m, rr, cc)) {
        int fx = x + cc;
        int fy = y + rr;
        if (fx < 0 || fx >= WIDTH || fy < 0 || fy >= HEIGHT) return true;
        if (field[fx][fy]) return true;
      }
    }
  }
  return false;
}
void lockPiece() {
  uint16_t m = PIECES[curPiece][curRot];
  for (int rr = 0; rr < 4; rr++) {
    for (int cc = 0; cc < 4; cc++) {
      if (getMaskBit(m, rr, cc))
        field[curX + cc][curY + rr] = true;
    }
  }
}
void clearLines() {
  for (int y = 0; y < HEIGHT; y++) {
    bool full = true;
    for (int x = 0; x < WIDTH; x++)
      if (!field[x][y]) full = false;
    if (full) {
      for (int yy = y; yy > 0; yy--)
        for (int x = 0; x < WIDTH; x++)
          field[x][yy] = field[x][yy - 1];
      for (int x = 0; x < WIDTH; x++) field[x][0] = false;
      if (fallInterval > 100) fallInterval -= 15;
    }
  }
}
void spawnPiece() {
  curPiece = random(7);
  curRot = 0;
  curX = (WIDTH / 2) - 2;
  curY = 0;
  if (collide(curX, curY, curPiece, curRot))
    gameOver = true;
}
void rotatePiece() {
  int newRot = (curRot + 1) & 3;
  if (!collide(curX, curY, curPiece, newRot))
    curRot = newRot;
}
void moveLR(int dx) {
  if (!collide(curX + dx, curY, curPiece, curRot))
    curX += dx;
}
void softDrop() {
  if (!collide(curX, curY + 1, curPiece, curRot))
    curY++;
  else {
    lockPiece();
    clearLines();
    spawnPiece();
  }
}
// ====== INPUT ======
void handleInput() {
  static bool prevBtn = true;
  int ax = analogRead(VRX_PIN);
  int ay = analogRead(VRY_PIN);
  bool btn = digitalRead(SW_PIN);
  if (btn == LOW && prevBtn == HIGH) rotatePiece();
  prevBtn = btn;
  if (ax < TH_LEFT) moveLR(-1);
  else if (ax > TH_RIGHT) moveLR(+1);
  if (ay > TH_DOWN) softDrop();
}
// ====== DISPLAY ======
void drawPixel(int x, int y, bool on) {
  if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
  panel.setPixel(x, y, on);
}
void render() {
  panel.fillScreen(false);
  for (int y = 0; y < HEIGHT; y++)
    for (int x = 0; x < WIDTH; x++)
      if (field[x][y]) drawPixel(x, y, true);
  uint16_t m = PIECES[curPiece][curRot];
  for (int rr = 0; rr < 4; rr++)
    for (int cc = 0; cc < 4; cc++)
      if (getMaskBit(m, rr, cc))
        drawPixel(curX + cc, curY + rr, true);
  panel.update();
}
// ====== SETUP ======
void setup() {
  Serial.begin(115200);
  pinMode(SW_PIN, INPUT_PULLUP);
  analogReadResolution(12);
  mx.begin();
  panel.begin();
  panel.setRotation(1);  // 0,1,2,3 → regola per ottenere la verticale corretta
  panel.setIntensity(3); // luminosità 0..15
  panel.fillScreen(false);
  panel.update();
  for (int y = 0; y < HEIGHT; y++)
    for (int x = 0; x < WIDTH; x++)
      field[x][y] = false;
  randomSeed(analogRead(32) ^ micros());
  spawnPiece();
  lastFall = millis();
}
// ====== LOOP ======
void loop() {
  if (gameOver) {
    panel.fillScreen(true);
    panel.update();
    delay(200);
    panel.fillScreen(false);
    panel.update();
    delay(200);
    if (digitalRead(SW_PIN) == LOW) {
      memset(field, 0, sizeof(field));
      gameOver = false;
      fallInterval = 450;
      spawnPiece();
    }
    return;
  }
  handleInput();
  if (millis() - lastFall >= fallInterval) {
    softDrop();
    lastFall = millis();
  }
  render();
  delay(50);
}