/***************************************************************************
 dx_driver.cpp Copyright (C) 2002,2003,2005-6 Alexander R Angas,
               Some portions Copyright (C) 1999 Dmitry Jemerov

 This program may be modified and copied freely according to the terms of
 the GNU general public license (GPL), as long as the above copyright
 notice and the licensing information contained herein are preserved.

 Please refer to www.gnu.org for licensing details.

 This work is provided AS IS, without warranty of any kind, expressed or
 implied, including but not limited to the warranties of merchantibility,
 noninfringement, and fitness for a specific purpose. The author will not
 be held liable for any damage caused by this work or derivatives of it.

 By using this source code, you agree to the licensing terms as stated
 above.

 Please contact the maintainer for bug reports or inquiries.

 Current Maintainer:

		Alexander R Angas (Alex) <arangas AT internode dot on dot net>

 History:

	20051106 (AAngas) - Rewrite
	20060201 (AAngas) - Fixed wrong format for texture priority maps
	20060205 (AAngas) - Changed pointer to use D3DXSprite
	20060208 (AAngas) - Fixed pointer alpha blending bug


TODO:
	Full screen
	Lost devices
	Moving and activating window
	Allow user to specify hardware or software vertex processing
	Add fancies

***************************************************************************/

#ifdef HAVE_DIRECTX

#ifndef __cplusplus
#error NOTE: This file MUST be compiled as C++. In Visual C++, use the /Tp command line option.
#endif

#include <graphics_directx.h>

#if (DIRECT3D_VERSION < 0x0800)
#error ERROR: DirectX 8 or higher is required for this driver.
#endif


// Store driver flags
static int flags = 0;


// Windows message processing
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
	return DefWindowProc( hWnd, msg, wParam, lParam );
}


///// RENDERING

// Render the scene to screen
static gfx_return_value_t
RenderD3D(struct _gfx_driver *drv)
{
	HRESULT hr;

	// Check we haven't lost the device
	if (CheckDevice(drv))
	{
		// Begin scene
		DODX( (dx_state->pDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0, 0)), RenderD3D );
		DODX( (dx_state->pDevice->BeginScene()), RenderD3D );

		// Set texture
		DODX( (dx_state->pDevice->SetTexture( 0, dx_state->pTexVisuals[PRIMARY_VIS] )), RenderD3D );	// Scene image

		// Set texture states for scene
		DODX( (dx_state->pDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE )), RenderD3D );
		DODX( (dx_state->pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE )), RenderD3D );
		DODX( (dx_state->pDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE )), RenderD3D );

		// Set vertices and how to draw
		DODX( (dx_state->pDevice->SetStreamSource(0, dx_state->pVertBuff, sizeof(CUSTOMVERTEX))), RenderD3D );
		DODX( (dx_state->pDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX )), RenderD3D );
		DODX( (dx_state->pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2)), RenderD3D );

		// Draw the pointer sprite
		if (dx_state->pTexPointer)
		{
			D3DXVECTOR2 pointerPos((float)drv->pointer_x, (float)drv->pointer_y);
			DODX( (dx_state->pSPointer->Begin()), RenderD3D );
			DODX( (dx_state->pSPointer->Draw(dx_state->pTexPointer, NULL, NULL, NULL, 0.0, &pointerPos, 0xFFFFFFFF)), RenderD3D );
			DODX( (dx_state->pSPointer->End()), RenderD3D );
		}

		// Present scene
		DODX( (dx_state->pDevice->EndScene()), RenderD3D );
		DODX( (dx_state->pDevice->Present(NULL, NULL, NULL, NULL)), RenderD3D );
	}

	return GFX_OK;
}


// Check device hasn't been lost
static int
CheckDevice(struct _gfx_driver *drv)
{
	HRESULT hr;
	switch ( dx_state->pDevice->TestCooperativeLevel() )
	{
		case D3DERR_DEVICELOST: return false;	// Lost window focus

		case D3DERR_DEVICENOTRESET:				// We're back!
		{
			// Reset with our presentation parameters and reinitialise the scene
			DODX( (dx_state->pDevice->Reset(&dx_state->presParams)), CheckDevice );
			if (hr != D3D_OK)
			   return false;

			InitScene(drv);
			return true;
		}

		default: return true;
	}
}


///// INITIALIZATION

// Create window to draw to
static gfx_return_value_t
InitWindow(struct _gfx_driver *drv)
{
	// Register the window class.
	ZeroMemory( &(dx_state->wc), sizeof(WNDCLASSEX) );
	dx_state->wc.cbSize = sizeof(WNDCLASSEX);
	dx_state->wc.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE;
	dx_state->wc.lpfnWndProc = MsgProc;
	dx_state->wc.hInstance = GetModuleHandle(NULL);
	dx_state->wc.lpszClassName = DX_CLASS_NAME;
	if ( RegisterClassEx( &dx_state->wc ) == 0 )
	{
		sciprintf("dx_init_specific(): RegisterClassEx failed (%u)\n", GetLastError());
		return GFX_FATAL;
	}

	// Create the application's window.
	dx_state->hWnd = CreateWindow(
		DX_CLASS_NAME, DX_APP_NAME,
		0,
		CW_USEDEFAULT, CW_USEDEFAULT, dx_state->xsize, dx_state->ysize,
		GetDesktopWindow(), NULL, dx_state->wc.hInstance, NULL );

	if ( dx_state->hWnd == NULL )
	{
		sciprintf("dx_init_specific(): CreateWindow failed (%u)\n", GetLastError());
		return GFX_FATAL;
	}

	// Show the window
	ShowWindow( dx_state->hWnd, SW_SHOWDEFAULT );
	UpdateWindow( dx_state->hWnd );

	return GFX_OK;
}


