////////////////////////////////////////////////////////////////////////////////
// Main.cpp
//
//  Main program logic.

#include "stdhdr.h"

#include "ddutil.h"
#include "Debug.h"
#include "Sound.h"
#include "Input.h"
#include "Draw.h"
#include "DrawText.h"
#include "Window.h"

#include "Sprites.h"
#include "Logic.h"

////////////////////////////////////////////////////////////////////////////////
// DirectInput

// {5447976E-1120-4762-87E5-61ACC539746A}
GUID g_guidApp = 
{ 0x5447976e, 0x1120, 0x4762, { 0x87, 0xe5, 0x61, 0xac, 0xc5, 0x39, 0x74, 0x6a } };
DWORD   g_dwGenre = DIVIRTUAL_ARCADE_SIDE2SIDE;
LPCTSTR g_tszActionMapName = _T("Xonix");

enum Actions 
{
    ACTIONS_LEFTRIGHT,
    ACTIONS_UPDOWN,
    ACTIONS_LEFT,
    ACTIONS_RIGHT,
    ACTIONS_UP,
    ACTIONS_DOWN,
    ACTIONS_QUIT,
    ACTIONS_HELP,
    ACTIONS_CONFIGURE,
    ACTIONS_MUSIC,
    ACTIONS_PAUSE,
    ACTIONS_START,
    ACTIONS_NONE
};

bool    g_bQuit, g_bShowHelp, g_bPaused, g_bDoConfig, g_bStart;
bool    g_bLeft, g_bRight, g_bUp, g_bDown;

DIACTION g_rgActions[] =
{
    // Genre-defined virtual axes
    { ACTIONS_LEFTRIGHT,    DIAXIS_ARCADES_LATERAL,             0,  "Left/Right" },
    { ACTIONS_UPDOWN,       DIAXIS_ARCADES_MOVE,                0,  "Up/Down" },

    // Genre-defined virtual buttons
    { ACTIONS_LEFT,         DIBUTTON_ARCADES_LEFT_LINK,         0,  "Left" },
    { ACTIONS_RIGHT,        DIBUTTON_ARCADES_RIGHT_LINK,        0,  "Right" },
    { ACTIONS_UP,           DIBUTTON_ARCADES_FORWARD_LINK,      0,  "Up" },
    { ACTIONS_DOWN,         DIBUTTON_ARCADES_BACK_LINK,         0,  "Down" },
    { ACTIONS_PAUSE,        DIBUTTON_ARCADES_PAUSE,             0,  "Pause" },
    { ACTIONS_START,        DIBUTTON_ARCADES_ATTACK,            0,  "Start" },

    // Keys
    { ACTIONS_START,        DIKEYBOARD_RETURN,                  0,  "Start" },
    { ACTIONS_START,        DIKEYBOARD_SPACE,                   0,  "Start" },
    { ACTIONS_LEFT,         DIKEYBOARD_LEFT,                    0,  "Left" },
    { ACTIONS_LEFT,         DIKEYBOARD_NUMPAD4,                 0,  "Left" },
    { ACTIONS_RIGHT,        DIKEYBOARD_RIGHT,                   0,  "Right" },
    { ACTIONS_RIGHT,        DIKEYBOARD_NUMPAD6,                 0,  "Right" },
    { ACTIONS_UP,           DIKEYBOARD_UP,                      0,  "Up" },
    { ACTIONS_UP,           DIKEYBOARD_NUMPAD8,                 0,  "Up" },
    { ACTIONS_DOWN,         DIKEYBOARD_DOWN,                    0,  "Down" },
    { ACTIONS_DOWN,         DIKEYBOARD_NUMPAD2,                 0,  "Down" },
    { ACTIONS_PAUSE,        DIKEYBOARD_P,           DIA_APPFIXED,   "Pause" },
    { ACTIONS_MUSIC,        DIKEYBOARD_M,           DIA_APPFIXED,   "Music on/off" },
    { ACTIONS_HELP,         DIKEYBOARD_F1,          DIA_APPFIXED,   "Help" },
    { ACTIONS_CONFIGURE,    DIKEYBOARD_F2,          DIA_APPFIXED,   "Configure" },
    { ACTIONS_QUIT,         DIKEYBOARD_ESCAPE,      DIA_APPFIXED,   "Quit" },
};

