////////////////////////////////////////////////////////////////////////////////
// Graphics.cpp
//
//  DirectX Graphics handling.

#include "stdhdr.h"

#include "Debug.h"
#include "Graphics.h"

// Force the inclusion of the libraries we need
#pragma comment(lib, "kernel32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3d8.lib")
#pragma comment(lib, "d3dx8.lib")

IDirect3D8          *g_pD3D;
IDirect3DDevice8    *g_pd3dDevice;

// Initialise DirectX Graphics and go to full screen mode.
BOOL InitDirectXGraphics()
{
    g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);
    if (g_pD3D == NULL)
        return FALSE;

    return InitDevice(TRUE);
}

BOOL InitDevice(BOOL bFullScreen)
{
    HRESULT h;

    ShowWindow(hWndMain, SW_HIDE);
    if (bFullScreen)
    {
        // Set up main window to cover the screen
        SetWindowLong(hWndMain, GWL_STYLE, WS_POPUP);
        SetWindowPos(hWndMain, NULL, 0, 0, 
            GetSystemMetrics(SM_CXSCREEN), 
            GetSystemMetrics(SM_CYSCREEN), SWP_NOZORDER);
    }
    else
    {
        // Set up main window to be not sizable
        SetWindowLong(hWndMain, GWL_STYLE, WS_POPUP|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_MINIMIZEBOX);
    }
    ShowWindow(hWndMain, SW_SHOW);

    D3DPRESENT_PARAMETERS d3dpp; 
    ZeroMemory(&d3dpp, sizeof(d3dpp));
    if (bFullScreen)
    {
        d3dpp.Windowed = FALSE;
        d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;
        d3dpp.BackBufferCount = 2;
        d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
        d3dpp.hDeviceWindow = hWndMain;
        d3dpp.BackBufferWidth = 640;
        d3dpp.BackBufferHeight = 480;
        d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
        d3dpp.FullScreen_PresentationInterval = D3DPRESENT_INTERVAL_ONE;
    }
    else
    {
        // Get the current desktop display mode
        D3DDISPLAYMODE d3ddm;
        if (FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
            return FALSE;

        // Set up the structure used to create the D3DDevice. Most parameters are
        // zeroed out. We set Windowed to TRUE, since we want to do D3D in a
        // window, and then set the SwapEffect to "discard", which is the most
        // efficient method of presenting the back buffer to the display.  And 
        // we request a back buffer format that matches the current desktop display 
        // format.
        d3dpp.Windowed = TRUE;
        d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
        d3dpp.BackBufferFormat = d3ddm.Format;
    }
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    h = g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWndMain,
                            D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                            &d3dpp, &g_pd3dDevice);
    if (FAILED(h))
        return FALSE;

    if (!bFullScreen)
    {
        // Set up main window to be 640x480, not sizable
        int cxScreen = GetSystemMetrics(SM_CXSCREEN);
        int cyScreen = GetSystemMetrics(SM_CYSCREEN);

        RECT    r;
        
        r.left = (cxScreen - 640) / 2;
        r.top = (cyScreen - 480) / 2;
        r.right = r.left + 640;
        r.bottom = r.top + 480;
        AdjustWindowRect(&r, WS_POPUP|WS_CAPTION|WS_BORDER|WS_SYSMENU|WS_MINIMIZEBOX, FALSE);
        SetWindowPos(hWndMain, NULL, r.left, r.top, r.right - r.left, r.bottom - r.top, SWP_NOZORDER);
    }

    // Turn off culling
    g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

    // Turn off D3D lighting
    g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

    // Turn off the zbuffer
    g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE);

    // Turn on alpha-blending
    g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
    return TRUE;
}

void BeginScene()
{
    // Clear to red in debug mode. This allows us to see where we're not drawing
#ifdef _DEBUG
    g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                         D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);
#endif
    g_pd3dDevice->BeginScene();
}

void EndScene()
{
    g_pd3dDevice->EndScene();
}

void Flip()
{
    g_pd3dDevice->Present(NULL, NULL, NULL, NULL);
}

void CloseDevice()
{
    if (g_pd3dDevice != NULL)
    {
        g_pd3dDevice->Release();
        g_pd3dDevice = NULL;
    }
}

