/*
 * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 * 
 * ============================================================================
 * REACTIVE GAME TEMPLATE - Event-Driven Architecture for RP2350
 * ============================================================================
 * 
 * This template provides a clean, reactive architecture for building games
 * and interactive applications on Raspberry Pi Pico with displays.
 * 
 * KEY FEATURES:
 * - Event-driven: Display only updates when input is received
 * - Power efficient: Uses __wfi() to sleep between inputs
 * - E-ink optimized: Minimizes screen refreshes
 * - Interrupt-driven: Touch and button handling via interrupts
 * - Modular: Clear separation of input, game logic, and rendering
 * 
 * ARCHITECTURE:
 * 1. Interrupt handlers set flags (kept minimal)
 * 2. Main loop processes input events
 * 3. Game logic updates state based on events
 * 4. Screen refreshes only when changes occur
 * 
 * HOW TO CREATE YOUR OWN GAME:
 * ============================================================================
 * 1. Modify GameState structure with your game variables
 * 2. Implement game_init() to set initial values
 * 3. Implement game_update() to handle input and update state
 * 4. Implement game_draw() to render your game graphics
 * 5. Adjust GameConfig for your game's needs
 * 6. The reactive loop and input system work automatically!
 * ============================================================================
 */

#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/sync.h"
#include "pico/multicore.h"
#include "board_config.h"  // Board-specific pin configuration
#include "sd_card.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "display/low_level_render.h"
#include "display/low_level_gui.h"
#include "display/low_level_display.h"
#include "display/low_level_display_epaper.h"
#include "display/low_level_touch.h"


// Binary info for RP2350 - ensures proper boot image structure
bi_decl(bi_program_description("4.0\" TFT ST7796 with Touch and SD Card Demo"));
bi_decl(bi_program_version_string("0.1"));
bi_decl(bi_program_build_date_string(__DATE__));

// ============================================================================
// DUAL-CORE DISPLAY REFRESH SYSTEM
// ============================================================================

// Shared variables for core communication
volatile bool refresh_requested = false;
volatile bool refresh_in_progress = false;
const uint8_t* volatile refresh_buffer = nullptr;
LowLevelDisplay* volatile refresh_display = nullptr;

/**
 * @brief Core 1 entry point - handles display refresh operations
 * 
 * Runs on the second core, waiting for refresh requests.
 * This keeps Core 0 responsive while display updates happen in background.
 */
void core1_entry() {
    printf("Core 1 started - handling display refreshes\n");
    
    while (1) {
        // Wait for refresh request
        if (refresh_requested && refresh_buffer && refresh_display) {
            refresh_in_progress = true;
            
            // Get local copies for safe access
            LowLevelDisplay* display = refresh_display;
            const uint8_t* buffer = refresh_buffer;
            
            // Perform the refresh operation (may be slow for e-ink)
            display->draw_buffer(buffer);
            display->refresh();
            
            // Clear flags
            refresh_requested = false;
            refresh_in_progress = false;
        }
        
        // Small delay to avoid busy-waiting
        sleep_us(100);
    }
}

/**
 * @brief Request a screen refresh (non-blocking)
 * 
 * Queues the refresh on Core 1, keeping Core 0 responsive.
 * 
 * @param buffer Pointer to 1-bit framebuffer
 * @param display Pointer to display abstraction
 * @return true if refresh started, false if already in progress
 */
bool refresh_screen_async(const uint8_t *buffer, LowLevelDisplay* display) {
    // Check if Core 1 is busy with previous refresh
    if (refresh_in_progress) {
        // Still refreshing previous frame, skip this one
        return false;
    }
    
    // Queue refresh on Core 1
    refresh_buffer = buffer;
    refresh_display = display;
    refresh_requested = true;
    
    return true;
}

/**
 * @brief Check if a refresh is currently in progress
 * @return true if Core 1 is still refreshing
 */
bool is_refresh_in_progress() {
    return refresh_in_progress;
}

// ============================================================================
// INPUT EVENT STRUCTURES
// ============================================================================