DWORD g_nActions = sizeof(g_rgActions) / sizeof(DIACTION);

////////////////////////////////////////////////////////////////////////////////
// DirectDraw

// Surface containing images
IDirectDrawSurface7 *lpImages;
// Surface containing splash screen
IDirectDrawSurface7 *lpSplash;
// Font to draw text
CTextFont           *g_lpFont;

void ReloadAllSurfaces()
{
    DDReLoadBitmap(lpImages, "Main.bmp");
    DDReLoadBitmap(lpSplash, "Splash.bmp");
}

////////////////////////////////////////////////////////////////////////////////
// Music handling

IDirectMusicSegment8    *g_pLevelUp, *g_pDie, *g_pFill;

#define NUMMUSICLEVELS  4

IDirectMusicSegment8    *g_pIntroMusic, *g_pLevelMusic[NUMMUSICLEVELS];
bool                    g_bIntroMusicPlaying, g_bLevelMusicPlaying[NUMMUSICLEVELS];
bool                    g_bMusicEnabled;

BOOL LoadMusic()
{
    g_pIntroMusic = LoadSound(L"passport.mid");
    if (g_pIntroMusic == NULL)
        return FALSE;
    g_pLevelMusic[0] = LoadSound(L"Dance of the Sugar-Plum Fairy.mid");
    if (g_pLevelMusic[0] == NULL)
        return FALSE;
    g_pLevelMusic[1] = LoadSound(L"canyon.mid");
    if (g_pLevelMusic[1] == NULL)
        return FALSE;
    g_pLevelMusic[2] = LoadSound(L"Beethoven's 5th Symphony.mid");
    if (g_pLevelMusic[2] == NULL)
        return FALSE;
    g_pLevelMusic[3] = LoadSound(L"In the Hall of the Mountain King.mid");
    if (g_pLevelMusic[3] == NULL)
        return FALSE;

    // Background music loops forever
    g_pIntroMusic->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
    // Standard MIDI file
    g_pIntroMusic->SetParam(GUID_StandardMIDIFile, 
        0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0, NULL);

    for (int i = 0; i < NUMMUSICLEVELS; i++)
    {
        g_pLevelMusic[i]->SetRepeats(DMUS_SEG_REPEAT_INFINITE);
        g_pLevelMusic[i]->SetParam(GUID_StandardMIDIFile, 
            0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0, NULL);
    }
    return TRUE;
}

void UnloadMusic()
{
    UnloadSound(g_pIntroMusic);
    g_pIntroMusic->Release();
    for (int i = 0; i < NUMMUSICLEVELS; i++)
    {
        UnloadSound(g_pLevelMusic[i]);
        g_pLevelMusic[i]->Release();
    }
}

void PlayBackgroundMusic()
{
    int n;

    if (!g_bMusicEnabled)
        return;

    switch (g_nGameState)
    {
    case Intro:
        if (g_bIntroMusicPlaying)
            return;

        StopBackgroundMusic();
        DownloadSound(g_pIntroMusic);
        PlaySound(g_pIntroMusic);
        g_bIntroMusicPlaying = true;
        break;

    case LevelScreen:
    case InGame:
    case InGamePaused:
        n = (g_nLevel - 1) % NUMMUSICLEVELS;
        
        if (g_bLevelMusicPlaying[n])
            return;

        StopBackgroundMusic();
        DownloadSound(g_pLevelMusic[n]);
        PlaySound(g_pLevelMusic[n]);
        g_bLevelMusicPlaying[n] = true;
        break;
    }
}

void StopBackgroundMusic()
{
    StopAllSound();
    g_bIntroMusicPlaying = false;
    memset(g_bLevelMusicPlaying, 0, sizeof(g_bLevelMusicPlaying));
}

////////////////////////////////////////////////////////////////////////////////
// Input handling

#define AXIS_THRESHOLD  20