// Initialize Direct3D
static gfx_return_value_t
InitD3D(struct _gfx_driver *drv)
{
	HRESULT hr;

	sciprintf("Initializing Direct3D...\n");

	// Set our colour format
	dx_state->d3dFormat = D3DFMT_A8R8G8B8;
	dx_state->vertexProcessing = D3DCREATE_MIXED_VERTEXPROCESSING;

	// Create Direct3D object
	dx_state->pD3d = Direct3DCreate8( D3D_SDK_VERSION );
	if ( FAILED( dx_state->pD3d ) ) {
		sciprintf("InitD3D(): Direct3DCreate8 failed\n");
		return GFX_FATAL;
	}

	// Look for adapters
	for ( UINT adapterLoop = 0; adapterLoop < dx_state->pD3d->GetAdapterCount(); adapterLoop++ )
	{
		D3DADAPTER_IDENTIFIER8 adapterId;
		DODX( (dx_state->pD3d->GetAdapterIdentifier(adapterLoop, D3DENUM_NO_WHQL_LEVEL, &adapterId)), InitD3D );
		if ( FAILED( hr ) )
			break;
		if (adapterId.Driver[0] == '\0')
			break;
		sciprintf(" Adapter %u: %s\n", adapterLoop++, adapterId.Description);
	}
	if (dx_state->adapterId == -1)
		dx_state->adapterId = D3DADAPTER_DEFAULT;

	// Get device caps
	DODX( (dx_state->pD3d->GetDeviceCaps(dx_state->adapterId, D3DDEVTYPE_HAL, &(dx_state->deviceCaps))), InitD3D );
	if ( FAILED( hr ) ) {
		sciprintf("Sorry, this adapter does not have a 3D accelerated video driver installed.\n");
		return GFX_FATAL;
	}

	// Define presentation parameters
	ZeroMemory( &dx_state->presParams, sizeof(D3DPRESENT_PARAMETERS) );
	dx_state->presParams.Windowed = TRUE;							// We want windowed by default
	dx_state->presParams.SwapEffect = D3DSWAPEFFECT_DISCARD;		// Throw away last frame
	dx_state->presParams.hDeviceWindow = dx_state->hWnd;			// Window handle
	dx_state->presParams.BackBufferWidth  = dx_state->xsize;		// Back buffer dimensions
	dx_state->presParams.BackBufferHeight = dx_state->ysize;		// 
	dx_state->presParams.BackBufferFormat = dx_state->d3dFormat;	// Colour format

	// Check if user requested full screen
	if (flags & DX_FLAGS_FULLSCREEN)
	{
		if (dx_state->deviceCaps.Caps2 & D3DCAPS2_CANRENDERWINDOWED) {
			dx_state->presParams.Windowed = FALSE;
		} else {
			sciprintf("Sorry, DirectX will not render in full screen with your video card\n");
		}
	}

	// Get current display mode and optimise for this screen
	DODX( (dx_state->pD3d->GetAdapterDisplayMode( dx_state->adapterId, &dx_state->displayMode )), InitD3D );
	sciprintf("   Display %lu x %lu\n", dx_state->displayMode.Width, dx_state->displayMode.Height);
	if (dx_state->xfact == 0)
		dx_state->xfact = (int)(dx_state->displayMode.Width / 320);
	dx_state->yfact = dx_state->xfact;

	// Turn off Windows mouse pointer
	ShowCursor(FALSE);

	return GFX_OK;
}


// Initialize scene
static gfx_return_value_t
InitScene(struct _gfx_driver *drv)
{
	HRESULT hr;

	sciprintf("Creating scene...\n");

	// Describe how scene will be rendered
	DODX((dx_state->pDevice->SetRenderState( D3DRS_AMBIENT, RGB(255,255,255) )), InitScene);	// Maximum ambient light
	DODX((dx_state->pDevice->SetRenderState( D3DRS_LIGHTING, FALSE )), InitScene);				// Disable lighting features
	DODX((dx_state->pDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE )), InitScene);		// Don't cull back side of polygons
	DODX((dx_state->pDevice->SetRenderState( D3DRS_ZENABLE, D3DZB_FALSE )), InitScene);		 	// No depth buffering

	return GFX_OK;
}


// For user to set a driver-specific parameter
static int
dx_set_param(struct _gfx_driver *drv, char *attribute, char *value)
{
/*	// Full screen
	if (!strncmp(attribute, "fullscreen", 11)) {
		if (string_truep(value))
			flags |= DX_FLAGS_FULLSCREEN;
		else
			flags &= ~DX_FLAGS_FULLSCREEN;

		return GFX_OK;
	}
*/
	// Adapter ID
	if (!strncmp(attribute, "adapterid", 11)) {
		int aid = D3DADAPTER_DEFAULT;
		dx_state->adapterId = atoi(value);

		return GFX_OK;
	}

	sciprintf("Unrecognised attempt to set DirectX parameter \"%s\" to \"%s\"\n", attribute, value);
	return GFX_ERROR;
}