void ExitDirectXGraphics()
{
    CloseDevice();
    if (g_pD3D != NULL)
    {
        g_pD3D->Release();
        g_pD3D = NULL;
    }
}

////////////////////////////////////////////////////////////////////////////////
// Object handling

struct CCustomVertex
{
    float   x, y, z, rhw;   // The transformed position for the vertex.
    DWORD   dwColor;        // The vertex colour.
    float   tu, tv;         // Texture co-ordinates.
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)

struct  CRectangularObject
{
    IDirect3DTexture8   *pTexture;
    CCustomVertex       vertices[4];
};

CRectangularObject      *g_pObjects;
UINT                    g_nObjectBufferSize, g_nNewObjectPointer;
IDirect3DVertexBuffer8  *g_pVB;

// Create the initial object buffers
BOOL SetObjectBufferSize(UINT nBufferSize)
{
    if (g_pObjects != NULL)
        return FALSE;

    g_pObjects = new CRectangularObject[nBufferSize];
    if (g_pObjects == NULL)
        return FALSE;

    g_nObjectBufferSize = nBufferSize;
    g_nNewObjectPointer = 0;

    return TRUE;
}

void ReleaseObjectBuffer()
{
    if (g_pObjects != NULL)
    {
        delete [] g_pObjects;
        g_pObjects = NULL;
    }
}

// Initialise an object in the buffer
int CreateRectangularObject(int x, int y, int cx, int cy,
                            IDirect3DTexture8 *pTexture, int xSrc, int ySrc)
{
    // Triangles:
    //
    // (x,y) ___ (x + cx, y)
    //      |  /|
    //      | / |
    //      |/__|(x + cx, y + cy)
    // (x, y + cy)

    if (g_pObjects == NULL)
        return -1;
    
    if (g_pVB != NULL)
        return -1;

    if (g_nNewObjectPointer == g_nObjectBufferSize)
        return -1;

    CRectangularObject  *pObj = &g_pObjects[g_nNewObjectPointer];

    D3DSURFACE_DESC desc;
    pTexture->GetLevelDesc(0, &desc);

    TRACE(_T("Creating object from texture %08X (%d,%d)\n"), pTexture, desc.Width, desc.Height);

    pObj->vertices[0].x = pObj->vertices[2].x = (float)x - 0.5f;
    pObj->vertices[1].x = pObj->vertices[3].x = (float)(x + cx) - 0.5f;
    pObj->vertices[0].y = pObj->vertices[1].y = (float)y - 0.5f;
    pObj->vertices[2].y = pObj->vertices[3].y = (float)(y + cy) - 0.5f;
    
    // z and rhw co-ordinates are fixed at 0.5 and 1.0 respectively
    pObj->vertices[0].z = pObj->vertices[1].z = 
    pObj->vertices[2].z = pObj->vertices[3].z = 0.5f;
    
    pObj->vertices[0].rhw = pObj->vertices[1].rhw = 
    pObj->vertices[2].rhw = pObj->vertices[3].rhw = 1.0f;
    
    // White colour to avoid clashing with the texture
    pObj->vertices[0].dwColor = pObj->vertices[1].dwColor = 
    pObj->vertices[2].dwColor = pObj->vertices[3].dwColor = 0xFFFFFFFF;

    // Texture co-ordinates
    pObj->vertices[0].tu = pObj->vertices[2].tu = (float)xSrc / (float)desc.Width;
    pObj->vertices[1].tu = pObj->vertices[3].tu = (float)(xSrc + cx) / (float)desc.Width;
    
    pObj->vertices[0].tv = pObj->vertices[1].tv = (float)ySrc / (float)desc.Height;
    pObj->vertices[2].tv = pObj->vertices[3].tv = (float)(ySrc + cy) / (float)desc.Height;

    pObj->pTexture = pTexture;
    
    g_nNewObjectPointer++;
    return g_nNewObjectPointer - 1;
}

