////////////////////////////////////////////////////////////////////////////////
// DirectXInput.cpp
//
//  DirectDraw, sprites and DirectInput.

#include <windows.h>
#include <tchar.h>
#include <ddraw.h>
#include <dinput.h>
#include <dmusicc.h>
#include <dmusici.h>

#include "ddutil.h"

////////////////////////////////////////////////////////////////////////////////
// Error handling

static  TCHAR   szErrorBuf[512], szMessage[256];

void DisplayError(LPCTSTR szFile, int nLine, HRESULT hResult, LPCTSTR lpMessage)
{
    // Get an error description using FormatMessage.
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | 
            FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, hResult, 0, szMessage, 
        sizeof(szMessage) / sizeof(TCHAR), NULL);

    // Create the string.
    wsprintf(szErrorBuf, _T("%s\nFile %s, line %d, error code 0x%08X: %s"), 
        lpMessage, szFile, nLine, hResult, szMessage);

    // Dump the error to the debugger, if present.
    OutputDebugString(szErrorBuf);

    // Display the error using MessageBox.
    MessageBox(NULL, szErrorBuf, _T("Error"), MB_OK);
}

void DisplayError(LPCTSTR lpMessage)
{
    // Dump the error to the debugger, if present.
    OutputDebugString(szErrorBuf);

    // Display the error using MessageBox.
    MessageBox(NULL, szErrorBuf, _T("Error"), MB_OK);
}

#define DISPLAYERROR(h, s)\
    DisplayError(__FILE__, __LINE__, h, _T(s))
#define DISPLAYLASTERROR(s)\
    DisplayError(__FILE__, __LINE__, GetLastError(), _T(s))

////////////////////////////////////////////////////////////////////////////////
// Sound and music support

IDirectMusicPerformance8    *g_pPerformance;
IDirectMusicLoader8         *g_pLoader;
IDirectMusicSegment8        *g_pBackgroundMusic;

BOOL InitDirectMusic()
{
    HRESULT h;

    // Initialise COM, so that we can use CoCreateInstance.
    ::CoInitialize(NULL);
    
    // Create an IDirectMusicPerformance8 object, through which 
    //  we will access all audio functions.
    h = ::CoCreateInstance(CLSID_DirectMusicPerformance, NULL, 
                     CLSCTX_INPROC, IID_IDirectMusicPerformance8,
                     (void **)&g_pPerformance);
    if (FAILED(h))
    {
        DISPLAYERROR(h, "DirectMusic could not be initialised. DirectX 8 may not be installed.");
        return FALSE;
    }
    
    // Set up the audio: a normal stereo and reverb system.
    h = g_pPerformance->InitAudio(NULL, NULL, NULL, 
        DMUS_APATH_SHARED_STEREOPLUSREVERB, 
        64, DMUS_AUDIOF_ALL, NULL);
    if (FAILED(h))
    {
        DISPLAYERROR(h, "DirectX Audio could not be initialised. Check for low memory or low system resources.");
        return FALSE;
    }

    // Create an IDirectMusicLoader8 object. which will load
    //  audio files for us.
    h = ::CoCreateInstance(CLSID_DirectMusicLoader, NULL, 
                     CLSCTX_INPROC, IID_IDirectMusicLoader8,
                     (void **)&g_pLoader);
    if (FAILED(h))
    {
        DISPLAYERROR(h, "DirectX Audio could not be initialised. Check for low memory or low system resources.");
        return FALSE;
    }

    // Load the background music MIDI file
    h = g_pLoader->LoadObjectFromFile(CLSID_DirectMusicSegment, 
        IID_IDirectMusicSegment8, L"passport.mid", (void **) &g_pBackgroundMusic);
    if (FAILED(h))
    {
        DISPLAYERROR(h, "Background music could not be loaded.");
        return FALSE;
    }

    // Standard MIDI file
    g_pBackgroundMusic->SetParam(GUID_StandardMIDIFile, 
        0xFFFFFFFF, DMUS_SEG_ALLTRACKS, 0, NULL);
    // Repeat forever
    g_pBackgroundMusic->SetRepeats(DMUS_SEG_REPEAT_INFINITE);

    // Download this to the sound card (if necessary)
    g_pBackgroundMusic->Download(g_pPerformance);
    
    return TRUE;
}

void PlayMusic()
{
    // Play the segment immediately
    g_pPerformance->PlaySegment(g_pBackgroundMusic, DMUS_SEGF_AFTERPREPARETIME, 0, NULL);
}

void StopMusic()
{
    g_pPerformance->Stop(NULL, NULL, 0, 0);
}