// Initialize a specific graphics mode
static int
dx_init_specific(struct _gfx_driver *drv,
				 int xfact, int yfact,	/* horizontal and vertical scaling */
				 int bytespp)			/* must be value 4 */
{
	HRESULT hr;
	int red_shift = 8, green_shift = 16, blue_shift = 24, alpha_shift = 32;
	int alpha_mask = 0x00000000, red_mask = 0x00ff0000, green_mask = 0x0000ff00, blue_mask = 0x000000ff;
	gfx_return_value_t d3dret;

	drv->state = (struct gfx_dx_struct_t *) sci_malloc(sizeof(gfx_dx_struct_t));
	ZeroMemory(drv->state, sizeof(gfx_dx_struct_t));
	dx_state->adapterId = -1;	// we will set this later

	// Check for scaling
	if (xfact != 0)
		dx_state->xfact = xfact;
	if (yfact != 0)
		dx_state->yfact = yfact;

	// Set up Direct3D
	d3dret = InitD3D(drv);
	if (d3dret != GFX_OK)
		return d3dret;

	// Set window size factor (now that InitD3D has told us how big the window is)
	dx_state->xsize = dx_state->xfact * 320;
	dx_state->ysize = dx_state->yfact * 200;
	dx_state->bpp = bytespp;

	// Create window
	InitWindow(drv);

	// Create D3D device
	DODX( (dx_state->pD3d->CreateDevice(dx_state->adapterId, D3DDEVTYPE_HAL, dx_state->hWnd,
		dx_state->vertexProcessing, &dx_state->presParams, &dx_state->pDevice)), dx_init_specific );

	// Create the scene
	d3dret = InitScene(drv);
	if (d3dret != GFX_OK)
		return d3dret;

	// Define and populate vertex buffers
	DODX((dx_state->pDevice->CreateVertexBuffer( 4 * sizeof(CUSTOMVERTEX), D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX,
												D3DPOOL_MANAGED, &dx_state->pVertBuff )), dx_init_specific);

	dx_state->pvData[0].p = D3DXVECTOR4(                  0.0f,                   0.0f, 0.0f, 1.0f);
    dx_state->pvData[1].p = D3DXVECTOR4((float)dx_state->xsize,                   0.0f, 0.0f, 1.0f);
    dx_state->pvData[2].p = D3DXVECTOR4(                  0.0f, (float)dx_state->ysize, 0.0f, 1.0f);
    dx_state->pvData[3].p = D3DXVECTOR4((float)dx_state->xsize, (float)dx_state->ysize, 0.0f, 1.0f);
	dx_state->pvData[0].colour = dx_state->pvData[1].colour = dx_state->pvData[2].colour = dx_state->pvData[3].colour = 0xffffffff;
	dx_state->pvData[0].t = D3DXVECTOR2(0.0f, 0.0f);
	dx_state->pvData[1].t = D3DXVECTOR2(1.0f, 0.0f);
	dx_state->pvData[2].t = D3DXVECTOR2(0.0f, 1.0f);
	dx_state->pvData[3].t = D3DXVECTOR2(1.0f, 1.0f);

	VOID *ptr;
	DODX((dx_state->pVertBuff->Lock(0, 0, (BYTE**)&ptr, 0)), dx_init_specific);
	memcpy(ptr, dx_state->pvData, sizeof(dx_state->pvData));
	DODX((dx_state->pVertBuff->Unlock()), dx_init_specific);

	// Create textures
	int i;
	for (i = 0; i < NUM_VISUAL_BUFFERS; i++)
	{
		DODX((dx_state->pDevice->CreateTexture(dx_state->xsize, dx_state->ysize,
			1, 0,
			dx_state->d3dFormat,
			D3DPOOL_MANAGED,
			&dx_state->pTexVisuals[i])), dx_init_specific);
	}
	for (i = 0; i < NUM_PRIORITY_BUFFERS; i++)
	{
		DODX((dx_state->pDevice->CreateTexture(dx_state->xsize, dx_state->ysize,
			1, 0,
			dx_state->d3dFormat,
			D3DPOOL_MANAGED,
			&dx_state->pTexPrioritys[i])), dx_init_specific);
	}

	// Create sprite for pointer
	DODX( (D3DXCreateSprite(dx_state->pDevice, &dx_state->pSPointer)), dx_init_specific );

	// Allocate priority maps
	for (int i = 0; i < NUM_PRIORITY_BUFFERS; i++)
	{
		dx_state->priority_maps[i] = gfx_pixmap_alloc_index_data(gfx_new_pixmap(dx_state->xsize, dx_state->ysize, GFX_RESID_NONE, -i, -777));
		if (!dx_state->priority_maps[i]) {
			GFXERROR("Out of memory: Could not allocate priority maps! (%dx%d)\n", dx_state->xsize, dx_state->ysize);
			return GFX_FATAL;
		}
		dx_state->priority_maps[i]->flags |= GFX_PIXMAP_FLAG_SCALED_INDEX;
	}

	// Set up the event queue
	dx_state->queue_first = 0;
	dx_state->queue_last  = 0;
	dx_state->queue_size  = 256;
	dx_state->event_queue = (sci_event_t *) sci_malloc (dx_state->queue_size * sizeof (sci_event_t));

	// Set up graphics mode for primary vis
	drv->mode = gfx_new_mode(dx_state->xfact, dx_state->yfact, dx_state->bpp,
			   red_mask, green_mask, blue_mask, alpha_mask,
			   red_shift, green_shift, blue_shift, alpha_shift,
			   (dx_state->bpp == 1) ? 256 : 0, 0);

	// Set up graphics mode for pointer
	gfx_mode_t *mode = gfx_new_mode(dx_state->xfact, dx_state->yfact, dx_state->bpp,
    									0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000,
										        24,         16,          8,          0,
										0, GFX_MODE_FLAG_REVERSE_ALPHA);
	dx_state->pointerMode = *mode;

	return GFX_OK;
}


// Initialize 'most natural' graphics mode
static int
dx_init(struct _gfx_driver *drv)
{
	return dx_init_specific(drv, NULL, NULL, 4);
}


