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

// --- Global definitions ---

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

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};
AiJob aiJob = {};

// --- Board helpers (use global board) ---

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 (uses global board) ---

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)
{
    return evaluateBoardB(board, aiP, huP);
}

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;
}

// ================================================================
//  Pure board functions (no globals — safe for core 1)
// ================================================================

int getFirstEmptyRowB(const int8_t b[][ROWS], int col)
{
    for (int r = 0; r < ROWS; r++)
        if (b[col][r] == 0) return r;
    return -1;
}

bool isBoardFullB(const int8_t b[][ROWS])
{
    for (int c = 0; c < COLS; c++)
        if (b[c][ROWS - 1] == 0) return false;
    return true;
}

int8_t scanBoardB(const int8_t b[][ROWS])
{
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c <= COLS - 4; c++) {
            int8_t p = b[c][r];
            if (p && b[c+1][r]==p && b[c+2][r]==p && b[c+3][r]==p) return p;
        }
    for (int r = 0; r <= ROWS - 4; r++)
        for (int c = 0; c < COLS; c++) {
            int8_t p = b[c][r];
            if (p && b[c][r+1]==p && b[c][r+2]==p && b[c][r+3]==p) return p;
        }
    for (int r = 0; r <= ROWS - 4; r++)
        for (int c = 0; c <= COLS - 4; c++) {
            int8_t p = b[c][r];
            if (p && b[c+1][r+1]==p && b[c+2][r+2]==p && b[c+3][r+3]==p) return p;
        }
    for (int r = 3; r < ROWS; r++)
        for (int c = 0; c <= COLS - 4; c++) {
            int8_t p = b[c][r];
            if (p && b[c+1][r-1]==p && b[c+2][r-2]==p && b[c+3][r-3]==p) return p;
        }
    return 0;
}

int evaluateBoardB(const int8_t b[][ROWS], int8_t aiP, int8_t huP)
{
    int score = 0;
    int aiThreats = 0, huThreats = 0;

    // Center column bonus
    for (int r = 0; r < ROWS; r++) {
        if (b[3][r] == aiP) score += 3;
        else if (b[3][r] == huP) score -= 3;
    }

    // Score a window of 4 cells — playability-aware for 3-in-a-row
    auto sw = [&](int c, int r, int dc, int dr) -> int {
        int ai = 0, hu = 0;
        int emptyC = -1, emptyR = -1;
        for (int i = 0; i < 4; i++) {
            int cc = c + i * dc, rr = r + i * dr;
            int8_t v = b[cc][rr];
            if (v == aiP) ai++;
            else if (v == huP) hu++;
            else { emptyC = cc; emptyR = rr; }
        }
        if (ai && hu) return 0;
        if (ai == 3) {
            aiThreats++;
            bool playable = (emptyR == 0) || (b[emptyC][emptyR - 1] != 0);
            return playable ? 100 : 40;
        }
        if (ai == 2) return 5;
        if (hu == 3) {
            huThreats++;
            bool playable = (emptyR == 0) || (b[emptyC][emptyR - 1] != 0);
            return playable ? -100 : -40;
        }
        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);

    // Fork bonus: multiple simultaneous threats are disproportionately dangerous
    if (aiThreats >= 2) score += 200;
    if (huThreats >= 2) score -= 200;

    return score;
}