void ExitDirectMusic()
{
    if (g_pBackgroundMusic)
    {
        g_pBackgroundMusic->Release();
        g_pBackgroundMusic = NULL;
    }
    if (g_pPerformance)
    {
        g_pPerformance->CloseDown();
        g_pPerformance->Release();
        g_pPerformance = NULL;
    }
    if (g_pLoader)
    {
        g_pLoader->Release();
        g_pLoader = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////
// DirectInput definitions

// {BD0E8406-FB56-4322-8BC6-BD6E481F5564}
GUID g_guidApp = 
{ 0xbd0e8406, 0xfb56, 0x4322, { 0x8b, 0xc6, 0xbd, 0x6e, 0x48, 0x1f, 0x55, 0x64 } };
DWORD   g_dwGenre = DIVIRTUAL_ARCADE_SIDE2SIDE;
LPCTSTR g_tszActionMapName = _T("Sample");

enum Actions 
{
    ACTIONS_LEFTRIGHT,
    ACTIONS_LEFT,
    ACTIONS_RIGHT,
    ACTIONS_QUIT,
    ACTIONS_NONE
};

bool    g_bQuit;
bool    g_bLeft, g_bRight;

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

    // Actions mapped to keys as well as to virtual controls
    { ACTIONS_LEFT,         DIKEYBOARD_LEFT,                0, "Left" },
    { ACTIONS_RIGHT,        DIKEYBOARD_RIGHT,               0, "Right" },

    // Actions mapped to keys
    { ACTIONS_QUIT,         DIKEYBOARD_ESCAPE,              0, "Quit" },
};

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

// Functions declared in input.cpp
BOOL InitDirectInput();
void ExitDirectInput();
void AcquireDevices();
void UnacquireDevices();
void CheckInput();

////////////////////////////////////////////////////////////////////////////////
// Graphics support

HWND hWndMain;
IDirectDraw7        *pDD;
IDirectDrawSurface7 *lpPrimary, *lpBackBuffer;

// TRUE if our application is active
BOOL                g_bActive = FALSE;

// Surface containing our background.
IDirectDrawSurface7 *lpA;
// Surface containing our sprites.
IDirectDrawSurface7 *lpSprites;

void ExitDirectDraw();

// Initialise DirectDraw and go to full screen mode.
BOOL InitDirectDraw()
{
    HRESULT h;

    // Create the DirectDraw object, through which we will create surfaces.
    h = DirectDrawCreateEx(NULL, (void **)&pDD, IID_IDirectDraw7, NULL);
    if (FAILED(h))
    {
        DISPLAYERROR(h, "DirectDraw could not be initialised. Check for low memory or low system resources.");
        return FALSE;
    }

    // Set the co-operative level to exclusive and full-screen.
    pDD->SetCooperativeLevel(hWndMain, 
        DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT);
    if (FAILED(h))
    {
        ExitDirectDraw();

        DISPLAYERROR(h, "DirectDraw could not be initialised. Check for low memory or low system resources.");
        return FALSE;
    }

    // Set the display mode to 640x480, 16-bit colour, default refresh rate.
    h = pDD->SetDisplayMode(640, 480, 16, 0, 0);
    if (FAILED(h))
    {
        ExitDirectDraw();
        
        DISPLAYERROR(h, "This graphics card does not support 640x480 in 16-bit colour.");
        return FALSE;
    }

    DDSURFACEDESC2  ddsd;

    // Create the primary surface with two back buffers.
    ZeroMemory(&ddsd, sizeof(DDSURFACEDESC2));
    ddsd.dwSize = sizeof(DDSURFACEDESC2);
    ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
            DDSCAPS_FLIP |
            DDSCAPS_COMPLEX;
    ddsd.dwBackBufferCount = 2;
    h = pDD->CreateSurface(&ddsd, &lpPrimary, NULL);
    if (FAILED(h))
    {
        ExitDirectDraw();
        
        DISPLAYERROR(h, "This graphics card does not have enough video memory.");
        return FALSE;
    }
    
    // Get the back buffer pointer, to which we will do all drawing.
    ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
    lpPrimary->GetAttachedSurface(&ddsd.ddsCaps, &lpBackBuffer);

    // Load our background.
    lpA = DDLoadBitmap(pDD, "a.bmp", 0, 0);
    if (lpA == NULL)
    {
        ExitDirectDraw();
        
        DisplayError(_T("Could not load background image."));
        return FALSE;
    }

    // Load the sprites.
    lpSprites = DDLoadBitmap(pDD, "sprites.bmp", 0, 0);
    if (lpA == NULL)
    {
        ExitDirectDraw();
        
        DisplayError(_T("Could not load sprites."));
        return FALSE;
    }
    DDSetColorKey(lpSprites, RGB(255, 0, 255));
    return TRUE;
}

void ExitDirectDraw()
{
    // Release our sprites surface.
    lpSprites->Release();
    lpSprites = NULL;

    // Release our background surface.
    lpA->Release();
    lpA = NULL;

    // Release the primary surface. Note that the back buffer is automatically released 
    //  (since it was created at the same time).
    lpPrimary->Release();
    lpPrimary = NULL;

    // Release the DirectDraw object.
    pDD->Release();
    pDD = NULL;
}

void RestoreAllSurfaces()
{
    pDD->RestoreAllSurfaces();
    DDReLoadBitmap(lpA, "a.bmp");
    DDReLoadBitmap(lpSprites, "sprites.bmp");
}

BOOL BackBlt(int x, int y, IDirectDrawSurface7 *lpSurf, LPCRECT pRect, DWORD dwFlags)
{
    HRESULT h;
    RECT    rc = *pRect;

    if ((x > 640) || (y > 480))
        return TRUE;

    if (x < 0)
    {   rc.left -= x;
        x = 0;
    }
    if (y < 0)
    {   rc.top -= y;
        y = 0;
    }
    
    if (rc.left < 0)
        rc.left = 0;
    if (rc.top < 0)
        rc.top = 0;
    
    if ((x + rc.right - rc.left) > 640)
        rc.right -= (x + rc.right - rc.left) - 640;
    if ((y + rc.bottom - rc.top) > 480)
        rc.bottom -= (y + rc.bottom - rc.top) - 480;

    if ((rc.right <= rc.left) || (rc.bottom <= rc.top))
        return TRUE;
    
    h = lpBackBuffer->BltFast(x, y, lpSurf, &rc, dwFlags);

    if (h == DDERR_SURFACELOST)
    {   RestoreAllSurfaces();
        h = lpBackBuffer->BltFast(x, y, lpSurf, &rc, dwFlags);
    }
    
    if (h != DD_OK)
        return FALSE;
    return TRUE;
}

// Handle all messages for the main window.
LRESULT CALLBACK MainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CLOSE:
        StopMusic();

        UnacquireDevices();
        ExitDirectDraw();
        ExitDirectInput();
        ExitDirectMusic();
        DestroyWindow(hWnd);
        return 0;

    // Clear the cursor while active.
    case WM_SETCURSOR:
        SetCursor(NULL);
        return 0;

    case WM_ACTIVATE:
        if (LOWORD(wParam) == WA_INACTIVE)
        {
            g_bActive = FALSE;
            UnacquireDevices();
        }
        else
        {
            g_bActive = TRUE;
            AcquireDevices();
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

// Register the window class for the main window.
void RegisterWindowClass()
{
    WNDCLASSEX  wcx;

    ZeroMemory(&wcx, sizeof(WNDCLASSEX));
    wcx.cbSize = sizeof(WNDCLASSEX);
    wcx.lpfnWndProc = MainWindowProc;
    wcx.hInstance = GetModuleHandle(NULL);
    // Windows-default icon. Replace with your own.
    wcx.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcx.hCursor = NULL;
    // Black background for the window.
    wcx.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wcx.lpszMenuName = NULL;
    wcx.lpszClassName = "SampleWindowClass";
    RegisterClassEx(&wcx);
}

////////////////////////////////////////////////////////////////////////////////
// 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_LEFT:
        g_bLeft = (dwData != 0);
        break;

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

    case ACTIONS_QUIT:
        g_bQuit = true;
        break;
    
    default:
        break;
    }
}

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

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

    if (!g_bActive)
        return;

    CheckInput();

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

    RECT    r;
    
    // Draw the background to the screen.
    r.left = 0;
    r.top = 0;
    r.right = 640;
    r.bottom = 480;
    BackBlt(0, 0, lpA, &r, 
        DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);

    // Draw a sprite in the middle of the screen.
    r.left = 128;
    r.top = 0;
    r.right = 192;
    r.bottom = 64;
    BackBlt(288, 208, lpSprites, &r, 
        DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

    // Move a sprite around automatically.
    static int x;

    x++;
    if (x > 100)
        x = -100;
    
    r.left = 0;
    r.right = 64;
    BackBlt(288 + x, 100, lpSprites, &r, 
        DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT);

    // Move a sprite around with the input.
    static int xSprite;

    if (g_bLeft)
        xSprite--;
    if (g_bRight)
        xSprite++;
    r.left = 64;
    r.right = 128;
    BackBlt(288 + xSprite, 150, lpSprites, &r, 
        DDBLTFAST_SRCCOLORKEY | DDBLTFAST_WAIT); 

    lpPrimary->Flip(NULL, DDFLIP_WAIT);
}

// Program entry point.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) 
{
    MSG msg;

    RegisterWindowClass();

    // Create a window that fills the screen.
    hWndMain = CreateWindowEx(WS_EX_APPWINDOW,
        "SampleWindowClass", "DirectDraw Sprites Sample", WS_POPUP,
        0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN),
        NULL, NULL, hInstance, NULL);
    if (hWndMain == NULL)
    {
        DisplayError(_T("Could not create window."));
        return 1;
    }
    
    ShowWindow(hWndMain, SW_SHOW);

    if (!InitDirectMusic())
        return 1;
    if (!InitDirectInput())
    {
        DisplayError(_T("Failed to initialise DirectInput."));
        ExitDirectMusic();
        return 1;
    }

    if (!InitDirectDraw())
    {
        ExitDirectInput();
        ExitDirectMusic();
        return 1;
    }

    AcquireDevices();

    PlayMusic();

    // Message loop. Note that this has been modified to allow
    //  us to execute even if no messages are being processed.
    for (;;)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
        {
            if (!GetMessage(&msg, NULL, 0, 0))
                break;
            
            // If you want WM_CHAR messages, add
            //  TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        
        // Idle-time processing
        OnIdle();
    }

    return 0;
}