// Input event types
enum InputType {
    INPUT_NONE = 0,
    INPUT_TOUCH_DOWN,
    INPUT_TOUCH_MOVE,
    INPUT_TOUCH_UP,
    INPUT_BUTTON_0,
    INPUT_BUTTON_1,
    INPUT_GESTURE
};

// Unified input event structure
struct InputEvent {
    InputType type;
    int16_t x;
    int16_t y;
    uint8_t gesture_code;  // For gesture events
    uint8_t button_id;     // For button events
    uint8_t pressure;      // Touch pressure/weight
    bool valid;            // Set to true if event is valid
};

// ============================================================================
// GAME STATE AND CONFIGURATION
// ============================================================================

// Game state - customize this for your game
struct GameState {
    // Tic-Tac-Toe game state
    uint8_t board[3][3];     // 0=empty, 1=X, 2=O
    uint8_t current_player;  // 1=X, 2=O
    uint8_t winner;          // 0=none, 1=X wins, 2=O wins, 3=tie
    uint8_t selected_row;    // Currently selected cell
    uint8_t selected_col;
    bool game_over;
    
    // Game statistics
    uint32_t x_wins;
    uint32_t o_wins;
    uint32_t ties;
    uint32_t total_moves;
};

// Game configuration - adjust these for your game
struct GameConfig {
    uint32_t touch_debounce_ms;    // Touch polling rate
    uint32_t button_debounce_ms;   // Button debounce delay
    bool enable_gestures;          // Enable gesture recognition
    bool enable_continuous_draw;   // Allow continuous drawing while touched
    bool debug_verbose;            // Print debug messages
};

// ============================================================================
// INTERRUPT HANDLERS (Keep these minimal!)
// ============================================================================

// Touch interrupt handling
volatile bool touch_interrupt_flag = false;
volatile bool touch_event_down = false;
LowLevelTouch* touch = nullptr;

// Button interrupt handling
#ifdef BUTTON_KEY0_PIN
volatile bool button_key0_pressed = false;
volatile bool button_key1_pressed = false;
#endif

/**
 * @brief Touch interrupt callback handler
 * 
 * Called automatically by hardware when INT pin changes state:
 * - Falling edge: Touch detected (INT goes LOW)
 * - Rising edge: Touch released (INT goes HIGH)
 * 
 * This runs in interrupt context, so keep it fast - just set a flag
 * 
 * @param gpio GPIO pin number that triggered the interrupt
 * @param events Event mask (GPIO_IRQ_EDGE_FALL and/or GPIO_IRQ_EDGE_RISE)
 */
void touch_interrupt_handler(uint gpio, uint32_t events) {
    // Set flag to indicate touch event occurred
    // Main loop will handle the actual touch reading
    touch_interrupt_flag = true;
    
    // Track which edge triggered (down vs up)
    if (events & GPIO_IRQ_EDGE_FALL) { 
        touch_event_down = true;
        printf("INT: FALL\n");
    }
    if (events & GPIO_IRQ_EDGE_RISE) { 
        touch_event_down = false;
        printf("INT: RISE\n");
    }
}

#ifdef BUTTON_KEY0_PIN
/**
 * @brief Button interrupt callback handler
 * 
 * Called automatically by hardware when button pins change state.
 * Buttons are active LOW (pressed = 0, released = 1) with pull-ups.
 * 
 * This runs in interrupt context, so keep it fast - just set flags.
 * 
 * @param gpio GPIO pin number that triggered the interrupt
 * @param events Event mask (GPIO_IRQ_EDGE_FALL and/or GPIO_IRQ_EDGE_RISE)
 */
void button_interrupt_handler(uint gpio, uint32_t events) {
    // Only respond to falling edge (button press)
    if (events & GPIO_IRQ_EDGE_FALL) {
        if (gpio == BUTTON_KEY0_PIN) {
            button_key0_pressed = true;
        }
#ifdef BUTTON_KEY1_PIN
        else if (gpio == BUTTON_KEY1_PIN) {
            button_key1_pressed = true;
        }
#endif
    }
}
#endif

