#include <Adafruit_GFX.h> 
#include <Adafruit_ST7735.h> 
#include <avr/io.h>
#include <avr/interrupt.h>

#define TFT_CS        10 
#define TFT_RST       9 
#define TFT_DC        8 
#define BUZZER_PIN     5  

#define MASK_SEL  (1 << 3)
#define MASK_W    (1 << 4)
#define MASK_S    (1 << 5)
#define MASK_A    (1 << 6)
#define MASK_D    (1 << 7)

void initSPIHardware() {
    DDRB |= (1 << PB3) | (1 << PB5) | (1 << PB2) | (1 << PB0) | (1 << PB1);
    PORTB |= (1 << PB2);
    SPCR = (1 << SPE) | (1 << MSTR);
    SPSR |= (1 << SPI2X);
}

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

enum StareJoc { MENIU, IN_JOC, GAME_OVER };
StareJoc stareCurenta = MENIU;

int dimensiuneGrid = 3; 
int tabla[5][5];        

int cursorX = 0, cursorY = 0;
int jucatorCurent = 1; 
int stareCastig = 0;   

int x_tabla, y_tabla, dim_tabla, dim_celula;

volatile unsigned long milisecundeManuale = 0;
unsigned long timpStartTura = 0;
const unsigned long TIMP_LIMITA = 10000; 

bool apasat_W    = false;
bool apasat_S    = false;
bool apasat_A    = false;
bool apasat_D    = false;
bool apasat_SEL = false;

void initTimer0Delay() {
    TCCR0A = 0x00;
    TCCR0B = 0x03;
}

void asteaptaMicrosecunde(unsigned int us) {
    unsigned int pasi = us / 4;
    if (pasi == 0) pasi = 1;
    
    TCNT0 = 0;
    TIFR0 |= (1 << TOV0);
    
    while (TCNT0 < pasi) {
        if (TIFR0 & (1 << TOV0)) break;
    }
}

void asteaptaMilsecunde(unsigned int ms) {
    while (ms--) {
        TCNT0 = 0;
        TIFR0 |= (1 << TOV0);
        TCNT0 = 6; 
        while (!(TIFR0 & (1 << TOV0)));
    }
}

void pulsHardware(int frecventaMicros, int durataMs) {
    long limite = (long)durataMs * 1000;
    long contor = 0;
    while (contor < limite) {
        PORTC |= (1 << BUZZER_PIN);   
        asteaptaMicrosecunde(frecventaMicros);
        PORTC &= ~(1 << BUZZER_PIN);  
        asteaptaMicrosecunde(frecventaMicros);
        contor += (frecventaMicros * 2);
    }
}

void sunetStartSistem() { pulsHardware(800, 300); asteaptaMilsecunde(100); pulsHardware(500, 300); }
void sunetSelect()   { pulsHardware(300, 50); }
void sunetMiscare()  { pulsHardware(600, 20); }
void sunetEroare()   { pulsHardware(1500, 100); }
void sunetTimpExpirat() { pulsHardware(1000, 150); asteaptaMilsecunde(50); pulsHardware(1200, 150); }
void sunetCastig()   { pulsHardware(500, 150); asteaptaMilsecunde(50); pulsHardware(400, 150); asteaptaMilsecunde(50); pulsHardware(300, 300); }
void sunetPierdere() { pulsHardware(1000, 250); asteaptaMilsecunde(50); pulsHardware(1500, 400); }

uint8_t citesteButoaneCuDebounce() {
    uint8_t starea1 = PIND;
    asteaptaMilsecunde(5); 
    uint8_t starea2 = PIND;
    return starea1 | (starea1 ^ starea2);
}

void calculeazaDimensiuni() {
    if (dimensiuneGrid == 3) { dim_tabla = 120; x_tabla = 20; } 
    else if (dimensiuneGrid == 4) { dim_tabla = 120; x_tabla = 20; } 
    else { dim_tabla = 125; x_tabla = 17; }
    y_tabla = 12; 
    dim_celula = dim_tabla / dimensiuneGrid;
}

void deseneazaMeniu() {
    tft.fillScreen(ST7735_BLACK);
    tft.setTextColor(ST7735_WHITE);
    tft.setTextSize(2);
    tft.setCursor(5, 15); 
    tft.print("TicTacToe++");
    
    tft.setTextSize(1);
    tft.setCursor(15, 50); 
    tft.print("alege dimensiunea:");
    
    tft.setTextSize(2);
    tft.setTextColor(ST7735_GREEN);
    tft.setCursor(45, 70); 
    tft.print(dimensiuneGrid); tft.print("x"); tft.print(dimensiuneGrid);
}

