#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

#define i2c_Address 0x3c 
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// --- PINY ---
#define PIN_A      5  // Przycisk potwierdzający/startowy
#define PIN_B 4 // Przycisk umiejętności specjalnej
#define PIN_GORA   7
#define PIN_PRAWO  8
#define PIN_DOL    9
#define PIN_LEWO   10

Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Wymiary i pozycjonowanie na ekranie
const int COLS = 12;      
const int ROWS = 6;       
const int CELL_SIZE = 10; 
const int OFFSET_X = 4;   
const int OFFSET_Y = 2;   

bool maze[COLS][ROWS]; 
int playerX = 0, playerY = 0;
bool showingPath = false;
unsigned long previewStartTime;

int levelCounter = 1;

//umiejetnosc specjalna

int powerUses = 1;        // Limit: 1 użycie na całą grę
bool powerActive = false; // Czy umiejętność jest teraz włączona
unsigned long powerTimer; // Odliczanie 2 sekund


// Stany gry
enum GameState {
  TITLE_SCREEN,
  PLAYING,
  GAME_OVER
};

GameState currentState = TITLE_SCREEN;

// --- LOGIKA GENERATORA MAPY (Zoptymalizowana dla Arduino) ---

bool canPlacePath(int x, int y) {
  if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return false;
  if (maze[x][y]) return false; // Pole już zajęte

  int neighbors = 0;
  if (x + 1 < COLS && maze[x+1][y]) neighbors++;
  if (x - 1 >= 0   && maze[x-1][y]) neighbors++;
  if (y + 1 < ROWS && maze[x][y+1]) neighbors++;
  if (y - 1 >= 0   && maze[x][y-1]) neighbors++;

  // Ścieżka może dotykać tylko jednego kafelka (żeby nie tworzyć pętli)
  return (neighbors <= 1);
}

void generateMazeDFS(int startY) {
  // Czyszczenie mapy
  for(int x=0; x<COLS; x++) {
    for(int y=0; y<ROWS; y++) {
      maze[x][y] = false;
    }
  }

  // Używamy prostych tablic do zapamiętywania ścieżki (Backtracking)
  int historyX[72];
  int historyY[72];
  int step = 0;

  int curX = 0;
  int curY = startY;
  
  maze[curX][curY] = true;
  historyX[step] = curX;
  historyY[step] = curY;

  while (curX < COLS - 1) {
    int validDirs[4]; // 0: Prawo, 1: Dół, 2: Góra, 3: Lewo
    int count = 0;

    if (canPlacePath(curX + 1, curY)) validDirs[count++] = 0;
    if (canPlacePath(curX, curY + 1)) validDirs[count++] = 1;
    if (canPlacePath(curX, curY - 1)) validDirs[count++] = 2;
    if (canPlacePath(curX - 1, curY)) validDirs[count++] = 3;

    if (count > 0) {
      // Algorytm promuje ruch w prawo, żeby mapa szybciej parła do przodu
      int dir = validDirs[random(count)]; 
      if (random(50 + 50*levelCounter) < 90 && canPlacePath(curX + 1, curY)) {
        dir = 0; // Wymuś krok w prawo jeśli to możliwe (60% szans)                     // u nas 90% trudnosc
      }

      step++;
      if (dir == 0) curX++;
      else if (dir == 1) curY++;
      else if (dir == 2) curY--;
      else if (dir == 3) curX--;

      maze[curX][curY] = true;
      historyX[step] = curX;
      historyY[step] = curY;
    } else {
      // Zablokowaliśmy się - ślepy zaułek. Cofamy się 
      if (step > 0) {
        maze[curX][curY] = false; // Usuwamy złą ścieżkę
        step--;
        curX = historyX[step];
        curY = historyY[step];
      } else {
        // Zabezpieczenie na wypadek absolutnej blokady początkowej
        break; 
      }
    }
  }
}

// --- FUNKCJE RYSOWANIA I STANU GRY ---

