////////////////////////////////////////////////////////////////////////////////
// Logic.cpp
//
//  Auxilary game logic functions

#include "stdhdr.h"

#include "Sound.h"
#include "Logic.h"

////////////////////////////////////////////////////////////////////////////////
// Game state

EGameState  g_nGameState = Initialising;

// Time that the level screen state started
DWORD       g_dwLevelScreenStartTime;

BYTE    Grid[YBLOCKS][XBLOCKS];

int     g_nBalls;
CEnemySprite **g_spriteBalls;

int     g_nLevel;
int     g_nLives;
int     g_nPercentFilled;

BOOL    g_bHasDied;
double  g_nPlayerVelocity;
CPlayerSprite *g_spritePlayer;

extern  IDirectMusicSegment8    *g_pLevelUp, *g_pDie, *g_pFill;

// Create the player sprite
void InitPlayer()
{
    DeletePlayer();

    g_bHasDied = FALSE;
    g_nPlayerVelocity = 2;
    g_spritePlayer = new CPlayerSprite(g_nPlayerVelocity);
}

// Delete the player sprite
void DeletePlayer()
{
    if (g_spritePlayer)
        delete g_spritePlayer;
    g_spritePlayer = NULL;
}

// Initialise a new level and the enemy sprites therein
void InitLevel()
{
    DeleteLevel();
    
    // Level 1 has 2 balls, level 2 has 3 balls, etc.
    g_nBalls = g_nLevel + 1;
    // Currently unfilled.
    g_nPercentFilled = 0;

    // Create the ball array.
    g_spriteBalls = new CEnemySprite*[g_nBalls];
    
    int nEnemiesDown = (int)sqrt(g_nBalls);
    int nEnemiesAcross = g_nBalls / nEnemiesDown;
    int i;

    for (i = 0; i < g_nBalls; i++)
    {
        g_spriteBalls[i] = new CEnemySprite;
        // Space the sprites evenly throughout the level
        g_spriteBalls[i]->m_x = ((i / nEnemiesDown + 1) * (XBLOCKS - 4) / (nEnemiesAcross + 1) + 2) * BLOCKSIZE;
        g_spriteBalls[i]->m_y = ((i % nEnemiesDown + 1) * (YBLOCKS - 4) / (nEnemiesDown + 1) + 2) * BLOCKSIZE;
    }

    // Clear the grid
    memset(Grid, GRID_EMPTY, sizeof(Grid));
    // Fill the top and bottom of the grid
    memset(Grid[0], GRID_FULL, sizeof(Grid[0]) * 2);
    memset(Grid[YBLOCKS - 2], GRID_FULL, sizeof(Grid[0]) * 2);
    // Fill the left and right of the grid
    for (i = 0; i < YBLOCKS; i++)
        Grid[i][0] = Grid[i][1] = Grid[i][XBLOCKS - 1] = Grid[i][XBLOCKS - 2] = GRID_FULL;

    // Go to the level screen
    ChangeToGameState(LevelScreen);
}

// Delete the level
void DeleteLevel()
{
    int i;

    for (i = 0; i < g_nBalls; i++)
        delete g_spriteBalls[i];
    g_nBalls = 0;

    if (g_spriteBalls)
        delete [] g_spriteBalls;
    g_spriteBalls = NULL;
}

// Fill any areas that have no balls in them
void FillGrid()
{
    HDC hDC;

    hDC = CreateCompatibleDC(NULL);

    // Create a black-and-white bitmap with each bit representing
    //  a block on the screen: white=full, black=empty
    HBITMAP hBitmap;
    LPBYTE  pBitmap = new BYTE[XBLOCKS * YBLOCKS / 8];

    int     x, y, i;
    UINT    nMask;

    memset(pBitmap, 0, XBLOCKS * YBLOCKS / 8);
    for (y = 0; y < YBLOCKS; y++)
    {
        for (x = 0; x < XBLOCKS; )
        {
            for (nMask = 0x80; nMask; nMask >>= 1, x++)
            {
                if (Grid[y][x] != GRID_EMPTY)
                    pBitmap[(y * XBLOCKS + x) / 8] |= nMask;
            }
        }
    }

    hBitmap = CreateBitmap(XBLOCKS, YBLOCKS, 1, 1, pBitmap);

    // Now fill this bitmap with white everywhere there is a ball
    SelectObject(hDC, hBitmap);
    for (i = 0; i < g_nBalls; i++)
        ExtFloodFill(hDC, (int)g_spriteBalls[i]->m_x / BLOCKSIZE, (int)g_spriteBalls[i]->m_y / BLOCKSIZE,
            0, FLOODFILLSURFACE);
    GetBitmapBits(hBitmap, XBLOCKS * YBLOCKS / 8, pBitmap);

    // Now, if the bitmap is black, it was previously empty but is now full, 
    //  so fill the grid.
    int nFill = 0;
    for (y = 0; y < YBLOCKS; y++)
    {
        for (x = 0; x < XBLOCKS; )
        {
            for (nMask = 0x80; nMask; nMask >>= 1, x++)
            {
                if ((pBitmap[(y * XBLOCKS + x) / 8] & nMask) == 0)
                    Grid[y][x] = GRID_FULL;
                if (Grid[y][x] == GRID_FULL)
                    nFill++;
            }
        }
    }
    
    // adjust for margin
    nFill -= XBLOCKS * 4 + YBLOCKS * 4;
    g_nPercentFilled = (nFill * 100) / ((XBLOCKS - 4) * (YBLOCKS - 4));

    DeleteObject(hBitmap);
    delete pBitmap;
}