// Screen dimensions and configuration from board_config.h
const int V_WIDTH = DISPLAY_WIDTH;
const int V_HEIGHT = DISPLAY_HEIGHT;

// Touch indicator settings
#define TOUCH_RADIUS 10

uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8];

/**
 * @brief Refresh the screen with the 1-bit buffer
 * 
 * Displays work directly with 1-bit monochrome buffers.
 * The display driver internally converts to its native format (RGB565, etc.)
 * 
 * @param buffer Pointer to 1-bit framebuffer (width*height/8 bytes)
 * @param display Pointer to display abstraction layer
 */
void refresh_screen(const uint8_t *buffer, LowLevelDisplay* display) {
    display->draw_buffer(buffer);
    display->refresh();
}

// ============================================================================
// INPUT PROCESSING
// ============================================================================

/**
 * @brief Get human-readable gesture name
 * 
 * @param gesture_code Gesture code from touch controller
 * @return Constant string with gesture name
 */
const char* get_gesture_name(uint8_t gesture_code) {
    switch(gesture_code) {
        case 0x10: return "Move Up";
        case 0x14: return "Move Right";
        case 0x18: return "Move Down";
        case 0x1C: return "Move Left";
        case 0x48: return "Zoom In";
        case 0x49: return "Zoom Out";
        default: return "Unknown";
    }
}

/**
 * @brief Process touch input and convert to InputEvent
 * 
 * Reads touch data from controller and creates appropriate InputEvent.
 * Handles debouncing and filtering internally.
 * 
 * @param config Game configuration
 * @param last_time Pointer to last touch time for debouncing
 * @return InputEvent structure (valid=false if no valid input)
 */
InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time) {
    InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false};
    
    // Check if touch interrupt flag is set
    if (!touch_interrupt_flag) {
        return event;  // No touch event
    }
    
    printf("Processing touch: flag=%d, event_down=%d\n", touch_interrupt_flag, touch_event_down);
    
    // Don't clear the flag yet - we may still be processing continuous touch
    
    // Check if touch is active
    if (!touch_event_down) {
        // Touch released - reset timing for next touch
        touch_interrupt_flag = false;
        *last_time = 0;  // Reset so next touch is treated as new touch-down
        event.type = INPUT_TOUCH_UP;
        event.valid = true;
        printf("Touch UP\n");
        return event;
    }
    
    // Touch is down - check debounce timing
    uint32_t now = to_ms_since_boot(get_absolute_time());
    if (now - *last_time < config.touch_debounce_ms) {
        return event;  // Too soon, skip
    }
    
    // Read touch data
    TouchData touch_data;
    if (!touch || !touch->read_touch(&touch_data)) {
        // Clear flag even if read failed to prevent getting stuck
        touch_interrupt_flag = false;
        printf("Touch read FAILED\n");
        return event;  // Read failed
    }
    
    // Clear the interrupt flag after successfully reading touch data
    // This allows the next touch interrupt to be detected
    touch_interrupt_flag = false;
    
    printf("Touch DOWN at (%d,%d)\n", touch_data.points[0].x, touch_data.points[0].y);
    
    // Populate event structure
    event.x = touch_data.points[0].x;
    event.y = touch_data.points[0].y;
    event.pressure = touch_data.points[0].pressure;
    event.gesture_code = touch_data.gesture;
    event.valid = true;
    
    // Determine event type
    if (*last_time == 0) {
        event.type = INPUT_TOUCH_DOWN;
    } else {
        event.type = INPUT_TOUCH_MOVE;
    }
    
    // Handle gesture events
    if (config.enable_gestures && touch_data.gesture != 0) {
        event.type = INPUT_GESTURE;
        if (config.debug_verbose) {
            printf("Gesture: 0x%02X (%s)\n", event.gesture_code, get_gesture_name(event.gesture_code));
        }
    }
    
    *last_time = now;
    return event;
}

/**
 * @brief Process button input and convert to InputEvent
 * 
 * Checks button flags and verifies button state with debouncing.
 * Clears flags after processing.
 * 
 * @param config Game configuration
 * @return InputEvent structure (valid=false if no valid input)
 */