void drawGrid() {
  for(int x=0; x<COLS; x++) {
    for(int y=0; y<ROWS; y++) {
      int dx = x * CELL_SIZE + OFFSET_X;
      int dy = y * CELL_SIZE + OFFSET_Y;
      display.drawRect(dx, dy, CELL_SIZE, CELL_SIZE, SH110X_WHITE);
      
      // 1. Standardowy podgląd na starcie poziomu
      if (showingPath && maze[x][y]) {
        display.fillRect(dx + 4, dy + 4, 2, 2, SH110X_WHITE);
      }

      // 2. NOWA MECHANIKA: Podświetlenie w promieniu 2
      if (powerActive && maze[x][y]) {
        // Obliczamy odległość w kwadracie (promień 2 komórki)
        if (abs(x - playerX) <= 2 && abs(y - playerY) <= 2) {
          // Rysujemy nieco większą kropkę (4x4), żeby odróżnić ją od podpowiedzi startowej
          display.fillRect(dx + 3, dy + 3, 4, 4, SH110X_WHITE);
        }
      }
    }
  }
}

void killProgram() {
  display.clearDisplay();
  
  if(levelCounter >= 4){
    display.setTextSize(2);
    display.setCursor(2, 10);
    display.print("Gratulacje");
    display.setTextSize(1);
    display.setCursor(10, 40);
    display.print("Odkryles zaginiony");
    display.setCursor(45, 50);
    display.print("skarb!");

 
                   // To tutja jest napis koncowy
  }
  else{
    display.setTextSize(2);
    display.setCursor(5, 10);
    display.print("Nie zyjesz");
    display.setTextSize(1);
    display.setCursor(20, 40);
    display.print("Gubisz sie w");
    display.setCursor(2, 50);
    display.print("czelusciach swiatyni!");

   
  }
  display.display();
  delay(2000);
  

    

  
  // Nieskończona pętla - całkowicie zamraża program
  while(true) {
    // Arduino "zawiesza się" tutaj
  }
}

void animacjaSpadania(int x, int y) {
    // Obliczamy środek komórki, w której stoi gracz
    int centerX = x * CELL_SIZE + OFFSET_X + (CELL_SIZE / 2);
    int centerY = y * CELL_SIZE + OFFSET_Y + (CELL_SIZE / 2);

    // Zmniejszamy rozmiar kropki od 6 do 0 pikseli
    for (int size = 6; size >= 0; size--) {
        display.clearDisplay();
        drawGrid(); // Rysujemy siatkę w tle, żeby było widać gdzie spadamy
        
        // Rysujemy kropkę scentrowaną, która się zmniejsza
        int halfSize = size / 2;
        display.fillRect(centerX - halfSize, centerY - halfSize, size, size, SH110X_WHITE);
        
        display.display();
        delay(100); // Prędkość spadania
    }
    delay(500); // Chwila ciemności po "upadku"
}

void animacjaStrzaly(int x, int y, bool ruchPionowy) {
  int targetX = x * CELL_SIZE + OFFSET_X + (CELL_SIZE / 2);
  int targetY = y * CELL_SIZE + OFFSET_Y + (CELL_SIZE / 2);

  if (ruchPionowy) {
    // Gracz ruszył się GÓRA/DÓŁ -> Strzała leci POZIOMO (z lewej do prawej)
    for (int ax = 0; ax <= targetX; ax += 5) {
      display.clearDisplay();
      drawGrid();
      // Gracz
      display.fillRect(x * CELL_SIZE + OFFSET_X + 2, y * CELL_SIZE + OFFSET_Y + 2, 6, 6, SH110X_WHITE);
      // Rysowanie strzały (linia + grot)
      display.drawLine(ax - 8, targetY, ax, targetY, SH110X_WHITE);
      display.drawTriangle(ax, targetY - 2, ax, targetY + 2, ax + 4, targetY, SH110X_WHITE);
      display.display();
    }
  } else {
    // Gracz ruszył się LEWO/PRAWO -> Strzała leci PIONOWO (z góry do dołu)
    for (int ay = 0; ay <= targetY; ay += 4) {
      display.clearDisplay();
      drawGrid();
      // Gracz
      display.fillRect(x * CELL_SIZE + OFFSET_X + 2, y * CELL_SIZE + OFFSET_Y + 2, 6, 6, SH110X_WHITE);
      // Rysowanie strzały (linia + grot)
      display.drawLine(targetX, ay - 8, targetX, ay, SH110X_WHITE);
      display.drawTriangle(targetX - 2, ay, targetX + 2, ay, targetX, ay + 4, SH110X_WHITE);
      display.display();
    }
  }
  
  // Efekt uderzenia - mignięcie ekranu
  display.invertDisplay(true);
  delay(100);
  display.invertDisplay(false);
  delay(500);
}