void deseneazaGrila() {
    tft.fillScreen(ST7735_BLACK);
    for (int i = 1; i < dimensiuneGrid; i++) {
        tft.drawFastVLine(x_tabla + i * dim_celula, y_tabla, dim_tabla, ST7735_WHITE);
        tft.drawFastHLine(x_tabla, y_tabla + i * dim_celula, dim_tabla, ST7735_WHITE);
    }
}

void deseneazaPiesa(int rand, int col, int tip) {
    int x = x_tabla + col * dim_celula + dim_celula / 2;
    int y = y_tabla + rand * dim_celula + dim_celula / 2;
    int r = dim_celula / 3;

    if (tip == 1) { 
        tft.drawLine(x - r, y - r, x + r, y + r, ST7735_BLUE);
        tft.drawLine(x + r, y - r, x - r, y + r, ST7735_BLUE);
    } else if (tip == 2) { 
        tft.drawCircle(x, y, r, ST7735_ORANGE);
    }
}

void deseneazaCursor(uint16_t culoare) {
    int x = x_tabla + cursorX * dim_celula;
    int y = y_tabla + cursorY * dim_celula;
    tft.drawRect(x + 2, y + 2, dim_celula - 4, dim_celula - 4, culoare);
}

bool verifica3LaRand(int jucator) {
    for (int r = 0; r < dimensiuneGrid; r++) {
        for (int c = 0; c <= dimensiuneGrid - 3; c++) {
            if (tabla[r][c] == jucator && tabla[r][c+1] == jucator && tabla[r][c+2] == jucator) return true;
        }
    }
    for (int c = 0; c < dimensiuneGrid; c++) {
        for (int r = 0; r <= dimensiuneGrid - 3; r++) {
            if (tabla[r][c] == jucator && tabla[r+1][c] == jucator && tabla[r+2][c] == jucator) return true;
        }
    }
    for (int r = 0; r <= dimensiuneGrid - 3; r++) {
        for (int c = 0; c <= dimensiuneGrid - 3; c++) {
            if (tabla[r][c] == jucator && tabla[r+1][c+1] == jucator && tabla[r+2][c+2] == jucator) return true;
        }
    }
    for (int r = 0; r <= dimensiuneGrid - 3; r++) {
        for (int c = 2; c < dimensiuneGrid; c++) {
            if (tabla[r][c] == jucator && tabla[r+1][c-1] == jucator && tabla[r+2][c-2] == jucator) return true;
        }
    }
    return false;
}

bool esteTablaPlina() {
    for (int r = 0; r < dimensiuneGrid; r++) {
        for (int c = 0; c < dimensiuneGrid; c++) { 
            if (tabla[r][c] == 0) return false;
        }
    }
    return true;
}

void pornesteJocul() {
    calculeazaDimensiuni();
    for (int r = 0; r < 5; r++) {
        for (int c = 0; c < 5; c++) tabla[r][c] = 0;
    }
    cursorX = 0; cursorY = 0;
    jucatorCurent = 1;
    stareCastig = 0;
    stareCurenta = IN_JOC;
    deseneazaGrila();
    deseneazaCursor(ST7735_RED);
    timpStartTura = milisecundeManuale; 
}

void updateTimerGrafic() {
    unsigned long timpScurs = milisecundeManuale - timpStartTura;
    if (timpScurs > TIMP_LIMITA) timpScurs = TIMP_LIMITA;
    unsigned long timpRamas = TIMP_LIMITA - timpScurs;

    int latimeBara = (timpRamas * 120) / TIMP_LIMITA; 
    tft.fillRect(20, 2, 120, 3, ST7735_BLACK); 
    tft.fillRect(20, 2, latimeBara, 3, (timpRamas < 3000) ? ST7735_RED : ST7735_GREEN);

    tft.setCursor(20, 4);
    tft.setTextSize(1);
    tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
    tft.print("Rand: ");
    tft.print((jucatorCurent == 1) ? "X" : "O");

    if (timpScurs >= TIMP_LIMITA) {
        sunetTimpExpirat(); 
        deseneazaCursor(ST7735_BLACK); 
        jucatorCurent = (jucatorCurent == 1) ? 2 : 1; 
        timpStartTura = milisecundeManuale;
        deseneazaCursor(ST7735_RED);
    }
}

void deseneazaGameOver() {
    tft.fillScreen(ST7735_BLACK);
    tft.setTextSize(2);
    tft.setCursor(25, 25);
    tft.setTextColor(ST7735_RED);
    tft.print("GAME OVER");

    tft.setTextSize(1);
    tft.setTextColor(ST7735_WHITE);
    tft.setCursor(15, 60); 
    
    if (stareCastig == 3) {
        tft.print("EGALITATE!");
        sunetPierdere();
    } else {
        tft.print("CASTIGATOR: ");
        tft.print((stareCastig == 1) ? "X" : "O");
        sunetCastig(); 
    }

    tft.setCursor(5, 100); 
    tft.print("Apasati SEL: Meniu");
}