InputEvent process_button_input(const GameConfig& config) {
    InputEvent event = {INPUT_NONE, 0, 0, 0, 0, 0, false};
    
#ifdef BUTTON_KEY0_PIN
    // Check KEY0
    if (button_key0_pressed) {
        button_key0_pressed = false;
        sleep_ms(config.button_debounce_ms);
        
        if (gpio_get(BUTTON_KEY0_PIN) == 0) {  // Verify still pressed
            event.type = INPUT_BUTTON_0;
            event.button_id = 0;
            event.valid = true;
            
            if (config.debug_verbose) {
                printf("Button KEY0 action triggered\n");
            }
            return event;
        }
    }
    
#ifdef BUTTON_KEY1_PIN
    // Check KEY1
    if (button_key1_pressed) {
        button_key1_pressed = false;
        sleep_ms(config.button_debounce_ms);
        
        if (gpio_get(BUTTON_KEY1_PIN) == 0) {  // Verify still pressed
            event.type = INPUT_BUTTON_1;
            event.button_id = 1;
            event.valid = true;
            
            if (config.debug_verbose) {
                printf("Button KEY1 action triggered\n");
            }
            return event;
        }
    }
#endif
#endif
    
    return event;
}

// ============================================================================
// GAME LOGIC (Customize this section for your game!)
// ============================================================================

// Game board dimensions (used for both drawing and touch detection)
const int BOARD_SIZE = 200;
const int CELL_SIZE = BOARD_SIZE / 3;
const int BOARD_Y = 80;  // Y position below title

/**
 * @brief Check if there's a winner on the board
 * @return 0=no winner, 1=X wins, 2=O wins, 3=tie
 */
uint8_t check_winner(const GameState* state) {
    // Check rows
    for (int row = 0; row < 3; row++) {
        if (state->board[row][0] != 0 && 
            state->board[row][0] == state->board[row][1] && 
            state->board[row][1] == state->board[row][2]) {
            return state->board[row][0];
        }
    }
    
    // Check columns
    for (int col = 0; col < 3; col++) {
        if (state->board[0][col] != 0 && 
            state->board[0][col] == state->board[1][col] && 
            state->board[1][col] == state->board[2][col]) {
            return state->board[0][col];
        }
    }
    
    // Check diagonals
    if (state->board[0][0] != 0 && 
        state->board[0][0] == state->board[1][1] && 
        state->board[1][1] == state->board[2][2]) {
        return state->board[0][0];
    }
    
    if (state->board[0][2] != 0 && 
        state->board[0][2] == state->board[1][1] && 
        state->board[1][1] == state->board[2][0]) {
        return state->board[0][2];
    }
    
    // Check for tie (board full)
    bool board_full = true;
    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            if (state->board[row][col] == 0) {
                board_full = false;
                break;
            }
        }
        if (!board_full) break;
    }
    
    if (board_full) return 3; // Tie
    return 0; // No winner yet
}

/**
 * @brief Initialize game state
 * 
 * Called once at startup to set initial game values.
 * Customize this for your game.
 * 
 * @param state Pointer to GameState to initialize
 */
void game_init(GameState* state) {
    // Clear the board
    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            state->board[row][col] = 0;
        }
    }
    
    state->current_player = 1;  // X starts
    state->winner = 0;
    state->selected_row = 1;    // Start in center
    state->selected_col = 1;
    state->game_over = false;
    state->total_moves = 0;
    
    // Keep win statistics across games
    // state->x_wins, state->o_wins, state->ties remain unchanged
}

/**
 * @brief Update game state based on input event
 * 
 * This is where your game logic goes.
 * Called whenever an input event occurs.
 * 
 * @param state Pointer to GameState to update
 * @param input Input event to process
 * @param config Game configuration
 * @param renderer Renderer for drawing operations
 * @return true if screen needs refresh (drawing occurred)
 */