void setup() {
  pinMode(PIN_A,     INPUT_PULLUP);
  pinMode(PIN_B, INPUT_PULLUP);
  pinMode(PIN_GORA,  INPUT_PULLUP);
  pinMode(PIN_PRAWO, INPUT_PULLUP);
  pinMode(PIN_DOL,   INPUT_PULLUP);
  pinMode(PIN_LEWO,  INPUT_PULLUP);

  display.begin(i2c_Address, true);
  display.setRotation(2); 
  display.setTextColor(SH110X_WHITE);
  randomSeed(analogRead(0)); // Losowość na podstawie szumu na pinie analogowym

  // --- EKRAN STARTOWY ---
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(20, 10);
  display.print("INDIANA");
  display.setCursor(35, 28);
  display.print("JONES");
  display.setTextSize(1);
  display.setCursor(10, 50);
  display.print("the forbidden path");
  display.display();
  delay(2000); 
}



void loop() {

    
  if (currentState == TITLE_SCREEN) {
   
    display.clearDisplay();
    display.setTextSize(2);
    display.setCursor(25, 10);
    display.print("WITAJ!");
    display.setTextSize(1);
    display.setCursor(15, 40);
    display.print("Kliknij A aby");
    display.setCursor(35, 50);
    display.print("rozpoczac");
    display.display();

    // Czekamy na kliknięcie przycisku A
    if (digitalRead(PIN_A) == LOW) {
      playerY = random(0, ROWS); // Pierwszy start w losowym miejscu Y
      playerX = 0;
      generateMazeDFS(playerY);
      
      showingPath = true;
      previewStartTime = millis();
      currentState = PLAYING;
      delay(300); // Debouncing przycisku
    }
  } 
  
  else if (currentState == PLAYING) {
    // Wygaszanie podglądu ścieżki po 2 sekundach
    if (millis() - previewStartTime > 2000) showingPath = false;
    
    bool pionowo = false;
    
    bool moved = false;
    if (digitalRead(PIN_GORA) == LOW && playerY > 0) { playerY--; moved = true; pionowo = true; }
    else if (digitalRead(PIN_DOL) == LOW && playerY < ROWS - 1) { playerY++; moved = true; pionowo = true;}
    else if (digitalRead(PIN_LEWO) == LOW && playerX > 0) { playerX--; moved = true; pionowo = false; }
    else if (digitalRead(PIN_PRAWO) == LOW) { playerX++; moved = true; pionowo = false; }

    if (digitalRead(PIN_B) == LOW && powerUses > 0 && !powerActive) {
      powerActive = true;
      powerUses--; // Zużywamy jedyną szansę
      powerTimer = millis();
    }

    // Wyłączanie podświetlenia po 2 sekundach
    if (powerActive && (millis() - powerTimer > 2000)) {
      powerActive = false;
    }

    if (moved) {
      delay(200); // Czas pomiędzy krokami (debounce)

      if (playerX >= COLS) {
        // SUKCES: Przechodzimy na nową planszę
        levelCounter++;
        if(levelCounter >=4){
          currentState = GAME_OVER;
          killProgram();
        }

        int nextStartY = playerY; // Zachowujemy obecną pozycję Y!
        generateMazeDFS(nextStartY); // Budujemy ścieżkę zaczynając od tego Y
        playerX = 0; // Wracamy na lewą krawędź
        
        showingPath = true;
        previewStartTime = millis();
      } 
      else if (!maze[playerX][playerY]) {
        // Animacja spadania przed końcem gry
        int los = random(0,2);
        if(los == 0){
          animacjaSpadania(playerX, playerY);
        }
        else{
          animacjaStrzaly(playerX, playerY, pionowo);
        }

        // BŁĄD: Gracz zszedł z wyznaczonej trasy
        currentState = GAME_OVER;
        killProgram();
      }
    }

    display.clearDisplay();
    drawGrid();
    
    // Rysowanie gracza
    int px = playerX * CELL_SIZE + OFFSET_X + 2;
    int py = playerY * CELL_SIZE + OFFSET_Y + 2;
    display.fillRect(px, py, 6, 6, SH110X_WHITE);
    
    display.display();
  }
}