int minimaxB(int8_t b[][ROWS], int depth, int alpha, int beta,
             bool isMax, int8_t aiP, int8_t huP, volatile bool &abortFlag)
{
    if (abortFlag) return 0;

    int8_t win = scanBoardB(b);
    if (win == aiP) return 1000 + depth;
    if (win == huP) return -1000 - depth;
    if (depth == 0 || isBoardFullB(b)) return evaluateBoardB(b, aiP, huP);

    int best = isMax ? -10000 : 10000;
    for (int ci = 0; ci < COLS; ci++) {
        int c = colOrder[ci];
        if (abortFlag) return 0;
        int r = getFirstEmptyRowB(b, c);
        if (r == -1) continue;
        b[c][r] = isMax ? aiP : huP;
        int score = minimaxB(b, depth - 1, alpha, beta, !isMax, aiP, huP, abortFlag);
        b[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 computeAiMoveB(int8_t b[][ROWS], int8_t aiP, uint8_t ply,
                   bool useBlunder, volatile bool &abortFlag)
{
    int8_t huP = (aiP == 1) ? 2 : 1;
    int bestScore = -30000, bestCol = 3;
    bool found = false;

    // Phase 1a: instant win
    for (int c = 0; c < COLS && !found; c++) {
        int r = getFirstEmptyRowB(b, c);
        if (r == -1) continue;
        b[c][r] = aiP;
        if (scanBoardB(b) == aiP) { b[c][r] = 0; bestCol = c; found = true; }
        else b[c][r] = 0;
    }

    // Phase 1b: block opponent
    for (int c = 0; c < COLS && !found; c++) {
        int r = getFirstEmptyRowB(b, c);
        if (r == -1) continue;
        b[c][r] = huP;
        if (scanBoardB(b) == huP) { b[c][r] = 0; bestCol = c; found = true; }
        else b[c][r] = 0;
    }

    // Phase 2: blunder
    if (!found && useBlunder && blunderEnabled && (random(100) < blunderChance)) {
        int validCols[COLS], count = 0;
        for (int c = 0; c < COLS; c++)
            if (getFirstEmptyRowB(b, c) != -1) validCols[count++] = c;
        if (count > 0) { bestCol = validCols[random(count)]; found = true; }
    }

    // Phase 3: deep minimax
    if (!found) {
        for (int ci = 0; ci < COLS; ci++) {
            int c = colOrder[ci];
            if (abortFlag) break;
            int r = getFirstEmptyRowB(b, c);
            if (r == -1) continue;
            b[c][r] = aiP;
            int score = minimaxB(b, ply, -30000, 30000, false, aiP, huP, abortFlag);
            b[c][r] = 0;
            if (score > bestScore) { bestScore = score; bestCol = c; }
        }
    }

    return bestCol;
}

// --- AI on global board (core 0 only — for demo mode) ---

int computeAiMove(int8_t aiP)
{
    abortAi = false;
    int originalPly = currentLookAhead;
    if (gameState == DEMO) currentLookAhead = demoPly[aiP - 1];
    int result = computeAiMoveB(board, aiP, currentLookAhead,
                                gameState != DEMO, abortAi);
    currentLookAhead = originalPly;
    return result;
}

// ================================================================
//  AI job control (core 0 posts, core 1 executes)
// ================================================================

// ================================================================
//  Parallel AI: root splitting across both cores
// ================================================================

int computeAiMoveParallel(int8_t aiP)
{
    int8_t huP = (aiP == 1) ? 2 : 1;

    // Phase 1a: instant win (fast, core 0 only)
    for (int c = 0; c < COLS; c++) {
        int r = getFirstEmptyRow(c);
        if (r == -1) continue;
        board[c][r] = aiP;
        bool wins = (scanBoardB(board) == aiP);
        board[c][r] = 0;
        if (wins) return c;
    }

    // Phase 1b: block opponent (fast, core 0 only)
    for (int c = 0; c < COLS; c++) {
        int r = getFirstEmptyRow(c);
        if (r == -1) continue;
        board[c][r] = huP;
        bool wins = (scanBoardB(board) == huP);
        board[c][r] = 0;
        if (wins) return c;
    }

    // Phase 2: blunder
    if (blunderEnabled && (random(100) < blunderChance)) {
        int validCols[COLS], count = 0;
        for (int c = 0; c < COLS; c++)
            if (getFirstEmptyRow(c) != -1) validCols[count++] = c;
        if (count > 0) return validCols[random(count)];
    }

    // Phase 3: parallel minimax — split root columns across both cores
    // Core 1 gets odd-indexed columns from colOrder: [2, 1, 0] (indices 1,3,5)
    aiJob.abort = false;
    aiJob.done = false;
    memcpy(aiJob.boardCopy, board, sizeof(board));
    aiJob.aiPlayer = aiP;
    aiJob.ply = currentLookAhead;
    aiJob.searchCount = 0;
    for (int ci = 1; ci < COLS; ci += 2)
        aiJob.searchCols[aiJob.searchCount++] = colOrder[ci];
    aiJob.bestScore = -30000;
    aiJob.bestCol = 3;
    aiJob.type = JOB_SEARCH;  // Core 1 starts immediately

    // Core 0 searches even-indexed columns: [3, 4, 5, 6] (indices 0,2,4,6)
    int8_t boardCopy0[COLS][ROWS];
    memcpy(boardCopy0, board, sizeof(board));
    int bestScore0 = -30000, bestCol0 = 3;
    volatile bool abort0 = false;

    for (int ci = 0; ci < COLS; ci += 2) {
        int c = colOrder[ci];
        if (aiJob.abort) break;  // External abort (e.g. returning to menu)
        int r = getFirstEmptyRowB(boardCopy0, c);
        if (r == -1) continue;
        boardCopy0[c][r] = aiP;
        int score = minimaxB(boardCopy0, currentLookAhead, -30000, 30000,
                             false, aiP, huP, abort0);
        boardCopy0[c][r] = 0;
        if (score > bestScore0) { bestScore0 = score; bestCol0 = c; }
    }

    // Wait for core 1 to finish its columns
    while (!aiJob.done && aiJob.type != JOB_NONE) delay(1);

    // Merge: pick the best result from either core
    if ((int)aiJob.bestScore > bestScore0)
        return aiJob.bestCol;
    return bestCol0;
}

void startPonder(int8_t aiP)
{
    memcpy(aiJob.boardCopy, board, sizeof(board));
    aiJob.aiPlayer = aiP;
    aiJob.ply = currentLookAhead;
    aiJob.done = false;
    aiJob.abort = false;
    memset((void *)aiJob.ponderValid, 0, sizeof(aiJob.ponderValid));
    memset((void *)aiJob.ponderResults, 3, sizeof(aiJob.ponderResults));
    aiJob.type = JOB_PONDER;
}

void executeAiJob()
{
    if (aiJob.type == JOB_SEARCH) {
        int8_t aiP = aiJob.aiPlayer;
        int8_t huP = (aiP == 1) ? 2 : 1;
        int bestScore = -30000, bestCol = 3;

        for (int i = 0; i < aiJob.searchCount; i++) {
            int c = aiJob.searchCols[i];
            if (aiJob.abort) break;
            int r = getFirstEmptyRowB(aiJob.boardCopy, c);
            if (r == -1) continue;
            aiJob.boardCopy[c][r] = aiP;
            int score = minimaxB(aiJob.boardCopy, aiJob.ply, -30000, 30000,
                                 false, aiP, huP, aiJob.abort);
            aiJob.boardCopy[c][r] = 0;
            if (score > bestScore) { bestScore = score; bestCol = c; }
        }

        aiJob.bestScore = bestScore;
        aiJob.bestCol = bestCol;
        aiJob.done = true;
        aiJob.type = JOB_NONE;

    } else if (aiJob.type == JOB_PONDER) {
        int8_t aiP = aiJob.aiPlayer;
        int8_t huP = (aiP == 1) ? 2 : 1;

        for (int ci = 0; ci < COLS; ci++) {
            int c = colOrder[ci];
            if (aiJob.abort) break;

            int8_t localBoard[COLS][ROWS];
            memcpy(localBoard, aiJob.boardCopy, sizeof(localBoard));

            int r = getFirstEmptyRowB(localBoard, c);
            if (r == -1) continue;

            localBoard[c][r] = huP;
            if (scanBoardB(localBoard) != 0 || isBoardFullB(localBoard)) {
                aiJob.ponderValid[c] = true;
                aiJob.ponderResults[c] = 3;
                continue;
            }

            int8_t best = computeAiMoveB(localBoard, aiP, aiJob.ply,
                                         true, aiJob.abort);
            if (!aiJob.abort) {
                aiJob.ponderResults[c] = best;
                aiJob.ponderValid[c] = true;
            }
        }
        aiJob.done = true;
        aiJob.type = JOB_NONE;
    }
}