bool game_update(GameState* state, const InputEvent& input, const GameConfig& config, LowLevelRenderer* renderer) {
    bool needs_refresh = false;
    
    switch (input.type) {
        case INPUT_TOUCH_DOWN: {
            // If game is over, restart on touch
            if (state->game_over) {
                game_init(state);
                needs_refresh = true;
                break;
            }

            printf("Touch down at (%d,%d)\n", input.x, input.y);
            
            // Calculate board position (must match game_draw!)
            int board_x = V_WIDTH - BOARD_SIZE - 20;
            
            // Check if touch is within board
            if (input.x >= board_x && input.x < board_x + BOARD_SIZE &&
                input.y >= BOARD_Y && input.y < BOARD_Y + BOARD_SIZE) {
                
                int touched_col = (input.x - board_x) / CELL_SIZE;
                int touched_row = (input.y - BOARD_Y) / CELL_SIZE;
                
                // Clamp to valid range (safety check)
                if (touched_row >= 0 && touched_row < 3 && touched_col >= 0 && touched_col < 3) {
                    // Place piece if cell is empty
                    if (state->board[touched_row][touched_col] == 0) {
                        state->board[touched_row][touched_col] = state->current_player;
                        state->total_moves++;
                        
                        // Check for winner
                        state->winner = check_winner(state);
                        if (state->winner != 0) {
                            state->game_over = true;
                            if (state->winner == 1) state->x_wins++;
                            else if (state->winner == 2) state->o_wins++;
                            else if (state->winner == 3) state->ties++;
                        } else {
                            // Switch player
                            state->current_player = (state->current_player == 1) ? 2 : 1;
                        }
                        
                        needs_refresh = true;
                        
                        if (config.debug_verbose) {
                            printf("Touch at [%d,%d] (pixel %d,%d) by player %d\n", 
                                   touched_row, touched_col, input.x, input.y,
                                   state->current_player == 1 ? 2 : 1);
                        }
                    } else if (config.debug_verbose) {
                        printf("Cell [%d,%d] already occupied\n", touched_row, touched_col);
                    }
                }
            } else if (config.debug_verbose) {
                printf("Touch outside board: %d,%d\n", input.x, input.y);
            }
            break;
        }
            
        case INPUT_BUTTON_0:
            // KEY0: Move selection (for button-only boards)
            if (!state->game_over) {
                // Move to next cell, skipping occupied ones
                int attempts = 0;
                do {
                    state->selected_col++;
                    if (state->selected_col > 2) {
                        state->selected_col = 0;
                        state->selected_row++;
                        if (state->selected_row > 2) {
                            state->selected_row = 0;
                        }
                    }
                    attempts++;
                    // If we've tried all 9 cells, stop (game might be full)
                    if (attempts >= 9) break;
                } while (state->board[state->selected_row][state->selected_col] != 0);
                
                needs_refresh = true;
                if (config.debug_verbose) {
                    printf("Selection moved to [%d,%d]\n", state->selected_row, state->selected_col);
                }
            } else {
                // Restart game
                game_init(state);
                needs_refresh = true;
            }
            break;
            
        case INPUT_BUTTON_1:
            // KEY1: Place piece at selected position
            if (!state->game_over) {
                if (state->board[state->selected_row][state->selected_col] == 0) {
                    state->board[state->selected_row][state->selected_col] = state->current_player;
                    state->total_moves++;
                    
                    // Check for winner
                    state->winner = check_winner(state);
                    if (state->winner != 0) {
                        state->game_over = true;
                        if (state->winner == 1) state->x_wins++;
                        else if (state->winner == 2) state->o_wins++;
                        else if (state->winner == 3) state->ties++;
                    } else {
                        // Switch player
                        state->current_player = (state->current_player == 1) ? 2 : 1;
                    }
                    
                    needs_refresh = true;
                    
                    if (config.debug_verbose) {
                        printf("Piece placed at [%d,%d]\n", state->selected_row, state->selected_col);
                    }
                }
            }
            break;
            
        default:
            break;
    }
    
    return needs_refresh;
}

/**
 * @brief Draw game graphics to screen buffer
 * 
 * All initial UI drawing operations go here.
 * Called once at startup to create the initial screen.
 * 
 * @param state Pointer to current GameState
 * @param renderer Renderer for drawing primitives
 * @param gui GUI system for widgets (optional)
 */
