/*	Direct3DDevice HAL driver
 *
 * Copyright 2001 TransGaming Technologies Inc.
 */

#include "config.h"

#include <assert.h>
#include <stdlib.h>
#include <float.h>

#include "wine/debug.h"
#include "ddrawi.h"
#include "d3dhal.h"
#include "d3d_private.h"
#include "d3ddevice/main.h"
#include "d3ddevice/hal.h"

/* Wireframe toggle hack - useful for debugging - uses Control key to switch
   back and forth between wireframe and non-wireframe modes */
#ifdef WIREFRAME
BOOL gWireframe = FALSE;
#endif

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

static HRESULT
HAL_Direct3DDevice_lazy_allocate_dibsections(IDirectDrawSurfaceImpl* pDDS);

extern HMODULE d3dgl;
typedef LPVOID WINAPI (*D3DConstructFunc)(LPD3D_P, DWORD);
typedef void WINAPI (*D3DDestructFunc)(LPVOID);

HRESULT
HAL_Direct3DDevice_Construct(IDirect3DDeviceImpl* This,
			     IDirectDrawImpl* pDD,
			     IDirectDrawSurfaceImpl* pDDS)
{
    LPDDRAWI_DIRECTDRAW_GBL dd_gbl = pDD->local.lpGbl;
    D3DHAL_CONTEXTCREATEDATA data;
    HRESULT hr;

    /* Ensure that the surface has a DIBsection if it needs one - ie, if we're
     * rendering into an offscreen surface via an XPixmap */
    hr = HAL_Direct3DDevice_lazy_allocate_dibsections(pDDS);
    if (FAILED(hr)) return hr;

    TRACE("(%p,%p,%p)\n",This,pDD,pDDS);
    hr = Main_Direct3DDevice_Construct(This, pDD, pDDS);
    if (FAILED(hr)) return hr;

    This->final_release = HAL_Direct3DDevice_final_release;

    This->late_allocate_surface = HAL_Direct3DDevice_late_allocate_surface;

    This->set_render_target = HAL_Direct3DDevice_set_render_target;

    This->begin_scene = HAL_Direct3DDevice_begin_scene;
    This->end_scene = HAL_Direct3DDevice_end_scene;

    This->d3dp.lcl = &pDD->local;
    This->d3dp.gdi = pDD->get_driver_info;
    This->d3dp.cbs1 = (LPD3DHAL_CALLBACKS)dd_gbl->lpD3DHALCallbacks;
    This->d3dp.cbs2 = (LPD3DHAL_CALLBACKS2)dd_gbl->lpD3DHALCallbacks2;

    This->d3dp.surface_heap = surface_heap;

    This->d3dp.surf = &This->surface->t.lcl;
    This->d3dp.zbuf = This->zbuffer ? &This->zbuffer->t.lcl : NULL;

    /* create HAL context */
    data.u1.lpDDLcl = &pDD->local;
    data.u2.lpDDSLcl = This->d3dp.surf;
    data.u3.lpDDSZLcl = This->d3dp.zbuf;
    data.u4.dwPID = pDD->local.dwProcessId;
    data.ddrval = 0;
    data.dwhContext = 0;
    This->d3dp.cbs1->ContextCreate(&data);
    hr = data.ddrval;
    if (FAILED(hr)) {
	Main_Direct3DDevice_final_release(This);
	return hr;
    }
    This->d3dp.dwhContext = data.dwhContext;

    This->ip_ctx = This;

#ifdef HAVE_OPENGL
    if (((LPD3DHAL_GLOBALDRIVERDATA)This->d3dp.lcl->lpGbl->lpD3DGlobalDriverData)
	->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) {
	/* This->ip_ctx = GL_Direct3DDevice_Construct(This); */
	D3DConstructFunc func;

	This->demolish_texture  = (LPVOID)GetProcAddress(d3dgl, "DemolishTexture");
	This->alloc_state_block = (LPVOID)GetProcAddress(d3dgl, "AllocStateBlock");
	This->free_state_block  = (LPVOID)GetProcAddress(d3dgl, "FreeStateBlock");
	This->start_dp2         = (LPVOID)GetProcAddress(d3dgl, "StartDP2");
	This->start_dp2_sd      = (LPVOID)GetProcAddress(d3dgl, "StartDP2SD");
	This->emit_dp2          = (LPVOID)GetProcAddress(d3dgl, "EmitDP2");
	This->flush_dp2         = (LPVOID)GetProcAddress(d3dgl, "FlushDP2");
	This->validate_device   = (LPVOID)GetProcAddress(d3dgl, "ValidateDevice");

	func = (LPVOID)GetProcAddress(d3dgl, "Construct");
	if (func) This->ip_ctx = func(&This->d3dp, (pDD->local.dwLocalFlags & DDRAWILCL_DIRECTDRAW7) ? 7 : 6);
    }
#endif

    Main_Direct3DDevice_post_init(This);

    return D3D_OK;
}

