#include "game.h"
#include "touch.h"
#include "storage.h"

// --- Global definitions ---

Adafruit_ST7789 tft = Adafruit_ST7789(&SPI1, LCD_CS, LCD_DC, LCD_RST);

int8_t board[COLS][ROWS];
WinPos winPos[4];
int winCount = 0;

State gameState = MENU;
int8_t menuMode = 0;
int8_t currentPlayer = 1;
int8_t winnerPlayer = 0;
int8_t activeCol = 3;
int8_t gameMenuMode = 0;
uint8_t demoPly[2] = {4, 4};
bool abortAi = false;
String currentMoves;
uint8_t gameLevel = 0;
uint8_t currentLookAhead = DEFAULT_LOOK_AHEAD;
bool blunderEnabled = BLUNDER_ENABLED;
uint8_t blunderChance = BLUNDER_CHANCE;

GameEntry gameLog[MAX_GAME_LOG];
uint8_t gameLogCount = 0;

uint32_t lastActivityTime = 0;
uint32_t demoResetTimer = 0;
uint32_t lastDemoMove = 0;
bool flashToggle = true;
uint32_t lastFlash = 0;
bool needRedraw = true;

const int8_t colOrder[] = {3, 2, 4, 1, 5, 0, 6};

// --- Board helpers ---

uint16_t playerColor(int8_t p) { return p == 1 ? C_P1 : C_P2; }
uint16_t playerColorDim(int8_t p) { return p == 1 ? C_P1DIM : C_P2DIM; }

int cellX(int c) { return BRD_X + c * CELL + CELL / 2; }
int cellY(int r) { return BRD_Y + (ROWS - 1 - r) * CELL + CELL / 2; }

void resetBoard()
{
    memset(board, 0, sizeof(board));
    winnerPlayer = 0;
    winCount = 0;
}

int getFirstEmptyRow(int col)
{
    for (int r = 0; r < ROWS; r++)
        if (board[col][r] == 0)
            return r;
    return -1;
}

bool isBoardFull()
{
    for (int c = 0; c < COLS; c++)
        if (board[c][ROWS - 1] == 0)
            return false;
    return true;
}

bool isWinPos(int c, int r)
{
    for (int i = 0; i < winCount; i++)
        if (winPos[i].c == c && winPos[i].r == r)
            return true;
    return false;
}

void randomizeDemoPlies()
{
    uint8_t strong = random(4, 6), weak = random(2, 4);
    if (random(2))
    {
        demoPly[0] = strong;
        demoPly[1] = weak;
    }
    else
    {
        demoPly[0] = weak;
        demoPly[1] = strong;
    }
}

// --- Game logic ---

int8_t scanBoard()
{
    winCount = 0;
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c <= COLS - 4; c++)
        {
            int8_t p = board[c][r];
            if (p && board[c + 1][r] == p && board[c + 2][r] == p && board[c + 3][r] == p)
            {
                for (int i = 0; i < 4; i++)
                    winPos[i] = {(int8_t)(c + i), (int8_t)r};
                winCount = 4;
                return p;
            }
        }
    for (int r = 0; r <= ROWS - 4; r++)
        for (int c = 0; c < COLS; c++)
        {
            int8_t p = board[c][r];
            if (p && board[c][r + 1] == p && board[c][r + 2] == p && board[c][r + 3] == p)
            {
                for (int i = 0; i < 4; i++)
                    winPos[i] = {(int8_t)c, (int8_t)(r + i)};
                winCount = 4;
                return p;
            }
        }
    for (int r = 0; r <= ROWS - 4; r++)
        for (int c = 0; c <= COLS - 4; c++)
        {
            int8_t p = board[c][r];
            if (p && board[c + 1][r + 1] == p && board[c + 2][r + 2] == p && board[c + 3][r + 3] == p)
            {
                for (int i = 0; i < 4; i++)
                    winPos[i] = {(int8_t)(c + i), (int8_t)(r + i)};
                winCount = 4;
                return p;
            }
        }
    for (int r = 3; r < ROWS; r++)
        for (int c = 0; c <= COLS - 4; c++)
        {
            int8_t p = board[c][r];
            if (p && board[c + 1][r - 1] == p && board[c + 2][r - 2] == p && board[c + 3][r - 3] == p)
            {
                for (int i = 0; i < 4; i++)
                    winPos[i] = {(int8_t)(c + i), (int8_t)(r - i)};
                winCount = 4;
                return p;
            }
        }
    return 0;
}