// Reset to the start of a new game
void ResetGame()
{
    g_nLives = 3;
    g_nLevel = 1;
    InitLevel();
    InitPlayer();
}

// Move each of the sprites as appropriate, and update the game state.
void UpdateGameState()
{
    int     x, y, i;

    if (g_bHasDied)
    {
        PlaySound(g_pDie, DMUS_SEGF_SECONDARY);
        g_nLives--;
        if (g_nLives <= 0)
        {   
            ChangeToGameState(Intro);
            return;
        }
        else
        {   
            // Change all GRID_MIDs to GRID_EMPTYs
            for (y = 0; y < YBLOCKS; y++)
            {
                for (x = 0; x < XBLOCKS; x++)
                {
                    if (Grid[y][x] == GRID_MID)
                        Grid[y][x] = GRID_EMPTY;
                }
            }
            InitPlayer();
        }
    }
    else if (g_nPercentFilled >= 75)
    {
        g_nLevel++;
        g_nLives++;
        InitLevel();
        InitPlayer();
        PlaySound(g_pLevelUp, DMUS_SEGF_SECONDARY);
    }

    for (i = 0; i < g_nBalls; i++)
        g_spriteBalls[i]->Move();

    CPlayerSprite::Direction nOldDirection = g_spritePlayer->m_nDirection;

    g_spritePlayer->Move();

    if (g_spritePlayer->m_bAtGrid)
    {
        x = (int)g_spritePlayer->m_x / BLOCKSIZE;
        y = (int)g_spritePlayer->m_y / BLOCKSIZE;

        int xNew = x, yNew = y;

        switch (g_spritePlayer->m_nDirection)
        {
        case CPlayerSprite::Direction::UP:
            if (yNew > 0)
                yNew--;
            break;

        case CPlayerSprite::Direction::DOWN:
            if (yNew < (YBLOCKS - 1))
                yNew++;
            break;

        case CPlayerSprite::Direction::LEFT:
            if (xNew > 0)
                xNew--;
            break;

        case CPlayerSprite::Direction::RIGHT:
            if (xNew < (XBLOCKS - 1))
                xNew++;
            break;

        case CPlayerSprite::Direction::STOPPED:
            break;
        }

        switch (Grid[yNew][xNew])
        {
        case GRID_FULL:
            if (Grid[y][x] == GRID_EMPTY)
            {
                Grid[y][x] = GRID_MID;

                // Fill enclosed areas:
                // Change all GRID_MIDs to GRID_FULLs
                for (int y = 0; y < YBLOCKS; y++)
                {
                    for (int x = 0; x < XBLOCKS; x++)
                    {
                        if (Grid[y][x] == GRID_MID)
                            Grid[y][x] = GRID_FULL;
                    }
                }
                
                PlaySound(g_pFill, DMUS_SEGF_SECONDARY);
                FillGrid();

                g_spritePlayer->m_nNextDirection = CPlayerSprite::Direction::STOPPED;
            }
            break;

        case GRID_EMPTY:
            if (Grid[y][x] == GRID_FULL)
            {   
                if (nOldDirection != CPlayerSprite::Direction::STOPPED)
                    g_spritePlayer->m_nDirection = 
                        g_spritePlayer->m_nNextDirection = 
                        CPlayerSprite::Direction::STOPPED;
            }
            else                    
                Grid[y][x] = GRID_MID;
            break;

        case GRID_MID:
            // die
            g_bHasDied = TRUE;
            break;
        }
    }
}

void    PlayBackgroundMusic();

void ChangeToGameState(EGameState nNewState)
{
    switch (nNewState)
    {
    case Intro:
        break;

    case LevelScreen:
        g_dwLevelScreenStartTime = GetTickCount();
        break;
    }
    g_nGameState = nNewState;
    // Play the appropriate background music
    PlayBackgroundMusic();
}