HRESULT
HAL_Direct3DDevice_Create(IDirectDrawImpl *pDD,
			  IDirectDrawSurfaceImpl *pDDS,
			  LPDIRECT3DDEVICE7 *ppd3dd)
{
    IDirect3DDeviceImpl* This;
    HRESULT hr;

    This = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
		     sizeof(*This) + sizeof(HAL_Direct3DDeviceImpl));
    if (This == NULL) return E_OUTOFMEMORY;

    This->private = (HAL_Direct3DDeviceImpl*)(This+1);

    hr = HAL_Direct3DDevice_Construct(This, pDD, pDDS);
    if (FAILED(hr))
	HeapFree(GetProcessHeap(), 0, This);
    else
	*ppd3dd = ICOM_INTERFACE(This, IDirect3DDevice7);

    return hr;
}

void HAL_Direct3DDevice_final_release(IDirect3DDeviceImpl* This)
{
    D3DHAL_CONTEXTDESTROYDATA data;
    IDirectDrawSurfaceImpl* pDDS;

    if (This->d3dp.in_scene) {
	ERR("releasing device while in scene\n");
	IDirect3DDevice7_EndScene(ICOM_INTERFACE(This, IDirect3DDevice7));
    }

    /* ensure we have a system-memory copy of all surfaces
     * before destroying the context. */
    pDDS = This->ddraw_owner->surfaces;
    while (pDDS)
    {
	/* this makes d3dgl load all surfaces into system memory. */
	pDDS->lock_update(pDDS, NULL, DDLOCK_READONLY);
	pDDS->unlock_update(pDDS, NULL);
	pDDS = pDDS->next_ddraw;
    }

#ifdef HAVE_OPENGL
    if (((LPD3DHAL_GLOBALDRIVERDATA)This->d3dp.lcl->lpGbl->lpD3DGlobalDriverData)
	->hwCaps.dwFlags & D3DDD_WINE_OPENGL_DEVICE) {
	/* GL_Direct3DDevice_Destruct(This->ip_ctx); */
	D3DDestructFunc func;
	func = (LPVOID)GetProcAddress(d3dgl, "Destruct");
	if (func) func(This->ip_ctx);
    }
#endif

    /* destroy HAL context */
    data.dwhContext = This->d3dp.dwhContext;
    data.ddrval = 0;
    This->d3dp.cbs1->ContextDestroy(&data);

    /* disconnect any late allocated surfaces */
    pDDS = This->ddraw_owner->surfaces;
    while (pDDS)
    {
	/* Set the context field to NULL, since this context is going
	 * to be destroyed */
	if (pDDS->t.lcl.dwReserved1 == This->d3dp.dwhContext)
	    pDDS->t.lcl.dwReserved1 = 0;
	pDDS = pDDS->next_ddraw;
    }

    Main_Direct3DDevice_final_release(This);
}