void game_draw(const GameState* state, LowLevelRenderer* renderer, LowLevelGUI* gui) {
    // Draw main window
    LowLevelWindow *w1 = gui->draw_new_window(10, 10, V_WIDTH - 20, V_HEIGHT - 20, "Tic-Tac-Toe");
    
    renderer->set_font(&font_5x5_obj);
    
    // Draw current player or game result
    if (state->game_over) {
        if (state->winner == 1) {
            renderer->draw_string(20, 40, "X WINS!", true);
        } else if (state->winner == 2) {
            renderer->draw_string(20, 40, "O WINS!", true);
        } else {
            renderer->draw_string(20, 40, "TIE GAME!", true);
        }
        renderer->draw_string(20, 55, "Touch or KEY0 to restart", true);
    } else {
        char turn_text[30];
        snprintf(turn_text, sizeof(turn_text), "Turn: %s", state->current_player == 1 ? "X" : "O");
        renderer->draw_string(20, 40, turn_text, true);
        renderer->draw_string(20, 55, "Touch cell or use keys", true);
    }
    
    // Draw game board (use same layout as touch detection!)
    int board_x = V_WIDTH - BOARD_SIZE - 20;
    
    // Draw current turn indicator (large, on left side)
    if (!state->game_over) {
        int indicator_x = 60;
        int indicator_y = V_HEIGHT / 2;
        int piece_offset = CELL_SIZE / 4;
        
        if (state->current_player == 1) {
            // Draw large X
            renderer->draw_line(indicator_x - piece_offset, indicator_y - piece_offset,
                              indicator_x + piece_offset, indicator_y + piece_offset, true, 4);
            renderer->draw_line(indicator_x + piece_offset, indicator_y - piece_offset,
                              indicator_x - piece_offset, indicator_y + piece_offset, true, 4);
        } else {
            // Draw large O
            int piece_radius = CELL_SIZE / 4;
            renderer->draw_circle(indicator_x, indicator_y, piece_radius, true);
            renderer->draw_circle(indicator_x, indicator_y, piece_radius - 1, true);
            renderer->draw_circle(indicator_x, indicator_y, piece_radius - 2, true);
            renderer->draw_circle(indicator_x, indicator_y, piece_radius - 3, true);
        }
    }
        // Draw grid lines
    for (int i = 1; i < 3; i++) {
        // Vertical lines
        renderer->draw_line(board_x + i * CELL_SIZE, BOARD_Y, 
                          board_x + i * CELL_SIZE, BOARD_Y + BOARD_SIZE, true, 2);
        // Horizontal lines
        renderer->draw_line(board_x, BOARD_Y + i * CELL_SIZE,
                          board_x + BOARD_SIZE, BOARD_Y + i * CELL_SIZE, true, 2);
    }
    
    // Draw outer border
    renderer->draw_rectangle(board_x, BOARD_Y, BOARD_SIZE, BOARD_SIZE, true, 3);
    
    // Draw X's and O's
    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            int cell_x = board_x + col * CELL_SIZE;
            int cell_y = BOARD_Y + row * CELL_SIZE;
            int center_x = cell_x + CELL_SIZE / 2;
            int center_y = cell_y + CELL_SIZE / 2;
            
            // Highlight selected cell (for button navigation)
            if (!state->game_over && row == state->selected_row && col == state->selected_col) {
                renderer->draw_rectangle(cell_x + 5, cell_y + 5, CELL_SIZE - 10, CELL_SIZE - 10, true, 1);
            }
            
            if (state->board[row][col] == 1) {
                // Draw X
                int offset = CELL_SIZE / 4;
                renderer->draw_line(center_x - offset, center_y - offset,
                                  center_x + offset, center_y + offset, true, 3);
                renderer->draw_line(center_x + offset, center_y - offset,
                                  center_x - offset, center_y + offset, true, 3);
            } else if (state->board[row][col] == 2) {
                // Draw O
                int radius = CELL_SIZE / 4;
                renderer->draw_circle(center_x, center_y, radius, true);
                renderer->draw_circle(center_x, center_y, radius - 1, true);
                renderer->draw_circle(center_x, center_y, radius - 2, true);
            }
        }
    }
    
    // Draw statistics at bottom
    char stats[60];
    snprintf(stats, sizeof(stats), "X:%d  O:%d  Tie:%d  Moves:%d", 
             state->x_wins, state->o_wins, state->ties, state->total_moves);
    renderer->draw_string(20, V_HEIGHT - 40, stats, true);
}

