#include "Menu.h"

// Colour mode menu item text.
const u8* colourModeText_;
const u8 GREEN_MODE_TEXT_[14] = "6. green mode";
const u8 COLOUR_MODE_TEXT_[15] = "6. colour mode";

// Sound menu item text.
const u8* soundModeText_;
const u8 SOUND_OFF_TEXT_[13] = "7. sound off";
const u8 SOUND_ON_TEXT_[12] = "7. sound on";

// Difficulty level menu item text.
const u8* difficultyMenuText_;
const u8 EASY_DIFFICULTY_TEXT_[19] = "4. easy difficulty";
const u8 NORMAL_DIFFICULTY_TEXT_[21] = "4. normal difficulty";
const u8 HARD_DIFFICULTY_TEXT_[19] = "4. hard difficulty";

// Menu item number controls.
#define START_GAME_KEY_ Key_1
#define PASS_CODE_KEY Key_2
#define REBIND_CONTROLS_KEY_ Key_3
#define DIFFICULTY_KEY_ Key_4
#define HIGH_SCORES_KEY_ Key_5
#define COLOUR_MODE_KEY_ Key_6
#define SOUND_MODE_KEY_ Key_7

// User entered the wrong pass code.
#define INVALID_PASS_CODE 255

// High score variables.
#define SCORE_COUNT_ 5
#define MAX_NAME_LENGTH_ 10
u16 highScores_[SCORE_COUNT_];
u8 highScoreNames_[SCORE_COUNT_][11];

// The current mode for playing sound (on, sound effects, music and sound effects).
u8 soundMode = SOUND_MODE_OFF;

void runMenu()
{
    // Default graphics mode is colour mode.
    colourModeText_ = COLOUR_MODE_TEXT_;

    // Default to sound enabled but not turned on.
    soundModeText_ = SOUND_ON_TEXT_;
    soundMode = SOUND_MODE_OFF;

    // Set the default high score entries.
    initHighScores();

    // Set the default keys for the player.
    setDefaultKeys();

    // Default difficulty is normal difficulty.
    chosenDifficulty = NORMAL_DIFFICULTY;
    difficultyMenuText_ = NORMAL_DIFFICULTY_TEXT_;

    drawMenu();

    // Main menu loop.
    while (1)
    {
        if (cpct_isKeyPressed(START_GAME_KEY_))
        {
            // Game should default to starting at the first level.
            currentMap = 0;

            // Select the level for debugging purposes, to remove later.
            // No longer required following final release.
            //debugLevelSelector();

            // Player playing without code so is eligible for game complete bonus.
            passcodeUsed = FALSE;

            // Runs the full game session and displays victory or defeat based on outcome (starting at level 0).
            runFullGame();

            // Redraw the menu after finishing the game session.
            drawMenu();
        }
        else if (cpct_isKeyPressed(PASS_CODE_KEY))
        {
            // Attempt to select a map using the pass code selection screen.
            u8 selectedMap = enterPasscode();

            if (selectedMap != INVALID_PASS_CODE)
            {
                currentMap = selectedMap;

                // Player used a pass code and will not be eligible for game complete bonus.
                passcodeUsed = TRUE;

                // Runs the full game session and displays victory or defeat based on outcome (staring at the selected level).
                runFullGame();
            }

            // Redraw the menu after finishing the game session or getting the wrong code.
            drawMenu();
        }
        else if (cpct_isKeyPressed(REBIND_CONTROLS_KEY_))
        {
            // Display rebind controls menu which asks the user for their chosen controls.
            rebindControls();

            // Redraw the menu after leaving rebind controls menu.
            drawMenu();
        }
        else if (cpct_isKeyPressed(DIFFICULTY_KEY_))
        {
            chooseDifficulty();

            // Redraw the menu after updating the menu item text.
            drawMenu();
        }
        else if (cpct_isKeyPressed(HIGH_SCORES_KEY_))
        {
            displayHighScores();

            // Redraw the menu after leaving the high scores.
            drawMenu();
        }
        else if (cpct_isKeyPressed(COLOUR_MODE_KEY_))
        {
            switchColourMode();

            // Redraw the menu after updating the menu item text.
            drawMenu();
        }
        else if (cpct_isKeyPressed(SOUND_MODE_KEY_))
        {
            switchSoundMode();

            // Redraw the menu after updating the menu item text.
            drawMenu();
        }
    }
}