static HRESULT WINAPI
HAL_Direct3DDevice_lad_enum(LPDIRECTDRAWSURFACE7 pDDS,
			    LPDDSURFACEDESC2 pDDSD,
			    LPVOID pContext)
{
    HRESULT *phr = pContext;

    IDirectDrawSurfaceImpl* surf = ICOM_OBJECT(IDirectDrawSurfaceImpl,
					       IDirectDrawSurface7,
					       pDDS);

    *phr = HAL_Direct3DDevice_lazy_allocate_dibsections(surf);

    return DDENUMRET_OK;
}

static HRESULT
HAL_Direct3DDevice_lazy_allocate_dibsections(IDirectDrawSurfaceImpl* pDDS)
{
    HRESULT hr;

    LPDIRECTDRAWSURFACE7 pSurf = ICOM_INTERFACE(pDDS, IDirectDrawSurface7);

    hr = pDDS->lazy_alloc_dibsection(pDDS);
    if (FAILED(hr)) return hr;

    IDirectDrawSurface7_EnumAttachedSurfaces(pSurf, &hr,
					     HAL_Direct3DDevice_lad_enum);

    return hr;
}

HRESULT HAL_Direct3DDevice_late_allocate_surface(IDirect3DDeviceImpl* This,
						 IDirectDrawSurfaceImpl* pDDS)
{
    assert(pDDS->t.lcl.dwReserved1 == 0);

    /* save this context in surface (hoping only one context uses it) */
    pDDS->t.lcl.dwReserved1 = This->d3dp.dwhContext;

    return Main_Direct3DDevice_late_allocate_surface(This, pDDS);
}

HRESULT HAL_Direct3DDevice_set_render_target(IDirect3DDeviceImpl *This,
					     IDirectDrawSurfaceImpl *pDDS,
					     IDirectDrawSurfaceImpl *pDDSZ,
					     DWORD dwFlags)
{
    D3DHAL_SETRENDERTARGETDATA data;
    HRESULT hr;

    /* Ensure that the surface has a DIBsection if it needs one - ie, if we're
     * rendering into an offscreen surface via an XPixmap */
    hr = HAL_Direct3DDevice_lazy_allocate_dibsections(pDDS);
    if (FAILED(hr)) return hr;

    /* The surface may not have a zbuffer associated with it */
    if( pDDSZ )
    {
        hr = HAL_Direct3DDevice_lazy_allocate_dibsections(pDDSZ);
        if (FAILED(hr)) return hr;
    }

    This->d3dp.surf = &pDDS->t.lcl;
    This->d3dp.zbuf = pDDSZ ? &pDDSZ->t.lcl : NULL;

    data.dwhContext = This->d3dp.dwhContext;
    data.u1.lpDDSLcl = This->d3dp.surf;
    data.u2.lpDDSZLcl = This->d3dp.zbuf;
    data.ddrval = 0;
    This->d3dp.cbs2->SetRenderTarget(&data);
    This->setup_render_target(This);
    return data.ddrval;
}

HRESULT HAL_Direct3DDevice_begin_scene(IDirect3DDeviceImpl *This)
{
    D3DHAL_SCENECAPTUREDATA data;

    data.dwhContext = This->d3dp.dwhContext;
    data.dwFlag = D3DHAL_SCENE_CAPTURE_START;
    data.ddrval = 0;
    if (This->d3dp.cbs1->SceneCapture)
       This->d3dp.cbs1->SceneCapture(&data);
    return data.ddrval;
}

HRESULT HAL_Direct3DDevice_end_scene(IDirect3DDeviceImpl *This)
{
    D3DHAL_SCENECAPTUREDATA data;

    data.dwhContext = This->d3dp.dwhContext;
    data.dwFlag = D3DHAL_SCENE_CAPTURE_END;
    data.ddrval = 0;
    if (This->d3dp.cbs1->SceneCapture)
	This->d3dp.cbs1->SceneCapture(&data);
    return data.ddrval;
}