// Uninitialize the current graphics mode
static void
dx_exit(struct _gfx_driver *drv)
{
	int i;

	if(drv->state == NULL)
		return;

	for (i = 0; i < NUM_PRIORITY_BUFFERS; i++)
		SAFE_RELEASE( dx_state->pTexPrioritys[i] );
	for (i = 0; i < NUM_VISUAL_BUFFERS; i++)
		SAFE_RELEASE( dx_state->pTexVisuals[i] );
	SAFE_RELEASE( dx_state->pTexPointer );
	SAFE_RELEASE( dx_state->pSPointer );
	SAFE_RELEASE( dx_state->pVertBuff );
    SAFE_RELEASE( dx_state->pDevice );
    SAFE_RELEASE( dx_state->pD3d );

	gfx_free_mode(&dx_state->pointerMode);

	if ( dx_state->event_queue )
		sci_free(dx_state->event_queue);
	dx_state->queue_size = 0;

	for (i = 0; i < NUM_PRIORITY_BUFFERS; i++)
		gfx_free_pixmap(drv, dx_state->priority_maps[i]);

    UnregisterClass( DX_CLASS_NAME, dx_state->wc.hInstance );
	DestroyWindow(dx_state->hWnd);

	sci_free(dx_state);
}


///// DRAWING

// Draws a single filled and possibly shaded rectangle to the back buffer
static int
dx_draw_filled_rect(struct _gfx_driver *drv, rect_t box,
			   gfx_color_t color1, gfx_color_t color2,
			   gfx_rectangle_fill_t shade_mode)
{
	if (color1.mask & GFX_MASK_VISUAL)
	{
		HRESULT hr;
		D3DLOCKED_RECT lockedRect;

		// Calculate colour value for line pixel
		UINT lineColor = (color1.alpha << 24) | (color1.visual.r << 16) | (color1.visual.g << 8) | color1.visual.b;
		RECT r = { box.x, box.y, box.x + box.xl, box.y + box.yl };
		RECT lr = r;

		// Fix bounds
		if (lr.left == lr.right)
			lr.right++;
		if (lr.top == lr.bottom)
			lr.bottom++;
		if ((UINT)r.right > dx_state->xsize)
			lr.right = r.right = dx_state->xsize;
		if ((UINT)r.bottom > dx_state->ysize)
			lr.bottom = r.bottom = dx_state->ysize;

//sciprintf("%08X  %i,%i -> %i,%i\n", lineColor, r.left, r.top, r.right, r.bottom);

		DODX( (dx_state->pTexVisuals[BACK_VIS]->LockRect(0, &lockedRect, &lr, 0)), dx_draw_filled_rect );
		UINT *rectPtr = (UINT*)lockedRect.pBits;

		// Going along x axis
		for (int y_pixel = r.top; y_pixel < r.bottom; y_pixel++)
		{
			UINT *startLine = rectPtr;
			for (int x_pixel = r.left; x_pixel < r.right; x_pixel++)
			{
				*rectPtr = lineColor;
				rectPtr++;
			}
			rectPtr = startLine;
			rectPtr += dx_state->xsize;
		}

		DODX( (dx_state->pTexVisuals[BACK_VIS]->UnlockRect(0)), dx_draw_filled_rect );
	}

	if (color1.mask & GFX_MASK_PRIORITY)
	{
		byte *pri;
		pri = dx_state->priority_maps[BACK_PRI]->index_data + box.x + box.y*(dx_state->xsize);
		for(int i=0; i<box.yl; i++)
		{
			memset(pri,color1.priority,box.xl);
			pri += dx_state->xsize;
		}
	}

	return GFX_OK;
}


// Draws a single line to the back buffer
static int
dx_draw_line(struct _gfx_driver *drv, rect_t line, gfx_color_t color,
		    gfx_line_mode_t line_mode, gfx_line_style_t line_style)
{
	if (color.mask & GFX_MASK_VISUAL) {

		// Calculate line thickness
		int xf = (line_mode == GFX_LINE_MODE_FINE) ? 1 : dx_state->xfact;
		int yf = (line_mode == GFX_LINE_MODE_FINE) ? 1 : dx_state->yfact;
		
		rect_t line_rect = { line.x, line.y, line.xl, line.yl };
		if (line_rect.xl == 0)	// Line parallel to x axis
			line_rect.xl += xf;
		if (line_rect.yl == 0)	// Line parallel to y axis
			line_rect.yl += yf;

		// Make sure priorities are not updated in dx_draw_filled_rect()
		gfx_color_t col = color;
		col.mask = GFX_MASK_VISUAL;

		dx_draw_filled_rect(drv, line_rect, col, col, GFX_SHADE_FLAT);
	}

	if (color.mask & GFX_MASK_PRIORITY) {
		unsigned int xc, yc;
		rect_t newline;

		newline.xl = line.xl;
		newline.yl = line.yl;

		for (xc = 0; xc < dx_state->xfact; xc++)
			for (yc = 0; yc < dx_state->yfact; yc++) {
				newline.x = line.x + xc;
				newline.y = line.y + yc;
				gfx_draw_line_pixmap_i(dx_state->priority_maps[BACK_PRI], newline, color.priority);
			}
	}

	return GFX_OK;
}


///// PIXMAPS

// Register the pixmap as a texture
static int
dx_register_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm)
{
	HRESULT hr;

	int i, xs;
	byte *s, *d;
	D3DLOCKED_RECT lockedRect;
	LPDIRECT3DTEXTURE8 newTex;
	DODX( (dx_state->pDevice->CreateTexture(pxm->xl, pxm->yl, 1, 0, dx_state->d3dFormat, D3DPOOL_MANAGED, &newTex )), dx_register_pixmap );

	// Do gfx crossblit
	DODX( (newTex->LockRect(0, &lockedRect, NULL, 0)), dx_register_pixmap );
	s = pxm->data;
	d = (byte *) lockedRect.pBits;
	xs = drv->mode->bytespp * pxm->xl;

	for(i = 0; i < pxm->yl; i++)
	{
		memcpy(d, s, xs);
		s += xs;
		d += lockedRect.Pitch;
	}
	DODX( (newTex->UnlockRect(0)), dx_register_pixmap );

	pxm->internal.info = newTex;
	pxm->internal.handle = SCI_DX_HANDLE_NORMAL;

	return GFX_OK;
}