ISR(TIMER2_OVF_vect) {
    milisecundeManuale++;
    TCNT2 = 6; 
}

void setup() {
    initSPIHardware();
    initTimer0Delay();

    DDRD &= ~(MASK_SEL | MASK_W | MASK_S | MASK_A | MASK_D);
    PORTD |= (MASK_SEL | MASK_W | MASK_S | MASK_A | MASK_D);
    DDRC |= (1 << BUZZER_PIN); 

    TCCR2A = 0x00;          
    TCCR2B = 0x04;          
    TCNT2 = 6;              
    TIMSK2 |= (1 << TOIE2); 
    
    sei(); 

    sunetStartSistem();

    tft.initR(INITR_144GREENTAB); 
    tft.setRotation(1); 
    deseneazaMeniu();
}

void loop() {
    uint8_t stareaPortului = citesteButoaneCuDebounce();

    bool stW   = ((stareaPortului & MASK_W) == 0);
    bool stS   = ((stareaPortului & MASK_S) == 0);
    bool stA   = ((stareaPortului & MASK_A) == 0);
    bool stD   = ((stareaPortului & MASK_D) == 0);
    bool stSel = ((stareaPortului & MASK_SEL) == 0);

    if (stareCurenta == MENIU) {
        if (stW && !apasat_W) {
            if (dimensiuneGrid < 5) { dimensiuneGrid++; sunetMiscare(); deseneazaMeniu(); }
            apasat_W = true;
        } else if (!stW) apasat_W = false;

        if (stS && !apasat_S) {
            if (dimensiuneGrid > 3) { dimensiuneGrid--; sunetMiscare(); deseneazaMeniu(); }
            apasat_S = true;
        } else if (!stS) apasat_S = false;

        if (stSel && !apasat_SEL) {
            sunetSelect();
            pornesteJocul();
            apasat_SEL = true;
        } else if (!stSel) apasat_SEL = false;
    }
    else if (stareCurenta == IN_JOC) {
        updateTimerGrafic();

        int vechiX = cursorX;
        int vechiY = cursorY;
        bool mutat = false;

        if (stW && !apasat_W) { if (cursorY > 0) { cursorY--; mutat = true; } apasat_W = true; } 
        else if (!stW) apasat_W = false;
        
        if (stS && !apasat_S) { if (cursorY < dimensiuneGrid - 1) { cursorY++; mutat = true; } apasat_S = true; } 
        else if (!stS) apasat_S = false;
        
        if (stA && !apasat_A) { if (cursorX > 0) { cursorX--; mutat = true; } apasat_A = true; } 
        else if (!stA) apasat_A = false;
        
        if (stD && !apasat_D) { if (cursorX < dimensiuneGrid - 1) { cursorX++; mutat = true; } apasat_D = true; } 
        else if (!stD) apasat_D = false;

        if (mutat) {
            sunetMiscare();
            int x = x_tabla + vechiX * dim_celula;
            int y = y_tabla + vechiY * dim_celula;
            tft.drawRect(x + 2, y + 2, dim_celula - 4, dim_celula - 4, ST7735_BLACK);
            if (tabla[vechiY][vechiX] != 0) deseneazaPiesa(vechiY, vechiX, tabla[vechiY][vechiX]);
            deseneazaCursor(ST7735_RED);
        }

        if (stSel && !apasat_SEL) {
            apasat_SEL = true;
            if (tabla[cursorY][cursorX] == 0) {
                sunetSelect();
                tabla[cursorY][cursorX] = jucatorCurent;
                deseneazaPiesa(cursorY, cursorX, jucatorCurent);

                if (verifica3LaRand(jucatorCurent)) {
                    stareCastig = jucatorCurent;
                    stareCurenta = GAME_OVER;
                    deseneazaGameOver();
                } else if (esteTablaPlina()) {
                    stareCastig = 3; 
                    stareCurenta = GAME_OVER;
                    deseneazaGameOver();
                } else {
                    jucatorCurent = (jucatorCurent == 1) ? 2 : 1;
                    timpStartTura = milisecundeManuale;
                    deseneazaCursor(ST7735_RED);
                }
            } else {
                sunetEroare(); 
            }
        } else if (!stSel) apasat_SEL = false;
    }
    else if (stareCurenta == GAME_OVER) {
        if (stSel && !apasat_SEL) {
            sunetSelect();
            stareCurenta = MENIU;
            deseneazaMeniu();
            apasat_SEL = true;
        } else if (!stSel) apasat_SEL = false;
    }
}