int evaluateBoard(int8_t aiP, int8_t huP)
{
    int score = 0;
    for (int r = 0; r < ROWS; r++)
    {
        if (board[3][r] == aiP)
            score += 3;
        else if (board[3][r] == huP)
            score -= 3;
    }
    auto sw = [&](int c, int r, int dc, int dr) -> int
    {
        int ai = 0, hu = 0;
        for (int i = 0; i < 4; i++)
        {
            int8_t v = board[c + i * dc][r + i * dr];
            if (v == aiP)
                ai++;
            else if (v == huP)
                hu++;
        }
        if (ai && hu)
            return 0;
        if (ai == 3)
            return 50;
        if (ai == 2)
            return 5;
        if (hu == 3)
            return -50;
        if (hu == 2)
            return -5;
        return 0;
    };
    for (int r = 0; r < 6; r++)
        for (int c = 0; c < 4; c++)
            score += sw(c, r, 1, 0);
    for (int r = 0; r < 3; r++)
        for (int c = 0; c < 7; c++)
            score += sw(c, r, 0, 1);
    for (int r = 0; r < 3; r++)
        for (int c = 0; c < 4; c++)
            score += sw(c, r, 1, 1);
    for (int r = 3; r < 6; r++)
        for (int c = 0; c < 4; c++)
            score += sw(c, r, 1, -1);
    return score;
}

bool checkGameEnd()
{
    winnerPlayer = scanBoard();
    bool won = winnerPlayer != 0;
    bool draw = !won && isBoardFull();
    if (!won && !draw)
        return false;
    if (gameState != DEMO)
        logGame(won ? winnerPlayer : 0);
    gameState = won ? FINISHED_WIN : FINISHED_DRAW;
    demoResetTimer = millis();
    lastActivityTime = millis();
    flashToggle = false;
    lastFlash = millis();
    return true;
}

// --- AI ---

int minimax(int depth, int alpha, int beta, bool isMax,
            int8_t aiP, int8_t huP)
{
    if (gameState == DEMO && depth >= currentLookAhead - 1)
    {
        int16_t rx, ry;
        if (readRawTouch(rx, ry))
        {
            abortAi = true;
            return 0;
        }
    }
    yield();
    if (abortAi)
        return 0;

    int8_t win = scanBoard();
    if (win == aiP)
        return 1000 + depth;
    if (win == huP)
        return -1000 - depth;
    if (depth == 0 || isBoardFull())
        return evaluateBoard(aiP, huP);

    int best = isMax ? -10000 : 10000;
    for (int ci = 0; ci < COLS; ci++)
    {
        int c = colOrder[ci];
        if (abortAi)
            return 0;
        int r = getFirstEmptyRow(c);
        if (r == -1)
            continue;
        board[c][r] = isMax ? aiP : huP;
        int score = minimax(depth - 1, alpha, beta, !isMax, aiP, huP);
        board[c][r] = 0;
        if (isMax)
        {
            if (score > best)
                best = score;
            if (best > alpha)
                alpha = best;
        }
        else
        {
            if (score < best)
                best = score;
            if (best < beta)
                beta = best;
        }
        if (beta <= alpha)
            break;
    }
    return best;
}

int computeAiMove(int8_t aiP)
{
    abortAi = false;
    int8_t huP = (aiP == 1) ? 2 : 1;
    int bestScore = -30000, bestCol = 3;
    int originalPly = currentLookAhead;
    if (gameState == DEMO)
        currentLookAhead = demoPly[aiP - 1];

    bool found = false;

    for (int c = 0; c < COLS && !found; c++)
    {
        int r = getFirstEmptyRow(c);
        if (r == -1)
            continue;
        board[c][r] = aiP;
        if (scanBoard() == aiP)
        {
            board[c][r] = 0;
            bestCol = c;
            found = true;
        }
        else
            board[c][r] = 0;
    }

    for (int c = 0; c < COLS && !found; c++)
    {
        int r = getFirstEmptyRow(c);
        if (r == -1)
            continue;
        board[c][r] = huP;
        if (scanBoard() == huP)
        {
            board[c][r] = 0;
            bestCol = c;
            found = true;
        }
        else
            board[c][r] = 0;
    }

    if (!found && blunderEnabled && gameState != DEMO &&
        (random(100) < blunderChance))
    {
        int validCols[COLS], count = 0;
        for (int c = 0; c < COLS; c++)
            if (getFirstEmptyRow(c) != -1)
                validCols[count++] = c;
        bestCol = validCols[random(count)];
        found = true;
    }

    if (!found)
    {
        for (int ci = 0; ci < COLS; ci++)
        {
            int c = colOrder[ci];
            if (abortAi)
                break;
            int r = getFirstEmptyRow(c);
            if (r == -1)
                continue;
            board[c][r] = aiP;
            int score = minimax(currentLookAhead, -30000, 30000, false, aiP, huP);
            board[c][r] = 0;
            if (score > bestScore)
            {
                bestScore = score;
                bestCol = c;
            }
        }
    }

    currentLookAhead = originalPly;
    return bestCol;
}