// Unregister the pixmap
static int
dx_unregister_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm)
{
	SAFE_RELEASE((LPDIRECT3DTEXTURE8) (pxm->internal.info));
	pxm->internal.info = NULL;

	return GFX_OK;
}


// Draws part of a pixmap to the static or back buffer
static int
dx_draw_pixmap(struct _gfx_driver *drv, gfx_pixmap_t *pxm, int priority,
		      rect_t src, rect_t dest, gfx_buffer_t buffer)
{
	HRESULT hr;
	int bufnr = (buffer == GFX_BUFFER_STATIC) ? 2 : 1;
	int pribufnr = bufnr - 1;
	LPDIRECT3DTEXTURE8 srct, dstt;
	LPDIRECT3DSURFACE8 sbuf, dbuf;
	D3DLOCKED_RECT lockedRect;
	byte *pri_map = NULL;

	if (pxm->internal.handle == SCI_DX_HANDLE_GRABBED) {
		// copy from pxm->internal.info to visual[bufnr]
		RECT srcRect = RECT_T_TO_RECT(src);
		POINT dstPoint = { dest.x, dest.y };

		srct = (LPDIRECT3DTEXTURE8) (pxm->internal.info);
		dstt = dx_state->pTexVisuals[bufnr];

		DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
		DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
		DODX( (dx_state->pDevice->CopyRects(sbuf, &srcRect, 1, dbuf, &dstPoint)), dx_draw_pixmap );

		SAFE_RELEASE(sbuf);
		SAFE_RELEASE(dbuf);

		return GFX_OK;
	}


	// Create texture to temporarily hold visuals[bufnr]
	LPDIRECT3DTEXTURE8 temp;
	DODX( (dx_state->pDevice->CreateTexture(pxm->xl, pxm->yl, 1, 0, dx_state->d3dFormat, D3DPOOL_MANAGED, &temp)), dx_draw_pixmap );
	RECT srcRect = RECT_T_TO_RECT(dest);
	RECT destRect = { 0, 0, dest.xl, dest.yl };
	POINT dstPoint = { destRect.left, destRect.top };

	// Copy from visuals[bufnr] to temp
	srct = dx_state->pTexVisuals[bufnr];
	dstt = temp;
	DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
	DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
	DODX( (dx_state->pDevice->CopyRects(sbuf, &srcRect, 1, dbuf, &dstPoint)), dx_draw_pixmap );

	// Copy from given pixmap to temp
	DODX( (dbuf->LockRect(&lockedRect, &destRect, 0)), dx_draw_pixmap );
	gfx_crossblit_pixmap(drv->mode, pxm, priority, src, dest,
		       (byte *) lockedRect.pBits, lockedRect.Pitch,
		       dx_state->priority_maps[pribufnr]->index_data,
		       dx_state->priority_maps[pribufnr]->index_xl, 1,
		       GFX_CROSSBLIT_FLAG_DATA_IS_HOMED);
	DODX( (dbuf->UnlockRect()), dx_draw_pixmap );

	SAFE_RELEASE(sbuf);
	SAFE_RELEASE(dbuf);


	// Copy from temp to visuals[bufnr]
	RECT src2Rect = { 0, 0, dest.xl, dest.yl };
	POINT dst2Point = { dest.x, dest.y };

	srct = temp;
	dstt = dx_state->pTexVisuals[bufnr];
	DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_draw_pixmap );
	DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_draw_pixmap );
	DODX( (dx_state->pDevice->CopyRects(sbuf, &src2Rect, 1, dbuf, &dst2Point)), dx_draw_pixmap );

	SAFE_RELEASE(sbuf);
	SAFE_RELEASE(dbuf);
	SAFE_RELEASE(temp);

	return GFX_OK;
}


// Grabs an image from the visual or priority back buffer
static int
dx_grab_pixmap(struct _gfx_driver *drv, rect_t src, gfx_pixmap_t *pxm,
		      gfx_map_mask_t map)
{
	HRESULT hr;

	if (src.x < 0 || src.y < 0) {
		GFXERROR("Attempt to grab pixmap from invalid coordinates (%d,%d)\n", src.x, src.y);
		return GFX_ERROR;
	}

	if (!pxm->data) {
		GFXERROR("Attempt to grab pixmap to unallocated memory\n");
		return GFX_ERROR;
	}

	// Choose map to grab from
	switch (map) {

	case GFX_MASK_VISUAL: {
		LPDIRECT3DTEXTURE8 temp;
		LPDIRECT3DSURFACE8 tempSrf, backSrf;
		CONST RECT srcRect = RECT_T_TO_RECT(src);
		CONST POINT dstPoint = { 0, 0 };

		pxm->xl = src.xl;
		pxm->yl = src.yl;

		DODX( (dx_state->pDevice->CreateTexture(pxm->xl, pxm->yl, 1, 0, dx_state->d3dFormat, D3DPOOL_MANAGED, &temp)), dx_grab_pixmap );

		DODX( (dx_state->pTexVisuals[BACK_VIS]->GetSurfaceLevel(0, &backSrf)), dx_grab_pixmap );
		DODX( (temp->GetSurfaceLevel(0, &tempSrf)), dx_grab_pixmap );
		DODX( (dx_state->pDevice->CopyRects(backSrf, &srcRect, 1, tempSrf, &dstPoint)), dx_grab_pixmap );

		pxm->internal.info = temp;
		pxm->internal.handle = SCI_DX_HANDLE_GRABBED;
		pxm->flags |= GFX_PIXMAP_FLAG_INSTALLED | GFX_PIXMAP_FLAG_EXTERNAL_PALETTE | GFX_PIXMAP_FLAG_PALETTE_SET;

		SAFE_RELEASE(backSrf);
		SAFE_RELEASE(tempSrf);

		break;
	}

	case GFX_MASK_PRIORITY:
		sciprintf("FIXME: priority map grab not implemented yet!\n");
		break;

	default:
		sciprintf("Attempt to grab pixmap from invalid map 0x%02x\n", map);
		return GFX_ERROR;
	}

	return GFX_OK;
}


