//-----------------------------------------------------------------------------
// File: gfx.cpp
//
// Desc: Graphics routines
//
// Copyright (C) 1995-2001 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------
#include "duel.h"
#include "gfx.h"
#include "diutil.h"
#include "ddutil.h"


//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
extern BOOL             g_bFullscreen;   // Window or fullscreen mode ?
extern HWND             g_hwndMain;      // Main window
extern DWORD            g_dwKeys;               
extern int              g_nProgramState; // Current state of program
extern RECT             g_rcWindow;      // Client window rectangle
extern HINSTANCE        g_hInst;         // Application instance handle


LPDIRECTDRAW            g_pDD             = NULL; // DirectDraw interface
LPDIRECTDRAWPALETTE     g_pArtPalette     = NULL; // Game screen palette
LPDIRECTDRAWPALETTE     g_pSplashPalette  = NULL; // Splash screen palette
LPDIRECTDRAWSURFACE     g_pddsFrontBuffer = NULL; // primary surface
LPDIRECTDRAWSURFACE     g_pddsBackBuffer  = NULL; // back buffer for animation
LPDIRECTDRAWSURFACE     g_pddsShip[4];            // ship bitmaps
LPDIRECTDRAWSURFACE     g_pddsNumbers;            // Numbers bitmap
DWORD                   g_dwFillColor;