void drawMenu()
{
    // Clear the screen before drawing the menu.
    cpct_clearScreen(0);

    // Game title.
    drawString(18, 5, "fitzroy dives deep");

    // Menu item choices.
    drawString(20, 45, "1. start game");
    drawString(20, 60, "2. pass code");
    drawString(20, 75, "3. rebind controls");
    drawString(20, 90, difficultyMenuText_);
    drawString(20, 105, "5. high scores");
    drawString(20, 120, colourModeText_);
    drawString(20, 135, soundModeText_);

    // Draw the game version number on the bottom right corner of the screen.
    drawString(70, 180, GAME_VERSION);

    // Draw the build number on the bottom right corner of the screen.
    drawString(70, 190, BUILD_NUMBER);
}

void switchColourMode()
{
    // Update menu item text to reflect the chosen colour mode.
    if (greenMode)
    {
        greenMode = FALSE;
        colourModeText_ = COLOUR_MODE_TEXT_;
    }
    else
    {
        greenMode = TRUE;
        colourModeText_ = GREEN_MODE_TEXT_;
    }
}

void switchSoundMode()
{
    if (soundMode == SOUND_MODE_SOUND_DISABLED)
    {
        soundMode = SOUND_MODE_OFF;
        soundModeText_ = SOUND_ON_TEXT_;
    }
    else
    {
        soundMode = SOUND_MODE_SOUND_DISABLED;
        soundModeText_ = SOUND_OFF_TEXT_;
    }

#ifdef WINCPCTELERA
    // Add a delay to make it easier to switch modes with wincpctelera.
    wincpct_wait(200);
#endif
}

void rebindControls()
{
    // Clear the screen so that we can show control text.
    cpct_clearScreen(0);

    // Enter controls title.
    drawString(15,8, "enter controls");

    // Draw the text indicating which control is being set.
    // Make sure no key is currently down.
    // Set the key for the next control (this is done for each of control below).
    drawString(25, 30, "up");
    gameControls.up = getKey();

    drawString(25, 45, "down");
    gameControls.down = getKey();

    drawString(25, 60, "left");
    gameControls.left = getKey();

    drawString(25, 75, "right");
    gameControls.right = getKey();

    drawString(25, 90, "shoot");
    gameControls.shoot = getKey();

    drawString(25, 105, "use");
    gameControls.use = getKey();

    drawString(25, 120, "jump or boost");
    gameControls.jump = getKey();

    drawString(25, 135, "exit");
    gameControls.exit = getKey();
}

cpct_keyID getKey()
{
    // From Ronaldo and Arnaud on the cpcwiki forum.
    u8* keyStatus = cpct_keyboardStatusBuffer;
    u8 i;

    // Wait for any previous key presses to clear first.
    while (cpct_isAnyKeyPressed());

    // Then wait for the desired key press.
    while (!cpct_isAnyKeyPressed());

    for (i = 0; i < 10; ++i)
    {
        cpct_keyID keypressed = *keyStatus++ ^ 0xFF;

        if (keypressed)
        {
            return (keypressed << 8) + i;
        }
    }

    return 0;
}

void displayHighScores()
{
    u8 i = 0;
    u8 scoreStart = 25;  

    cpct_clearScreen(0);

    // High scores title.
    drawString(25, 5, "high scores");

    // Each of the entires in the high score list.
    for (i = 0; i < SCORE_COUNT_; ++i)
    {
        u8 y = 45 + i * 15;

        // Draw the name of the player from the names array.
        drawString(40, y, highScoreNames_[i]);
        
        // Draw the score with the zeros (there is an extra 0 so that scores can be bigger than the are actually stored).
        drawScore(highScores_[i], scoreStart, y);
    }

    getKey();
}

void initHighScores()
{
    u8 i = 0;

    for (i = 0; i < SCORE_COUNT_; ++i)
    {
        highScores_[i] = 10;
        cpct_memcpy(highScoreNames_[i], "cpc\0", 4);
    }
}