///// BUFFERING

// Updates the front buffer or the back buffers
static int
dx_update(struct _gfx_driver *drv, rect_t src, point_t dest, gfx_buffer_t buffer)
{
	HRESULT hr;
	LPDIRECT3DTEXTURE8 srct, dstt;
	LPDIRECT3DSURFACE8 sbuf, dbuf;
	CONST RECT srcRect = RECT_T_TO_RECT(src);
	CONST POINT dstPoint = { dest.x, dest.y };

	switch (buffer) {

	case GFX_BUFFER_FRONT:
		srct = dx_state->pTexVisuals[BACK_VIS];
		dstt = dx_state->pTexVisuals[PRIMARY_VIS];

		DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_update );
		DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_update );

		DODX( (dx_state->pDevice->CopyRects(sbuf, &srcRect, 1, dbuf, &dstPoint)), dx_update );

		SAFE_RELEASE(sbuf);
		SAFE_RELEASE(dbuf);

		RenderD3D(drv);
		break;

	case GFX_BUFFER_BACK:
		if (src.x == dest.x && src.y == dest.y)
			gfx_copy_pixmap_box_i(dx_state->priority_maps[BACK_PRI], dx_state->priority_maps[STATIC_PRI], src);

		srct = dx_state->pTexVisuals[STATIC_VIS];
		dstt = dx_state->pTexVisuals[BACK_VIS];

		DODX( (srct->GetSurfaceLevel(0, &sbuf)), dx_update );
		DODX( (dstt->GetSurfaceLevel(0, &dbuf)), dx_update );

		DODX( (dx_state->pDevice->CopyRects(sbuf, &srcRect, 1, dbuf, &dstPoint)), dx_update );

		SAFE_RELEASE(sbuf);
		SAFE_RELEASE(dbuf);

		break;

	default:
		GFXERROR("Invalid buffer %d in update!\n", buffer);
		return GFX_ERROR;
	}

	return GFX_OK;
}


// Sets the contents of the static visual and priority buffers
static int
dx_set_static_buffer(struct _gfx_driver *drv, gfx_pixmap_t *pic,
			    gfx_pixmap_t *priority)
{
	if (!pic->internal.info) {
		GFXERROR("Attempt to set static buffer with unregistered pixmap!\n");
		return GFX_ERROR;
	}

	HRESULT hr;
	LPDIRECT3DTEXTURE8 pii = (LPDIRECT3DTEXTURE8) (pic->internal.info);
	LPDIRECT3DSURFACE8 pbf;
	LPDIRECT3DTEXTURE8 vis = dx_state->pTexVisuals[STATIC_VIS];
	LPDIRECT3DSURFACE8 vbf;

	// Copy from pic to visual[static]
	DODX( (pii->GetSurfaceLevel(0, &pbf)), dx_set_static_buffer );
	DODX( (vis->GetSurfaceLevel(0, &vbf)), dx_set_static_buffer );
	DODX( (dx_state->pDevice->CopyRects(pbf, NULL, 0, vbf, NULL)), dx_set_static_buffer );

	SAFE_RELEASE(pbf);
	SAFE_RELEASE(vbf);

	// Copy priority map
	gfx_copy_pixmap_box_i(dx_state->priority_maps[STATIC_PRI], priority, gfx_rect(0, 0, dx_state->xsize, dx_state->ysize));

	return GFX_OK;
}


///// MOUSE

// Sets a new mouse pointer
static int
dx_set_pointer(struct _gfx_driver *drv, gfx_pixmap_t *pointer)
{
	HRESULT hr;

	if (pointer->data == NULL)
		return GFX_OK;

	LPDIRECT3DTEXTURE8 pntTex;
	LPDIRECT3DSURFACE8 pntSrf;

	// Get pointer dimensions and init
	POINTS pDims = {pointer->xl, pointer->yl};
	dx_state->pointerDims = pDims;
	RECT r = {0, 0, pointer->xl, pointer->yl};

	// Recreate pointer data according to the graphics mode we need
	gfx_pixmap_free_data(pointer);
	gfx_xlate_pixmap(pointer, &dx_state->pointerMode, GFX_XLATE_FILTER_NONE);

	// Create texture and fill with pointer data
	DODX( (dx_state->pDevice->CreateTexture(pointer->xl, pointer->yl, 1, 0, dx_state->d3dFormat, D3DPOOL_MANAGED, &pntTex )), dx_set_pointer );
	DODX( (pntTex->GetSurfaceLevel(0, &pntSrf)), dx_set_pointer );

	DODX( (D3DXLoadSurfaceFromMemory(pntSrf, NULL, &r, pointer->data, dx_state->d3dFormat, 256, NULL, &r, D3DX_FILTER_NONE, 0)), dx_set_pointer);

	SAFE_RELEASE(pntSrf);

	// Assign as current pointer texture
	if (dx_state->pTexPointer)
		SAFE_RELEASE(dx_state->pTexPointer);
	dx_state->pTexPointer = pntTex;

	return GFX_OK;
}


