#include "Game.h"

#ifdef ALLEGRO4
// The number of ticks that have been counted.
volatile int ticks_ = 0;

// The number of frames per second.
int fps_ = 0;

// Enables displaying the FPS count on the top left of the screen.
u8 displayFpsCounter_ = FALSE;

// Ticker for ensuring the game runs at a consistent speed.
void ticker(void)
{
    ++ticks_;
}
END_OF_FUNCTION(ticker)
#endif

void runGameSession()
{
#ifdef CPC

    // Clear everything off the screen.
    cpct_clearScreen(0);

#elif ALLEGRO4

    // Clear everything off the screen.
    clear_to_color(screen, makecol(25, 54, 78));

    // Enables the FPS counter at the top left of the screen.
    displayFpsCounter_ = get_config_int(NULL, "display_fps_counter", 0);

#elif ALLEGRO5

    // Clear everything off the screen.
    al_clear_to_color(al_map_rgb(0, 0, 128));
    al_flip_display();

#endif

    // Reset game state values for a new game.
    resetGameSessionState();

    // Loop while level state is ready.
    while (!levelState)
    {
        u8 levelStartX;
        u8 levelStartY;

        // Resets the level score to the current game score (0 at the start of the game session)
        levelScore = gameScore;

        // Resets the palette on starting the level.
        initPalette();

        // Initialise levelStart variables to the start of the selected level.
        levelStartX = level_start_position[currentMap][0];
        levelStartY = level_start_position[currentMap][1];

        // Initialises the map grid for the level.
        initMap(currentMap);

        // Initialise the player values, with map position.
        initPlayer(levelStartX, levelStartY);

        // Clear level inventory on level start.
        inventoryItems = 0;

        // Resets any previously down keys before starting the level.
        gameControls.controlFlags = 0;
        actionKeyFlag = FALSE;

        // Set the game to running for the game loop.
        gameRunning = TRUE;

        // Draw the hud on the bottom of the screen.
        hudRedraw = TRUE;

        // Run the actual game including the game loop.
        runGame();

        if (levelState == LEVEL_COMPLETE)
        {
            // Update current map to prepare to move to the next level.
            ++currentMap;

            // Finished the level so update the overall game score.
            gameScore = levelScore;

            // Check if all the levels have been completed.
            if (currentMap == LEVEL_COUNT)
            {
                // If currentMap is the last level then game complete
                levelState = LEVEL_GAME_COMPLETE;
            }
            else
            {
                // Move to the next level.
                levelState = LEVEL_READY;
                gameRunning = TRUE;
            }
        }
        else if (levelState == LEVEL_EXIT)
        {
            // Update the game score when exiting the level using escape.
            gameScore = levelScore;
        }
        else if (levelState == LEVEL_DEATH)
        {
            if (playerLives)
            {
                // Player has died reload the current map.
                levelState = LEVEL_READY;
                gameRunning = TRUE;
            }
            else
            {
                // Update the game score to the level score after dying part way through the level.
                gameScore = levelScore;

                // Player no longer has any lives so game over.
                levelState = LEVEL_GAME_OVER;
            }
        }
    }
}