// ============================================================================
// MAIN PROGRAM
// ============================================================================




int main()
{
    // Initialize standard I/O for debugging with timeout
    // This prevents hanging when USB is not connected
    stdio_init_all();
    sleep_ms(5000);  // Wait for USB connection (if present)
    
    printf("\n=== %s Demo ===\n", BOARD_NAME);
    printf("Starting dual-core system...\n");
    
    // Create display abstraction using factory method
    // The factory handles all board-specific configuration internally
    LowLevelDisplay* display = LowLevelDisplay::create((DisplayType)DISPLAY_TYPE_SELECTED, V_WIDTH, V_HEIGHT);
    
    if (!display) {
        printf("Failed to create display!\n");
        return -1;
    }
    
    printf("Initializing display...\n");
    
    // Initialize the display
    if (!display->init()) {
        printf("Display initialization failed!\n");
        delete display;
        return -1;
    }
    
    // Launch Core 1 for display refresh handling
    printf("Launching Core 1 for display refresh...\n");
    multicore_launch_core1(core1_entry);
    sleep_ms(100);  // Give Core 1 time to start
    
    // Do a full refresh with white screen first (removes ghosting on e-paper)
    printf("Performing initial full refresh to white...\n");
    display->clear(true);  // Clear to white
    
    // For e-paper, do a full refresh to ensure clean display
    if (display->get_type() == DISPLAY_TYPE_EPAPER) {
        LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
        epaper->full_refresh();  // Full refresh removes ghosting
        printf("Full refresh complete\n");
    } else {
        refresh_screen(bit_buffer, display);  // For TFT, just refresh normally
    }
    
    // Now clear to black for drawing
    display->clear(false);  // Clear to black

    // Initialize renderer and GUI system
    LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT);
    renderer.set_font(&font_5x5_obj);
    LowLevelGUI gui = LowLevelGUI(&renderer, font_BMplain_obj);
    
    // Initialize game configuration
    GameConfig config = {
        .touch_debounce_ms = 10,
        .button_debounce_ms = 20,
        .enable_gestures = true,
        .enable_continuous_draw = true,
        .debug_verbose = false
    };
    
    // Initialize game state
    GameState game_state;
    // Initialize statistics (persists across game restarts)
    game_state.x_wins = 0;
    game_state.o_wins = 0;
    game_state.ties = 0;
    game_init(&game_state);
    
    // Draw initial game graphics
    game_draw(&game_state, &renderer, &gui);
    
    // Refresh the screen with the rendered GUI (async on Core 1)
    refresh_screen_async(bit_buffer, display);
    printf("Initial screen refresh queued on Core 1\n");

    // Initialize touch screen using abstraction
    touch = LowLevelTouch::create((TouchType)TOUCH_TYPE_SELECTED, V_WIDTH, V_HEIGHT,
                                                 TOUCH_SWAP_XY, TOUCH_INVERT_X, TOUCH_INVERT_Y);
    
    if (touch) {
        printf("Touch initialized successfully\n");
        
        // Set up interrupt-driven touch detection
        printf("Setting up touch interrupt callback...\n");
        touch->set_interrupt_callback(touch_interrupt_handler);
        printf("Touch interrupt enabled on INT pin (falling and rising edges)\n");
        
        // Run communication test if available
        // Note: Commented out as it may hang on some hardware configurations
        printf("\nRunning touch reliability test...\n");
        touch->test_communication();
        printf("...\n");
    } else {
        printf("Touch initialization failed or not configured\n");
    }
    