void chooseDifficulty()
{
    // Switch between difficulties and upate the menu text.
    if (chosenDifficulty == EASY_DIFFICULTY)
    {
        chosenDifficulty = NORMAL_DIFFICULTY;
        difficultyMenuText_ = NORMAL_DIFFICULTY_TEXT_;
    }
    else if (chosenDifficulty == NORMAL_DIFFICULTY)
    {
        chosenDifficulty = HARD_DIFFICULTY;
        difficultyMenuText_ = HARD_DIFFICULTY_TEXT_;
    }
    else
    {
        chosenDifficulty = EASY_DIFFICULTY;
        difficultyMenuText_ = EASY_DIFFICULTY_TEXT_;
    }
}

void displayEndScreen()
{
    // Wait for any previous key presses to clear first.
    while (cpct_isAnyKeyPressed());

    // Clear the screen
    cpct_clearScreen(0);

    if (levelState == LEVEL_GAME_COMPLETE)
    {
        u8* memory;
        u8 count = 255;
        u8 colour = 0;
        u8 x = 0;
        u8 y = 0;

        // Draw a patterned background.
        while (count)
        {
            colour = 1 + cpct_rand() % 15;
            y = 18 + 8 * (cpct_rand() % 14);
            x = 4 * (cpct_rand() % 25);

            memory = cpct_getScreenPtr(CPCT_VMEM_START, x, y);
            cpct_drawSolidBox(memory, cpct_px2byteM0(colour, colour), 4, 11);

            --count;
        }

        // Draw victorious text on screen.
        memory = cpct_getScreenPtr(CPCT_VMEM_START, 25, 60);
        cpct_drawSolidBox(memory, cpct_px2byteM0(0, 0), 30, 20);
        drawString(30, 65, "victorious");

        // Only award bonuses if the player played without a pass code.
        if (!passcodeUsed)
        {
            // 1000 (after the extra 0 is displayed) points multiplied by difficulty for winning the game.
            gameScore += (chosenDifficulty + 1) * 100;

            // 2000 (after the extra 0 is displayed) points multiplied by difficulty for not losing a life.
            if (playerLives == startingLivesCount)
            {
                gameScore += (chosenDifficulty + 1) * 200;
            }
        }
    }
    else
    {
        static u8 generatedCode[6];

        // Display Game Over.
        drawString(30, 20, "game over");

        // Display the pass code for the level the player got up to before dying.
        drawString(30, 40, "pass code");
        generatePasscode(currentMap, generatedCode);
        drawString(34, 50, generatedCode);
    }

    // Wait for a key press before going back to the main menu.
    while (!cpct_isAnyKeyPressed());
}

void updateHighScores()
{
    // Check that the player got a higher score than the bottom score.
    if (gameScore > highScores_[4])
    {
        // Let the player enter their name.
        u8 letterCount = 0;
        static u8 scoreName[11];
        u8 i = 0;

        // Default the chosen key to whatever.
        cpct_keyID chosenKey = Key_0;

        // Set scoreName to the null terminator to end the string.
        cpct_memset(scoreName, '\0', 11);

        cpct_clearScreen(0);
        drawString(15, 5, "new high score");    

        // Draw the score with padding of zeroes.
        drawScore(gameScore, 45, 5);
        drawString(20, 25, "enter your name");

        // Enter the name of the user for the high scores.
        enterWord(scoreName, MAX_NAME_LENGTH_);

        // Update score values.
        highScores_[4] = gameScore;
        cpct_memcpy(highScoreNames_[4], scoreName, 11);

        for (i = 4; i > 0; --i)
        {
            if (highScores_[i] > highScores_[i - 1])
            {
                highScores_[i] = highScores_[i - 1];
                cpct_memcpy(highScoreNames_[i], highScoreNames_[i - 1], 11);

                highScores_[i - 1] = gameScore;
                cpct_memcpy(highScoreNames_[i - 1], scoreName, 11);
            }
        }
    }
}