void runGame()
{
    u8 logicFlag = 0;

#ifndef CPC
    const u8 logicSpeed = 60;
#endif

#ifdef ALLEGRO4

    // This is used as a speed counter to ensure a consistent speed.
    int ntick;

    // The number of frames that have been drawn.
    int frames = 0;

    // The number of frames left in this second.
    int didTicks = 0;

    fps_ = 0;

    LOCK_VARIABLE(ticks_);
    LOCK_FUNCTION(ticker);

    // Setup a timer with 60 ticks per second.
    install_int_ex(ticker, BPS_TO_TIMER(logicSpeed));

#elif ALLEGRO5

    ALLEGRO_TIMER* timer = al_create_timer(1.0 / logicSpeed);
    ALLEGRO_EVENT allegroEvent;

    ALLEGRO_EVENT_QUEUE* gameEventQueue = al_create_event_queue();
    al_register_event_source(gameEventQueue, al_get_keyboard_event_source());
    al_register_event_source(gameEventQueue, al_get_timer_event_source(timer));
    al_register_event_source(gameEventQueue, al_get_display_event_source(al_get_current_display()));

    // Start the timer that will ensure the logic is at a consistent speed.
    al_start_timer(timer);

#endif

    while (gameRunning)
    {
#ifdef CPC

        // Read input from the player.
        readInput();

        // Only run logic every second time.
        if (logicFlag++)
        {
            // Run the game logic which will change what is displayed.
            gameLogic();

            // Reset flag
            logicFlag = 0;
        }

        // Wait for vsync.
        cpct_waitVSYNC();

        // Draws the game on the screen.
        drawGame();

#elif ALLEGRO4

        ntick = ticks_;
        ticks_ = 0;

        while (ntick > 0)
        {
            // Read input from the player.
            readInput();

            // Only run logic every second time.
            if (logicFlag++)
            {
                // Run the game logic which will change what is displayed.
                gameLogic();

                // Reset flag
                logicFlag = 0;
            }

            ++didTicks;

            // There are 60 ticks in a second so we want to update the fps count every second.
            if (didTicks == 60)
            {
                // Update the FPS with the number of frames drawn in a second.
                fps_ = frames;
                didTicks = 0;
                frames = 0;
            }

            --ntick;
        }

        // Draws the game on the screen.
        drawGame();

        // Every time we draw the game that is a frame in the FPS count.
        ++frames;

#elif ALLEGRO5
        al_wait_for_event(gameEventQueue, &allegroEvent);

        // Read input from the player.
        readInput(gameEventQueue, &allegroEvent);

        // Only run logic every second time.
        if (logicFlag++)
        {
            // Run the game logic which will change what is displayed.
            gameLogic();

            // Reset flag
            logicFlag = 0;
        }

        // Draws the game on the screen.
        drawGame();

        al_flip_display();
#endif
    }

#ifdef ALLEGRO4
    remove_int(ticker);
#endif
}

#ifdef ALLEGRO5
void readInput(ALLEGRO_EVENT_QUEUE* eventQueue, ALLEGRO_EVENT* allegroEvent)
#else
void readInput()
#endif
{
#ifdef CPC
    // Reads the game controls from the player.
    readGameControls();
#elif ALLEGRO4
    // Reads the game controls from the player.
    readGameControlsAllegro4();
#else
    // Reads the game controls from the player.
    readGameControlsAllegro5(allegroEvent);
#endif

    // Check that the exit key has been pressed.
    if (gameControls.controlFlags & CONTROL_EXIT_FLAG)
    {
        // If the player chooses to press yes exit the game.
#ifdef CPC
        if (exitDialog())
#elif ALLEGRO4
        if (exitDialogAllegro4())
#else
        if (exitDialogAllegro5(eventQueue, allegroEvent))
#endif
        {
            gameRunning = FALSE;
            levelState = LEVEL_EXIT;
        }
        else
        {
            // Force a map redraw to cleanup after the dialog is closed.
            mapRedraw = TRUE;
        }
    }
}

void drawGame()
{
#ifdef ALLEGRO4
    acquire_screen();
#endif

    // Draws the map grid on the screen.
    drawMap();

    // Draw the player on the screen at their current position.
    drawPlayer();

    // Draw the hud at the bottom of the screen.
    drawHud();

#ifdef ALLEGRO4

    // Draw the framerate on the top of left the screen.
    // The counter is only updated once per second.
    if (displayFpsCounter_)
    {
        drawFps();
    }

    release_screen();

    // Vsync is only enabled from the configuration.
    if (get_config_int(NULL, "vsync", 0))
    {
        vsync();
    }

#endif
}

void gameLogic()
{
    // Move the player (update velocity).
    movePlayer();

    // Run the logic for the player (update position).
    logicPlayer();
}

#ifdef ALLEGRO4
void drawFps()
{
    u8 fpsText[6];
    convertNumberToString(fps_, fpsText);

    // Redraws the cells where the FPS text is being drawn for updates.
    drawCellAtPosition(0, 0);
    drawCellAtPosition(1, 0);

    if (fps_ > 999)
    {
        drawCellAtPosition(2, 0);
    }

    if (fps_ > 9999)
    {
        drawCellAtPosition(3, 0);
    }

    // Draws the framerate as text on the top left of the screen.
    drawString(4, 4, scaleFactor, fpsText, makecol(255, 255, 0));
}
#endif