#ifdef BUTTON_KEY0_PIN
    // Initialize hardware buttons (e-ink board only)
    printf("\nInitializing hardware buttons...\n");
    
    // Initialize KEY0 button
    gpio_init(BUTTON_KEY0_PIN);
    gpio_set_dir(BUTTON_KEY0_PIN, GPIO_IN);
    gpio_pull_up(BUTTON_KEY0_PIN);  // Active LOW with pull-up
    printf("  KEY0 initialized on GP%d (active LOW)\n", BUTTON_KEY0_PIN);
    
#ifdef BUTTON_KEY1_PIN
    // Initialize KEY1 button
    gpio_init(BUTTON_KEY1_PIN);
    gpio_set_dir(BUTTON_KEY1_PIN, GPIO_IN);
    gpio_pull_up(BUTTON_KEY1_PIN);  // Active LOW with pull-up
    printf("  KEY1 initialized on GP%d (active LOW)\n", BUTTON_KEY1_PIN);
#endif
    
    // Enable interrupts on falling edge (button press)
    gpio_set_irq_enabled_with_callback(BUTTON_KEY0_PIN, 
                                       GPIO_IRQ_EDGE_FALL, 
                                       true, 
                                       &button_interrupt_handler);
#ifdef BUTTON_KEY1_PIN
    gpio_set_irq_enabled(BUTTON_KEY1_PIN, GPIO_IRQ_EDGE_FALL, true);
#endif
    
    printf("Button interrupts enabled (falling edge = press)\n");
#endif
    
    // Test SD card and FatFS
    // if (sd_card_init_with_board_config()) {
    //    sd_card_test_fatfs();
    // } else {
    //    printf("SD Card initialization failed or no card present\n");
    // }
    
    // ========================================================================
    // REACTIVE GAME LOOP WITH DUAL-CORE REFRESH
    // ========================================================================
    // Core 0 (this loop): Handles input and game logic - stays responsive
    // Core 1: Handles display refresh - can take 1-2 seconds for e-ink
    // 
    // The loop sleeps until an interrupt occurs, then:
    // 1. Process input (button or touch)
    // 2. Update game state based on input
    // 3. Queue refresh on Core 1 (non-blocking)
    // This keeps Core 0 responsive even during slow e-ink refreshes
    // ========================================================================
    
    uint32_t last_touch_time = 0;
    bool pending_refresh = false;  // Track if we have a pending refresh
    
    printf("\nEntering reactive game loop (Core 0 - input & logic)\n");
    printf("Display refreshes handled by Core 1\n\n");
    
    while (1) {
        // Sleep until interrupt wakes us up (very power efficient!)
        __wfi();  // Wait For Interrupt - CPU sleeps until any interrupt occurs
        
        InputEvent input = {INPUT_NONE, 0, 0, 0, 0, 0, false};
        bool needs_refresh = false;
        
        // 1. Process button input first (higher priority)
        input = process_button_input(config);
        if (input.valid) {
            needs_refresh = game_update(&game_state, input, config, &renderer);
        }
        
        // 2. Process touch input (if no button was pressed)
        if (!input.valid) {
            input = process_touch_input(config, &last_touch_time);
            if (input.valid) {
                needs_refresh = game_update(&game_state, input, config, &renderer);
            }
        }
        
        // 3. Redraw and queue async refresh on Core 1
        if (needs_refresh || pending_refresh) {
            // Clear buffer and redraw entire UI with updated state
            memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8);
            game_draw(&game_state, &renderer, &gui);
            
            // Request async refresh (non-blocking - handled by Core 1)
            bool refresh_started = refresh_screen_async(bit_buffer, display);
            
            if (refresh_started) {
                pending_refresh = false;  // Refresh queued successfully
            } else {
                pending_refresh = true;   // Core 1 busy, retry next iteration
                if (config.debug_verbose) {
                    printf("Refresh pending - Core 1 still busy\n");
                }
            }
            
            // Core 0 continues immediately, Core 1 handles the refresh
        }
    }
    
    return 0;
}