// Display mouse pointer
static int
show_pointer(struct _gfx_driver *drv, LPARAM pos)
{
	POINTS mousePos;	// mouse coordinates

	// Sort out coordinates
	mousePos = MAKEPOINTS(pos);

	// Update pos
	drv->pointer_x = mousePos.x;
	drv->pointer_y = mousePos.y;

	RenderD3D(drv);

	return GFX_OK;
}


///// EVENTS

// Get event from the queue
static sci_event_t
get_queue_event(gfx_dx_struct_t *ctx)
{
	if (ctx->queue_first == ctx->queue_size)
		ctx->queue_first = 0;

	if (ctx->queue_first == ctx->queue_last)
	{
		sci_event_t noevent;
		noevent.data = 0;
		noevent.type = SCI_EVT_NONE;
		noevent.buckybits = 0;
		return noevent;
	}
	else
		return ctx->event_queue [ctx->queue_first++];
}


// Add event to the queue
static void add_queue_event(gfx_dx_struct_t *ctx, int type, int data, short buckybits)
{
	if ((ctx->queue_last+1) % ctx->queue_size == ctx->queue_first)
	{
		/* Reallocate queue */
		int i, event_count;
		sci_event_t *new_queue;

		new_queue = (sci_event_t *) sci_malloc (ctx->queue_size * 2 * sizeof (sci_event_t));
		event_count = (ctx->queue_last - ctx->queue_first) % ctx->queue_size;
		for (i=0; i<event_count; i++)
			new_queue [i] = ctx->event_queue [(ctx->queue_first+i) % ctx->queue_size];
		free (ctx->event_queue);
		ctx->event_queue = new_queue;
		ctx->queue_size *= 2;
		ctx->queue_first = 0;
		ctx->queue_last  = event_count;
	}

	ctx->event_queue [ctx->queue_last].data = data;
	ctx->event_queue [ctx->queue_last].type = type;
	ctx->event_queue [ctx->queue_last++].buckybits = buckybits;
	if (ctx->queue_last == ctx->queue_size)
		ctx->queue_last=0;
}


// Add keystroke event to queue
static void add_key_event (gfx_dx_struct_t *ctx, int data)
{
	short buckybits = 0;

	if (GetAsyncKeyState (VK_RSHIFT))
		buckybits |= SCI_EVM_RSHIFT;
	if (GetAsyncKeyState (VK_LSHIFT))
		buckybits |= SCI_EVM_LSHIFT;
	if (GetAsyncKeyState (VK_CONTROL))
		buckybits |= SCI_EVM_CTRL;
	if (GetAsyncKeyState (VK_MENU))
		buckybits |= SCI_EVM_ALT;
	if (GetKeyState (VK_SCROLL) & 1)
		buckybits |= SCI_EVM_SCRLOCK;
	if (GetKeyState (VK_NUMLOCK) & 1)
		buckybits |= SCI_EVM_NUMLOCK;
	if (GetKeyState (VK_CAPITAL) & 1)
		buckybits |= SCI_EVM_CAPSLOCK;

	add_queue_event (ctx, SCI_EVT_KEYBOARD, data, buckybits);
}


// Add mouse event to queue
static void add_mouse_event(gfx_dx_struct_t *ctx, int type, int data, WPARAM wParam)
{
	short buckybits = 0;
	if (wParam & MK_SHIFT)
		buckybits |= SCI_EVM_LSHIFT | SCI_EVM_RSHIFT;
	if (wParam & MK_CONTROL)
		buckybits |= SCI_EVM_CTRL;

	add_queue_event (ctx, type, data, buckybits);
}


// Returns the next event in the event queue for this driver
static sci_event_t
dx_get_event(struct _gfx_driver *drv)
{
	assert(drv->state != NULL);
	return get_queue_event(dx_state);
}


// Sleeps the specified amount of microseconds, or until the mouse moves
static int
dx_usleep(struct _gfx_driver *drv, long usecs)
{
	if (usecs < 1000)
	{
		sleep(0);
	}
	else
	{
		sleep(usecs/1000);
	}
	ProcessMessages(drv);

	return GFX_OK;
}