void HandleAction(UINT nAction, DWORD dwData)
{
    int nAxisPos = (int)dwData;

    switch (nAction)
    {
    case ACTIONS_LEFTRIGHT:
        if (nAxisPos < -AXIS_THRESHOLD)
        {
            g_bLeft = true;
            g_bRight = false;
        }
        else if (nAxisPos > AXIS_THRESHOLD)
        {
            g_bRight = true;
            g_bLeft = false;
        }
        else
        {
            g_bLeft = g_bRight = false;
        }
        break;
    
    case ACTIONS_UPDOWN:
        if (nAxisPos < -AXIS_THRESHOLD)
        {
            g_bUp = true;
            g_bDown = false;
        }
        else if (nAxisPos > AXIS_THRESHOLD)
        {
            g_bDown = true;
            g_bUp = false;
        }
        else
        {
            g_bUp = g_bDown = false;
        }
        break;
    
    case ACTIONS_LEFT:
        g_bLeft = ((dwData & 0x80) != 0);
        break;

    case ACTIONS_RIGHT:
        g_bRight = ((dwData & 0x80) != 0);
        break;

    case ACTIONS_UP:
        g_bUp = ((dwData & 0x80) != 0);
        break;

    case ACTIONS_DOWN:
        g_bDown = ((dwData & 0x80) != 0);
        break;

    case ACTIONS_QUIT:
        g_bQuit = true;
        break;

    case ACTIONS_MUSIC:
        if (dwData & 0x80)
        {
            g_bMusicEnabled = !g_bMusicEnabled;
            if (g_bMusicEnabled)
                PlayBackgroundMusic();
            else
                StopBackgroundMusic();
        }
        break;

    case ACTIONS_HELP:
        if (dwData & 0x80)
            g_bShowHelp = true;
        break;

    case ACTIONS_CONFIGURE:
        if (dwData & 0x80)
            g_bDoConfig = true;
        break;

    case ACTIONS_PAUSE:
        g_bPaused = (dwData & 0x80) ? true : false;
        break;

    case ACTIONS_START:
        if (dwData & 0x80)
            g_bStart = true;
        break;
    
    default:
        break;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Idle-time processing function.

int             g_nFrames, g_nFrameRate, g_nFrameTime;
LARGE_INTEGER   g_liStartTime;

void DrawFrame()
{
    RECT    r;
    int     x, y, i;
    // Times for calculating frame time and frame rate
    LARGE_INTEGER   liStart, liEnd, liFreq;
    // Temporary copy of the current grid.
    static BYTE DrawGrid[YBLOCKS][XBLOCKS];
    static TCHAR    szBuffer[256];

    QueryPerformanceCounter(&liStart);

    switch (g_nGameState)
    {
    case Intro:
        // Draw splash screen
        r.left = r.top = 0;
        r.right = 640;
        r.bottom = 480;
        BackBlt(0, 0, lpSplash, &r, DDBLTFAST_NOCOLORKEY);
        break;

    case LevelScreen:
        // Clear screen to black
        ClearScreen();

        // Draw level counter in center of screen
        _stprintf(szBuffer, _T("Level %d"), g_nLevel);
        g_lpFont->DrawText(szBuffer, 320, 240, DT_CENTER | DT_VCENTER);
        break;

    case InGame:
    case InGamePaused:
        // Clear screen to black
        ClearScreen();

        memcpy(DrawGrid, Grid, sizeof(Grid));

        for (y = 0; y < YBLOCKS; y++)
        {
            for (x = 0; x < XBLOCKS; x++)
            {
                if (DrawGrid[y][x] == GRID_EMPTY)
                    continue;

                // Extend the drawing rectangle as far as possible in the horizontal direction.
                for (i = 1; ((x + i) < XBLOCKS) && (DrawGrid[y][x + i] == DrawGrid[y][x]); i++)
                    DrawGrid[y][x + i] = GRID_EMPTY;
                if (i > 1)
                {
                    SetRect(&r, x * BLOCKSIZE, y * BLOCKSIZE, (x + i) * BLOCKSIZE, (y + 1) * BLOCKSIZE);
                }
                else
                {
                    // Rectangle was only 1 block wide, so extend the rectangle 
                    //  as far as possible in the vertical direction.
                    for (i = 1; ((y + i) < YBLOCKS) && (DrawGrid[y + i][x] == DrawGrid[y][x]); i++)
                        DrawGrid[y + i][x] = GRID_EMPTY;
                    SetRect(&r, x * BLOCKSIZE, y * BLOCKSIZE, (x + 1) * BLOCKSIZE, (y + i) * BLOCKSIZE);
                }

                switch (DrawGrid[y][x])
                {
                case GRID_EMPTY:
                    break;

                case GRID_FULL:
                    BackBlt(x * BLOCKSIZE, y * BLOCKSIZE, lpImages, &r, DDBLTFAST_NOCOLORKEY);
                    break;

                case GRID_MID:
                    r.top += MIDTOP;
                    r.bottom += MIDTOP;
                    BackBlt(x * BLOCKSIZE, y * BLOCKSIZE, lpImages, &r, DDBLTFAST_NOCOLORKEY);
                    break;
                }

                // Note that we don't have to reset the current position, since 
                //  it will not again be considered.
            }
        }
        g_spritePlayer->Draw();

        for (i = 0; i < g_nBalls; i++)
        {
            g_spriteBalls[i]->NextFrame();
            g_spriteBalls[i]->Draw();
        }

        _stprintf(szBuffer, _T("%d%%"), g_nPercentFilled);
        g_lpFont->DrawText(szBuffer, 0, YBLOCKS * BLOCKSIZE, DT_LEFT | DT_TOP);

        if (g_nLives > 1)
            _stprintf(szBuffer, _T("%d lives"), g_nLives);
        else
            _tcscpy(szBuffer, _T("1 life"));
        g_lpFont->DrawText(szBuffer, XSCREEN / 2, YBLOCKS * BLOCKSIZE, DT_CENTER | DT_TOP);

        _stprintf(szBuffer, _T("%d FPS (%dms)"), 
            g_nFrameRate,
            g_nFrameTime);
        g_lpFont->DrawText(szBuffer, XSCREEN, YBLOCKS * BLOCKSIZE, DT_RIGHT | DT_TOP);
        break;

    default:
        // Should never get here: clear screen to black
        ClearScreen();
        break;
    }

    QueryPerformanceCounter(&liEnd);
    QueryPerformanceFrequency(&liFreq);
    
    g_nFrames++;
    if (g_nFrames > 100)
    {
        g_nFrameRate = MulDiv((int)liFreq.QuadPart, 
            g_nFrames, (int)(liEnd.QuadPart - g_liStartTime.QuadPart));
        g_liStartTime = liEnd;
        g_nFrames = 0;
    }

    g_nFrameTime = MulDiv((int)(liEnd.QuadPart - liStart.QuadPart),
        1000, (int)liFreq.QuadPart);

    g_lpPrimary->Flip(NULL, DDFLIP_WAIT);
}

void OnIdle(void)
{
    if (g_lpPrimary == NULL)
        return;

    if (!g_bActive)
        return;

    if ((g_nGameState == Initialising) || (g_nGameState == Quitting))
        return;

    CheckInput();

    if (g_bQuit)
    {
        g_nGameState = Quitting;
        PostMessage(hWndMain, WM_CLOSE, 0, 0);
        return;
    }

    if (g_bShowHelp)
    {
        StopBackgroundMusic();
        g_bShowHelp = false;
        DisplayInput();
        // Music came back on when we were reactivated...
    }

    if (g_bDoConfig)
    {
        StopBackgroundMusic();
        g_bDoConfig = false;
        ConfigureInput();
        // Music came back on when we were reactivated...
    }
    
    switch (g_nGameState)
    {
    case Intro:
        if (g_bStart)
        {
            g_bStart = false;
            ResetGame();
        }
        break;

    case LevelScreen:
        if ((GetTickCount() - g_dwLevelScreenStartTime) > LEVELSCREEN_TIME)
            ChangeToGameState(InGame);
        break;

    case InGamePaused:
        if (g_bPaused)
            ChangeToGameState(InGame);
        break;

    case InGame:
        // Handle movement
        if (g_bPaused)
            ChangeToGameState(InGamePaused);
        if (g_bUp)
        {
            g_bUp = false;
            g_spritePlayer->m_nNextDirection = CPlayerSprite::Direction::UP;
        }
        if (g_bDown)
        {
            g_bDown = false;
            g_spritePlayer->m_nNextDirection = CPlayerSprite::Direction::DOWN;
        }
        if (g_bLeft)
        {
            g_bLeft = false;
            g_spritePlayer->m_nNextDirection = CPlayerSprite::Direction::LEFT;
        }
        if (g_bRight)
        {
            g_bRight = false;
            g_spritePlayer->m_nNextDirection = CPlayerSprite::Direction::RIGHT;
        }

        UpdateGameState();
        break;
    }
    
    DrawFrame();
}

////////////////////////////////////////////////////////////////////////////////
// Initialisation and shutdown

BOOL InitApplication(HINSTANCE hInstance)
{
    g_nGameState = Initialising;

    if (!InitWindow("DirectX Sample Application with configuration"))
        return FALSE;

    if (!InitDirectMusic())
        return FALSE;

    // Load the music and sound effects
    g_pLevelUp = LoadSound(L"chimes.wav");
    g_pDie = LoadSound(L"explode.wav");
    g_pFill = LoadSound(L"camera.wav");

    if ((g_pLevelUp == NULL) ||
        (g_pDie == NULL) ||
        (g_pFill == NULL) ||
        !LoadMusic())
    {
        ExitDirectMusic();
        DISPLAYERROR("Could not load music and sound effects.");
        return FALSE;
    }

    // One-shot sound effects
    g_pLevelUp->SetRepeats(0);
    g_pDie->SetRepeats(0);
    g_pFill->SetRepeats(0);

    // Download it, ready for playback
    DownloadSound(g_pLevelUp);
    DownloadSound(g_pDie);
    DownloadSound(g_pFill);

    if (!InitDirectInput())
    {
        DISPLAYERROR("Failed to initialise DirectInput.");
        ExitDirectMusic();
        return FALSE;
    }
    if (!InitDirectDraw())
    {
        ExitDirectInput();
        ExitDirectMusic();
        DestroyWindow(hWndMain);
        return FALSE;
    }

    // Load the sprites.
    lpImages = LoadSurface(_T("Main.bmp"));
    if (lpImages == NULL)
    {
        ExitDirectDraw();
        ExitDirectInput();
        DestroyWindow(hWndMain);
        
        DISPLAYERROR("Could not load sprites.");
        return FALSE;
    }
    DDSetColorKey(lpImages, RGB(0, 0, 0));

    lpSplash = LoadSurface(_T("Splash.bmp"));
    if (lpSplash == NULL)
    {
        ExitDirectDraw();
        ExitDirectInput();
        DestroyWindow(hWndMain);
        
        DISPLAYERROR("Could not load splash screen.");
        return FALSE;
    }

    g_lpFont = new CTextFont(lpImages, TEXTX, TEXTY, TEXTCX, TEXTCY, 
        _T("abcdefghijklmnopqrstuvwxyz")
        _T("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
        _T("01234567890-=_+[]{};'#:@~,")
        _T("./<>?\\|!\"£$%^&*() "), _T('\0'), 26);

    AcquireDevices();

    g_bMusicEnabled = true;
    ChangeToGameState(Intro);

    // Estimate frame rate so the game starts up correctly
    g_nFrameRate = 60;
    QueryPerformanceCounter(&g_liStartTime);
    return TRUE;
}

void OnClose()
{
    StopBackgroundMusic();

    DeletePlayer();
    DeleteLevel();

    delete g_lpFont;
    g_lpFont = NULL;

    // Release our images surface.
    lpImages->Release();
    lpImages = NULL;

    // Release our splash screen surface.
    lpSplash->Release();
    lpSplash = NULL;

    ExitDirectDraw();

    UnloadMusic();

    UnloadSound(g_pLevelUp);
    UnloadSound(g_pDie);
    UnloadSound(g_pFill);

    g_pLevelUp->Release();
    g_pLevelUp = NULL;
    g_pDie->Release();
    g_pDie = NULL;
    g_pFill->Release();
    g_pFill = NULL;

    ExitDirectMusic();

    UnacquireDevices();
    ExitDirectInput();
}

// Program entry point.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) 
{
    if (!InitApplication(hInstance))
        return 1;

    Run();

    return 0;
}