//-----------------------------------------------------------------------------
// Name: InitGraphics()
// Desc:
//-----------------------------------------------------------------------------
HRESULT InitGraphics()
{
    DDCAPS          ddcaps;
    HRESULT         hr;
    DDSURFACEDESC   ddsd;
    DDSCAPS         ddscaps;

    // Create a window
    g_hwndMain = CreateWindowEx( WS_EX_APPWINDOW, TEXT("DuelClass"),
                                 TEXT("Duel"), WS_POPUP | WS_SYSMENU, 0, 0, 
                                 GetSystemMetrics(SM_CXSCREEN),
                                 GetSystemMetrics(SM_CYSCREEN),
                                 NULL, NULL, g_hInst, NULL );
    if( NULL == g_hwndMain )
        return E_FAIL;

    UpdateWindow( g_hwndMain );
    SetFocus( g_hwndMain );

    // DDraw stuff begins here
    if( FAILED( hr = DirectDrawCreate( NULL, &g_pDD, NULL ) ) )
    {
        ShowError(IDS_DDRAW_ERROR_DDC);
        return E_FAIL;
    }

    // Set access mode based on fullscreen/window
    if( g_bFullscreen ) 
    {
        hr = g_pDD->SetCooperativeLevel( g_hwndMain,
                            DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );
    }
    else
    {
        hr = g_pDD->SetCooperativeLevel( g_hwndMain,
                            DDSCL_NORMAL);
    }

    if( FAILED(hr) )
    {
        ShowError(IDS_DDRAW_ERROR_SCL);
        return E_FAIL;
    }

    if( g_bFullscreen )
    {
        // Set the mode to 640 by 480 by 8
        if( FAILED( g_pDD->SetDisplayMode( 640, 480, 8 ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_SDM);
            return E_FAIL;
        }
    }
    else
    {
        RECT  rcWork;
        RECT  rc;
        DWORD dwStyle;

        // If we are still a WS_POPUP window we should convert to a
        // normal app window so we look like a windows app.
        dwStyle  = GetWindowStyle(g_hwndMain);
        dwStyle &= ~WS_POPUP;
        dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX;
        SetWindowLong( g_hwndMain, GWL_STYLE, dwStyle );

        // Aet window size
        SetRect( &rc, 0, 0, MAX_DEFWIN_X, MAX_DEFWIN_Y );

        AdjustWindowRectEx( &rc, GetWindowStyle(g_hwndMain),
                            GetMenu(g_hwndMain) != NULL,
                            GetWindowExStyle(g_hwndMain) );

        SetWindowPos( g_hwndMain, NULL, 0, 0, rc.right-rc.left,
                      rc.bottom-rc.top,
                      SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);

        SetWindowPos( g_hwndMain, HWND_NOTOPMOST, 0, 0, 0, 0,
                      SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        //  Make sure our window does not hang outside of the work area
        SystemParametersInfo( SPI_GETWORKAREA, 0, &rcWork, 0 );
        GetWindowRect( g_hwndMain, &rc );
        if( rc.left < rcWork.left ) rc.left = rcWork.left;
        if( rc.top  < rcWork.top )  rc.top  = rcWork.top;
        SetWindowPos( g_hwndMain, NULL, rc.left, rc.top, 0, 0,
                      SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
    }

    // Check the color key hardware capabilities
    ddcaps.dwSize = sizeof( ddcaps );
    memset( &ddsd, 0, sizeof( ddsd ) );
    ddsd.dwSize = sizeof( ddsd );

    if( g_bFullscreen )
    {
        // Create surfaces
        ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                              DDSCAPS_FLIP |
                              DDSCAPS_COMPLEX;
        ddsd.dwBackBufferCount = 1;
        if( FAILED( hr = g_pDD->CreateSurface( &ddsd, &g_pddsFrontBuffer,
                                               NULL ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_CREATESURFACE);
            return E_FAIL;
        }

        // Get a pointer to the back buffer
        ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
        if( FAILED( hr = g_pddsFrontBuffer->GetAttachedSurface( &ddscaps,
                                                         &g_pddsBackBuffer ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_GAS);
            return E_FAIL;
        }
    }
    else
    {
        LPDIRECTDRAWCLIPPER pcClipper;
        
        // Window case, create the primary surface
        // and create a backbuffer in offscreen memory
        ddsd.dwFlags = DDSD_CAPS;
        ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

        if( FAILED( g_pDD->CreateSurface( &ddsd, &g_pddsFrontBuffer, NULL ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_CREATESURFACE);
            return E_FAIL;
        }

        ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;    
        ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
        ddsd.dwWidth = MAX_DEFWIN_X;
        ddsd.dwHeight = MAX_DEFWIN_Y;
        if( FAILED( hr = g_pDD->CreateSurface( &ddsd, &g_pddsBackBuffer, NULL ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_CREATESURFACE);
            return E_FAIL;
        }

        if( FAILED( hr = g_pDD->CreateClipper( 0, &pcClipper, NULL) ) )
        {
            ShowError(IDS_DDRAW_ERROR_CC);
            return E_FAIL;
        }

        if( FAILED( hr = pcClipper->SetHWnd( 0, g_hwndMain) ) )
        {
            pcClipper->Release();
            ShowError(IDS_DDRAW_ERROR_SH);
            return E_FAIL;
        }

        if( FAILED( hr = g_pddsFrontBuffer->SetClipper( pcClipper) ) )
        {
            pcClipper->Release();
            ShowError(IDS_DDRAW_ERROR_SC);
            return E_FAIL;
        }

        // Done with clipper
        pcClipper->Release();
    }

    ddsd.dwFlags        = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; 
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;

    ddsd.dwWidth  = 320;
    ddsd.dwHeight = 128;
    
    for( DWORD i=0; i<4; i++ )
    {
        if( FAILED( hr = g_pDD->CreateSurface( &ddsd, &g_pddsShip[i], NULL ) ) )
        {
            ShowError(IDS_DDRAW_ERROR_CREATESURFACE);
            return E_FAIL;
        }   
    }

    ddsd.dwHeight = 16;
    if( FAILED( hr = g_pDD->CreateSurface( &ddsd, &g_pddsNumbers, NULL ) ) )
    {
        ShowError(IDS_DDRAW_ERROR_CREATESURFACE);
        return E_FAIL;
    }

    if( FAILED( RestoreSurfaces() ) )
    {
        ShowError(IDS_DDRAW_ERROR_RS);
        return E_FAIL;
    }
    
    g_dwKeys = 0;
    ShowWindow( g_hwndMain, SW_SHOW);
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CleanupGraphics()
// Desc:
//-----------------------------------------------------------------------------
VOID CleanupGraphics()
{
    for( DWORD i=0; i<4; i++ )
        if( g_pddsShip[i] )
            g_pddsShip[i]->Release();
    if( g_pddsNumbers )
        g_pddsNumbers->Release();
    if( g_pddsFrontBuffer )
        g_pddsFrontBuffer->Release();
    if( g_pArtPalette )
        g_pArtPalette->Release();
    if( g_pSplashPalette )
        g_pSplashPalette->Release();
    if( !g_bFullscreen && g_pddsBackBuffer )
        g_pddsBackBuffer->Release();
    if( g_pDD )
        g_pDD->Release();
}




//-----------------------------------------------------------------------------
// Name: BltSplashScreen()
// Desc:
//-----------------------------------------------------------------------------
HRESULT BltSplashScreen( RECT* prc )
{
    HRESULT hr;
    HBITMAP hbm;

    if( ( g_pddsFrontBuffer == NULL ) || ( g_pSplashPalette == NULL ) ||
        ( g_pddsBackBuffer == NULL ) )
        return E_FAIL;

    // Set the palette before loading the splash screen
    g_pddsFrontBuffer->SetPalette( g_pSplashPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle( NULL ), TEXT("SPLASH"),
                              IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if( NULL == hbm )
        return E_FAIL;
    
    // If the surface is lost, DDUtil_CopyBitmap will fail and the surface will
    // be restored below.
    hr = DDUtil_CopyBitmap( g_pddsBackBuffer, hbm, 0, 0, 0, 0 );

    DeleteObject( hbm );

    while( 1 )
    {
        hr = g_pddsFrontBuffer->Blt( &g_rcWindow, g_pddsBackBuffer,
                                     prc, DDBLT_WAIT, NULL);
        if( SUCCEEDED(hr) )
            return S_OK;
        if( hr == DDERR_SURFACELOST )
            if( FAILED( RestoreSurfaces() ) )
                return E_FAIL;
        if( hr != DDERR_WASSTILLDRAWING )
            return E_FAIL;
    }
}




//-----------------------------------------------------------------------------
// Name: BltNumber()
// Desc:
//-----------------------------------------------------------------------------
HRESULT BltNumber( CHAR* strScore, int x, int y )
{
    while( *strScore )
    {
        RECT src;
        src.left   = ((*strScore++)-'0')*16;
        src.top    = 0;
        src.right  = src.left + 16;
        src.bottom = src.top + 16;

        BltObject( x, y, g_pddsNumbers, &src, DDBLTFAST_SRCCOLORKEY );
        x += 16;
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: BltObject()
// Desc: 
//-----------------------------------------------------------------------------
HRESULT BltObject( int x, int y, LPDIRECTDRAWSURFACE pdds, RECT* prc,
                   DWORD flags )
{
	if( NULL == pdds )
		return E_FAIL;
    
	while( 1 )
    {
        HRESULT hr = g_pddsBackBuffer->BltFast( x, y, pdds, prc, flags );
        if( FAILED(hr) )
        {
            if( hr == DDERR_WASSTILLDRAWING )
                continue;

            if( hr == DDERR_SURFACELOST )
                if( SUCCEEDED( RestoreSurfaces() ) )
                    continue;
            return E_FAIL;
        }

        return S_OK;
    }
}




//-----------------------------------------------------------------------------
// Name: EraseScreen()
// Desc: 
//-----------------------------------------------------------------------------
VOID EraseScreen()
{
    // Erase the background
    DDBLTFX ddbltfx;
    ZeroMemory( &ddbltfx, sizeof(ddbltfx) );
    ddbltfx.dwSize = sizeof(ddbltfx);
#ifdef NONAMELESSUNION
    ddbltfx.u5.dwFillColor = g_dwFillColor;
#else
    ddbltfx.dwFillColor = g_dwFillColor;
#endif

    while( 1 )
    {
        HRESULT hr = g_pddsBackBuffer->Blt( NULL, NULL, NULL,
                                            DDBLT_COLORFILL, &ddbltfx );
        if( SUCCEEDED(hr) )
            return;
        
        if( hr == DDERR_SURFACELOST )
        {
            if( FAILED( RestoreSurfaces() ) )
                return;
        }

        if( hr != DDERR_WASSTILLDRAWING )
            return;
    }
}




//-----------------------------------------------------------------------------
// Name: FlipScreen()
// Desc: 
//-----------------------------------------------------------------------------
VOID FlipScreen()
{
    // Flip the surfaces
    if( g_bFullscreen )
    {
        while( 1 )
        {
            HRESULT hr = g_pddsFrontBuffer->Flip( NULL, 0 );

            if( hr == DDERR_SURFACELOST )
            {
                if( FAILED( RestoreSurfaces() ) )
                    return;
            }
            if( hr != DDERR_WASSTILLDRAWING )
                    return;
        }
    }
    else
    {
        g_pddsFrontBuffer->Blt( &g_rcWindow, g_pddsBackBuffer, NULL,
                                DDBLT_WAIT, NULL );
    }
}





//-----------------------------------------------------------------------------
// Name: RestoreSurfaces()
// Desc:
//-----------------------------------------------------------------------------
HRESULT RestoreSurfaces()
{
    HRESULT hr;
    HBITMAP hbm;

    if( FAILED( hr = g_pddsFrontBuffer->Restore() ) )
        return hr;
    if( FAILED( hr = g_pddsBackBuffer->Restore() ) )
        return hr;

    for( DWORD i=0; i<4; i++ )
        if( FAILED( hr = g_pddsShip[i]->Restore() ) )
            return hr;

    // Create and set the palette for the splash bitmap
    g_pSplashPalette = DDUtil_LoadPalette( g_pDD, TEXT("SPLASH") );
    if( NULL == g_pSplashPalette )
        return E_FAIL;

    // Create and set the palette for the art bitmap
    g_pArtPalette = DDUtil_LoadPalette( g_pDD, TEXT("Duel8") );
    if( NULL == g_pArtPalette )
        return E_FAIL;

    // set the palette before loading the art
    g_pddsFrontBuffer->SetPalette( g_pArtPalette );

    hbm = (HBITMAP)LoadImage( GetModuleHandle(NULL), TEXT("Duel8"),
                              IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION );
    if( NULL == hbm )
        return E_FAIL;

    if( FAILED( hr = DDUtil_CopyBitmap( g_pddsShip[0], hbm, 0, 0, 320, 128 ) ) )
    {
        DeleteObject( hbm );
        return E_FAIL;
    }

    if( FAILED( hr = DDUtil_CopyBitmap( g_pddsShip[1], hbm, 0, 128, 320, 128 ) ) )
    {
        DeleteObject( hbm );
        return E_FAIL;
    }

    if( FAILED( hr = DDUtil_CopyBitmap( g_pddsShip[2], hbm, 0, 256, 320, 128 ) ) )
    {
        DeleteObject( hbm );
        return E_FAIL;
    }

    if( FAILED( hr = DDUtil_CopyBitmap( g_pddsShip[3], hbm, 0, 384, 320, 128 ) ) )
    {
        DeleteObject( hbm );
        return E_FAIL;
    }

    if( FAILED( hr = DDUtil_CopyBitmap( g_pddsNumbers, hbm, 0, 512, 320, 16 ) ) )
    {
        DeleteObject( hbm );
        return E_FAIL;
    }

    DeleteObject( hbm );

    // set colorfill colors and colorkeys according to bitmap contents
    g_dwFillColor = DDUtil_ColorMatch( g_pddsShip[0], CLR_INVALID );
    
    DDUtil_SetColorKey( g_pddsShip[0], CLR_INVALID );
    DDUtil_SetColorKey( g_pddsShip[1], CLR_INVALID );
    DDUtil_SetColorKey( g_pddsShip[2], CLR_INVALID );
    DDUtil_SetColorKey( g_pddsShip[3], CLR_INVALID );
    DDUtil_SetColorKey( g_pddsNumbers, CLR_INVALID );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: SetGamePalette()
// Desc:
//-----------------------------------------------------------------------------
VOID SetGamePalette()
{
    if( g_pddsFrontBuffer )
        g_pddsFrontBuffer->SetPalette( g_pArtPalette );
}