u8 enterPasscode()
{
    static u8 passcode[6];
    static u8 generatedCode[6];
    u8 i = 0;
    u8 j = 0;
    cpct_memset(passcode, '\0', 6);
    cpct_memset(generatedCode, '\0', 6);

    // Clear the screen.
    cpct_clearScreen(0);

    // Instruct the user to enter the code
    drawString(15, 25, "enter pass code");

    // Get the user to enter a possible pass code.
    enterWord((u8*)passcode, PASS_CODE_LENGTH);

    // Compare user selection with existing pass codes to see if they are valid.
    for (i = 0; i < LEVEL_COUNT; ++i)
    {
        u8 invalid = FALSE;
        generatePasscode(i, generatedCode);

        for (j = 0; j < PASS_CODE_LENGTH; ++j)
        {
            invalid += (generatedCode[j] != passcode[j]);
        }

        if (!invalid)
        {
            // Code valid, return map number matching selected pass code.
            return i;
        }
    }

    // User selected pass code does not match.
    return INVALID_PASS_CODE;
}

void enterWord(u8* word, u8 maxLength)
{
    // Default the chosen key to whatever.
    cpct_keyID chosenKey = Key_0;
    u8 letterCount = 0;
    static u8 chosenCharacters[2];
    chosenCharacters[1] = '\0';

    do
    {
        // Get the next character from the user.
        chosenKey = getKey();

        // Del to move back to the previous key.
        if (chosenKey == Key_Del)
        {
            // Go back to the previous letter.
            if (letterCount)
            {
                --letterCount;
                --word;

                // Draw letter showing a space where it used to be a letter.
                drawString(25 + letterCount * 2, 50, " ");
            }
        }

        // Make sure the key is in the correct range and the word is not too long.
        else if (letterCount < maxLength)
        {
            switch (chosenKey)
            {
            case Key_A:
                chosenCharacters[0] = 'a';
                break;

            case Key_B:
                chosenCharacters[0] = 'b';
                break;

            case Key_C:
                chosenCharacters[0] = 'c';
                break;

            case Key_D:
                chosenCharacters[0] = 'd';
                break;

            case Key_E:
                chosenCharacters[0] = 'e';
                break;

            case Key_F:
                chosenCharacters[0] = 'f';
                break;

            case Key_G:
                chosenCharacters[0] = 'g';
                break;

            case Key_H:
                chosenCharacters[0] = 'h';
                break;

            case Key_I:
                chosenCharacters[0] = 'i';
                break;

            case Key_J:
                chosenCharacters[0] = 'j';
                break;

            case Key_K:
                chosenCharacters[0] = 'k';
                break;

            case Key_L:
                chosenCharacters[0] = 'l';
                break;

            case Key_M:
                chosenCharacters[0] = 'm';
                break;

            case Key_N:
                chosenCharacters[0] = 'n';
                break;

            case Key_O:
                chosenCharacters[0] = 'o';
                break;

            case Key_P:
                chosenCharacters[0] = 'p';
                break;

            case Key_Q:
                chosenCharacters[0] = 'q';
                break;

            case Key_R:
                chosenCharacters[0] = 'r';
                break;

            case Key_S:
                chosenCharacters[0] = 's';
                break;

            case Key_T:
                chosenCharacters[0] = 't';
                break;

            case Key_U:
                chosenCharacters[0] = 'u';
                break;

            case Key_V:
                chosenCharacters[0] = 'v';
                break;

            case Key_W:
                chosenCharacters[0] = 'w';
                break;

            case Key_X:
                chosenCharacters[0] = 'x';
                break;

            case Key_Y:
                chosenCharacters[0] = 'y';
                break;

            case Key_Z:
                chosenCharacters[0] = 'z';
                break;

            case Key_Space:
                chosenCharacters[0] = ' ';
                break;
            }

            if (chosenCharacters[0])
            {
                *word = chosenCharacters[0];

                // Draw letter, the one just selected.
                drawString(25 + letterCount * 2, 50, chosenCharacters);

                ++word;
                ++letterCount;

                // Make sure the chosen character is reset so the previous character
                // does not get drawn if the user chooses an invalid character.
                chosenCharacters[0] = 0;
            }
        }
    } 
    while (chosenKey != Key_Return && chosenKey != Key_Enter);
}

void runFullGame()
{
    // Runs the full game session.
    runGameSession();

    // Displays either game over or victory screen depending on if the player succeeded or not.
    displayEndScreen();

    // Update and show high scores.
    updateHighScores();
}