// Move a previously created object
BOOL MoveObject(int nIndex, int x, int y)
{
    if (g_pObjects == NULL)
        return FALSE;
    if (nIndex == -1)
        return FALSE;
    if ((UINT)nIndex >= g_nNewObjectPointer)
        return FALSE;

    CRectangularObject  *pObj = &g_pObjects[nIndex];
    int                 cx = (int)(pObj->vertices[3].x - pObj->vertices[0].x);
    int                 cy = (int)(pObj->vertices[3].y - pObj->vertices[0].y);

    pObj->vertices[0].x = pObj->vertices[2].x = (float)x - 0.5f;
    pObj->vertices[1].x = pObj->vertices[3].x = (float)(x + cx) - 0.5f;
    pObj->vertices[0].y = pObj->vertices[1].y = (float)y - 0.5f;
    pObj->vertices[2].y = pObj->vertices[3].y = (float)(y + cy) - 0.5f;
    return TRUE;
}

BOOL SetObjectTexture(int nIndex, IDirect3DTexture8 *pTexture)
{
    if (g_pObjects == NULL)
        return FALSE;
    if (nIndex == -1)
        return FALSE;
    if ((UINT)nIndex >= g_nNewObjectPointer)
        return FALSE;

    g_pObjects[nIndex].pTexture = pTexture;
    return TRUE;
}

////////////////////////////////////////////////////////////////////////////////
// Vertex buffer handling

// Create the vertex buffer which holds the sprite co-ordinates
BOOL CreateVertexBuffer()
{
    HRESULT h;

    if (g_pVB != NULL)
        return FALSE;

    h = g_pd3dDevice->CreateVertexBuffer(g_nNewObjectPointer * 4 * sizeof(CCustomVertex),
                                    D3DUSAGE_WRITEONLY, 
                                    D3DFVF_CUSTOMVERTEX,
                                    D3DPOOL_DEFAULT, &g_pVB);
    if (FAILED(h))
        return FALSE;
    // Lock the vertex buffer and put our co-ordinates in it
    CCustomVertex   *pVertices;

    h = g_pVB->Lock(0, 0, (BYTE **)&pVertices, D3DLOCK_DISCARD);
    if (FAILED(h))
        return FALSE;
    for (UINT i = 0; i < g_nNewObjectPointer; i++)
    {
        memcpy(pVertices, g_pObjects[i].vertices, 4 * sizeof(CCustomVertex));
        pVertices += 4;
    }

    g_pVB->Unlock();
    return TRUE;
}

// Refresh the vertex buffer co-ordinates
BOOL RefreshVertexBuffer()
{
    HRESULT h;

    if (g_pVB == NULL)
        return FALSE;

    // Lock the vertex buffer and put our co-ordinates in it
    CCustomVertex   *pVertices;

    h = g_pVB->Lock(0, 0, (BYTE **)&pVertices, D3DLOCK_DISCARD);
    if (FAILED(h))
        return FALSE;
    
    for (UINT i = 0; i < g_nNewObjectPointer; i++)
    {
        memcpy(pVertices, g_pObjects[i].vertices, 4 * sizeof(CCustomVertex));
        pVertices += 4;
    }
    g_pVB->Unlock();
    return TRUE;
}

// Release the vertex buffer
void ReleaseVertexBuffer()
{
    if (g_pVB != NULL)
    {
        g_pVB->Release();
        g_pVB = NULL;
    }
}

void BeginDrawingObjects()
{
    // Set the render state up for source alpha blending.
    g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
    g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

    g_pd3dDevice->SetStreamSource(0, g_pVB, sizeof(CCustomVertex));
    g_pd3dDevice->SetVertexShader(D3DFVF_CUSTOMVERTEX);

    // Get the colour information solely from the texture.
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    // Get the alpha information solely from the texture.
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1);
    g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
}

void DrawObject(int nIndex)
{
    g_pd3dDevice->SetTexture(0, g_pObjects[nIndex].pTexture);

    g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, nIndex * 4, 2);
}

IDirect3DTexture8 *LoadTexture(LPCTSTR lpFilename)
{
    IDirect3DTexture8   *pTexture;
//  HRESULT h = D3DXCreateTextureFromFile(g_pd3dDevice, lpFilename, &pTexture);
    // Create an unfiltered, unsized texture
    HRESULT h = D3DXCreateTextureFromFileEx(g_pd3dDevice, lpFilename, 
        D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 
        0, D3DFMT_UNKNOWN, D3DPOOL_DEFAULT, D3DX_FILTER_NONE, D3DX_DEFAULT,
        0, NULL, NULL, &pTexture);
    if (FAILED(h))
        return NULL;
    return pTexture;
}