// Process any Windows messages
static int
ProcessMessages(struct _gfx_driver *drv)
{
	MSG msg;

	while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
		switch( msg.message )
		{
			case WM_PAINT:
				ValidateRect( dx_state->hWnd, NULL );
				RenderD3D(drv);
				break;

			case WM_KEYDOWN:
				switch (msg.wParam)
				{
					MAP_KEY (VK_ESCAPE,		SCI_K_ESC);
					MAP_KEY (VK_BACK,		SCI_K_BACKSPACE);
					MAP_KEY (VK_RETURN,		SCI_K_ENTER);
					MAP_KEY (VK_TAB,		SCI_K_TAB);
					MAP_KEY (VK_END,		SCI_K_END);
					MAP_KEY (VK_DOWN,		SCI_K_DOWN);
					MAP_KEY (VK_NEXT,		SCI_K_PGDOWN);
					MAP_KEY (VK_LEFT,		SCI_K_LEFT);
					MAP_KEY (VK_RIGHT,		SCI_K_RIGHT);
					MAP_KEY (VK_HOME,		SCI_K_HOME);
					MAP_KEY (VK_UP,			SCI_K_UP);
					MAP_KEY (VK_PRIOR,		SCI_K_PGUP);
					MAP_KEY (VK_INSERT,		SCI_K_INSERT);
					MAP_KEY (VK_DELETE,		SCI_K_DELETE);
					MAP_KEY (VK_DECIMAL,	SCI_K_DELETE);
					MAP_KEY (VK_ADD,		SCI_K_PLUS);
					MAP_KEY (VK_OEM_PLUS,	SCI_K_EQUALS);
					MAP_KEY (VK_SUBTRACT,	SCI_K_MINUS);
					MAP_KEY (VK_OEM_MINUS,	SCI_K_MINUS);
					MAP_KEY (VK_MULTIPLY,	SCI_K_MULTIPLY);
					MAP_KEY (VK_DIVIDE,		SCI_K_DIVIDE);
					MAP_KEY (VK_OEM_COMMA,	',');
					MAP_KEY (VK_OEM_PERIOD,	'.');
					MAP_KEY (VK_OEM_1,		';');	// US keyboards only
					MAP_KEY (VK_OEM_2,		'/');
					MAP_KEY (VK_OEM_3,		'`');
					MAP_KEY (VK_OEM_4,		'[');
					MAP_KEY (VK_OEM_5,		'\\');
					MAP_KEY (VK_OEM_6,		']');
					MAP_KEY (VK_OEM_7,		'\'');
					MAP_KEY (VK_F1,			SCI_K_F1);
					MAP_KEY (VK_F2,			SCI_K_F2);
					MAP_KEY (VK_F3,			SCI_K_F3);
					MAP_KEY (VK_F4,			SCI_K_F4);
					MAP_KEY (VK_F5,			SCI_K_F5);
					MAP_KEY (VK_F6,			SCI_K_F6);
					MAP_KEY (VK_F7,			SCI_K_F7);
					MAP_KEY (VK_F8,			SCI_K_F8);
					MAP_KEY (VK_F9,			SCI_K_F9);
					MAP_KEY (VK_F10,		SCI_K_F10);

					case VK_RSHIFT:
					case VK_LSHIFT:
					case VK_CONTROL:
					case VK_MENU:
					case VK_SCROLL:
					case VK_NUMLOCK:
					case VK_CAPITAL:
						break;	// ignore

				default:
					if (msg.wParam >= 'A' && msg.wParam <= 'Z')
						add_key_event (dx_state, msg.wParam - 'A' + 97);
					else if (msg.wParam >= VK_NUMPAD0 && msg.wParam <= VK_NUMPAD9)
					{
						if (GetKeyState (VK_NUMLOCK) & 1)
							add_key_event (dx_state, msg.wParam - VK_NUMPAD0 + '0');
						else
						switch (msg.wParam)
						{
							MAP_KEY (VK_NUMPAD0, SCI_K_INSERT);
							MAP_KEY (VK_NUMPAD1, SCI_K_END);
							MAP_KEY (VK_NUMPAD2, SCI_K_DOWN);
							MAP_KEY (VK_NUMPAD3, SCI_K_PGDOWN);
							MAP_KEY (VK_NUMPAD4, SCI_K_LEFT);
							MAP_KEY (VK_NUMPAD6, SCI_K_RIGHT);
							MAP_KEY (VK_NUMPAD7, SCI_K_HOME);
							MAP_KEY (VK_NUMPAD8, SCI_K_UP);
							MAP_KEY (VK_NUMPAD9, SCI_K_PGUP);
						}
					}
					else if (msg.wParam == 0xC0)  // tilde key - used for invoking console
						add_key_event (dx_state, '`');
					else
						add_key_event (dx_state, msg.wParam);
					break;
				}
				break;

			case WM_MOUSEMOVE:
				// Turn off mouse cursor
				ShowCursor(FALSE);
				show_pointer(drv, msg.lParam);
				break;

			case WM_MOUSELEAVE:
				// Turn on mouse cursor
				ShowCursor(TRUE);
				break;

			case WM_LBUTTONDOWN: add_mouse_event (dx_state, SCI_EVT_MOUSE_PRESS, 1, msg.wParam);   break;
			case WM_RBUTTONDOWN: add_mouse_event (dx_state, SCI_EVT_MOUSE_PRESS, 2, msg.wParam);   break;
			case WM_MBUTTONDOWN: add_mouse_event (dx_state, SCI_EVT_MOUSE_PRESS, 3, msg.wParam);   break;
			case WM_LBUTTONUP:   add_mouse_event (dx_state, SCI_EVT_MOUSE_RELEASE, 1, msg.wParam); break;
			case WM_RBUTTONUP:   add_mouse_event (dx_state, SCI_EVT_MOUSE_RELEASE, 2, msg.wParam); break;
			case WM_MBUTTONUP:   add_mouse_event (dx_state, SCI_EVT_MOUSE_RELEASE, 3, msg.wParam); break;

			case WM_DESTROY:
				PostQuitMessage( 0 );
				drv->exit(drv);
				exit(-1);
				break;

		}
	}

	return 0;
}


extern "C"
gfx_driver_t gfx_driver_dx = {
	"directx",
	"0.4.1",
	SCI_GFX_DRIVER_MAGIC,
	SCI_GFX_DRIVER_VERSION,
	NULL,	/* mode */
	0, 0,	/* mouse pointer position */
	GFX_CAPABILITY_MOUSE_SUPPORT | GFX_CAPABILITY_MOUSE_POINTER | GFX_CAPABILITY_COLOR_MOUSE_POINTER | GFX_CAPABILITY_PIXMAP_REGISTRY | GFX_CAPABILITY_PIXMAP_GRABBING | GFX_CAPABILITY_FINE_LINES | GFX_CAPABILITY_WINDOWED,
	0,
	dx_set_param,
	dx_init_specific,
	dx_init,
	dx_exit,
	dx_draw_line,
	dx_draw_filled_rect,
	dx_register_pixmap,
	dx_unregister_pixmap,
	dx_draw_pixmap,
	dx_grab_pixmap,
	dx_update,
	dx_set_static_buffer,
	dx_set_pointer,
	NULL,
	dx_get_event,
	dx_usleep,
	NULL
};

#endif
