/*	Direct3DDevice interface
 *
 * Copyright 2001 TransGaming Technologies Inc.
 */

/* This file implements IDirect3DDevice, the first Direct3D interface
 * MS engineered, and the only interface that supports execute buffers.
 * Games written for IDirect3DDevice2/3 are allowed to QueryInterface
 * for IDirect3DDevice to use execute buffers, and many still do. */

#include "config.h"

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

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

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

#define CONVERT7(pd3dd) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice,	\
					   IDirect3DDevice7,	\
					   (pd3dd))
#define CONVERT3(pd3dd) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice,	\
					   IDirect3DDevice3,	\
					   (pd3dd))
#define CONVERT2(pd3dd) COM_INTERFACE_CAST(IDirect3DDeviceImpl,	\
					   IDirect3DDevice,	\
					   IDirect3DDevice2,	\
					   (pd3dd))

static HRESULT WINAPI
Direct3DDevice1_QueryInterface(LPDIRECT3DDEVICE iface, REFIID riid,
			       LPVOID *ppObj)
{
    return IDirect3DDevice7_QueryInterface(CONVERT7(iface), riid, ppObj);
}

static ULONG WINAPI
Direct3DDevice1_AddRef(LPDIRECT3DDEVICE iface)
{
    return IDirect3DDevice7_AddRef(CONVERT7(iface));
}

static ULONG WINAPI
Direct3DDevice1_Release(LPDIRECT3DDEVICE iface)
{
    return IDirect3DDevice7_Release(CONVERT7(iface));
}

static HRESULT WINAPI
Direct3DDevice1_Initialize(LPDIRECT3DDEVICE iface, LPDIRECT3D lpDirect3D,
			   LPGUID lpGUID, LPD3DDEVICEDESC lpD3DDVDesc)
{
    TRACE("(%p)->(%p,%p,%p)\n", iface, lpDirect3D, lpGUID, lpD3DDVDesc);
    return DDERR_ALREADYINITIALIZED;
}

static HRESULT WINAPI
Direct3DDevice1_GetCaps(LPDIRECT3DDEVICE iface,
			LPD3DDEVICEDESC lpHAL,
			LPD3DDEVICEDESC lpHEL)
{
    return IDirect3DDevice3_GetCaps(CONVERT3(iface), lpHAL, lpHEL);
}

static HRESULT WINAPI
Direct3DDevice1_CreateExecuteBuffer(LPDIRECT3DDEVICE iface,
				    LPD3DEXECUTEBUFFERDESC lpDesc,
				    LPDIRECT3DEXECUTEBUFFER* lplpExeBuffer,
				    IUnknown* pUnkOuter)
{
    ICOM_THIS_FROM(IDirect3DDeviceImpl, IDirect3DDevice, iface);
    LPDIRECTDRAWSURFACE7 surf = (LPDIRECTDRAWSURFACE7)NULL;
    DDSURFACEDESC2 desc;
    HRESULT ret;
    TRACE("(%p)->(%p,%p,%p)\n", iface, lpDesc, lplpExeBuffer, pUnkOuter);
    memset(&desc, 0, sizeof(DDSURFACEDESC2));
    desc.dwSize = sizeof(DDSURFACEDESC2);
    /* I assume D3DDEB_BUFSIZE is always set. */
    desc.dwFlags = DDSD_CAPS | DDSD_LINEARSIZE;
    desc.u1.dwLinearSize = lpDesc->dwBufferSize;
    desc.ddsCaps.dwCaps = DDSCAPS_EXECUTEBUFFER;
    if (lpDesc->dwFlags & D3DDEB_CAPS) {
	if (lpDesc->dwCaps & D3DDEBCAPS_SYSTEMMEMORY)
	    desc.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
	if (lpDesc->dwCaps & D3DDEBCAPS_VIDEOMEMORY)
	    desc.ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    }
    if (lpDesc->dwFlags & D3DDEB_LPDATA) {
	desc.dwFlags = DDSD_LPSURFACE;
	desc.lpSurface = lpDesc->lpData;
    }
    ret = This->ddraw_owner->create_exebuffer(This->ddraw_owner, &desc, &surf, NULL);
    if (surf)
	*lplpExeBuffer = COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
					    IDirectDrawSurface7,
					    IDirect3DExecuteBuffer,
					    surf);
    else
	*lplpExeBuffer = NULL;
    return ret;
}

static HRESULT WINAPI
Direct3DDevice1_Execute(LPDIRECT3DDEVICE iface,
			LPDIRECT3DEXECUTEBUFFER lpExeBuffer,
			LPDIRECT3DVIEWPORT lpView,
			DWORD dwFlags)
{
    IDirect3DDeviceImpl* dev = ICOM_OBJECT(IDirect3DDeviceImpl,
					   IDirect3DDevice,
					   iface);
    LPDIRECT3DDEVICE3 dev3 = ICOM_INTERFACE(dev, IDirect3DDevice3);
    LPDIRECT3D7 d3d7 = ICOM_INTERFACE(dev->ddraw_owner, IDirect3D7);
    LPDIRECT3DVERTEXBUFFER7 vbuf7;
    LPDIRECT3DVERTEXBUFFER vbuf;
    D3DVERTEXBUFFERDESC vbdesc;
    D3DEXECUTEDATA data;
    D3DEXECUTEBUFFERDESC desc;
    LPBYTE ins, verts;
    HRESULT hr;
    unsigned u;

    TRACE("(%p)->(%p,%p,%lx)\n", iface, lpExeBuffer, lpView, dwFlags);

    /* replace viewport (hmm, should we restore the old one, if any, afterwards?) */
    IDirect3DDevice3_SetCurrentViewport(dev3, (LPDIRECT3DVIEWPORT3)lpView);

    hr = IDirect3DExecuteBuffer_GetExecuteData(lpExeBuffer, &data);
    if (FAILED(hr)) return hr;

    /* perhaps we should use an internal API so we can specify DDLOCK_READONLY. */
    hr = IDirect3DExecuteBuffer_Lock(lpExeBuffer, &desc);
    if (FAILED(hr)) return hr;

    /* create a vertex buffer to hold transformed vertices */
    vbdesc.dwSize = sizeof(vbdesc);
    vbdesc.dwCaps = D3DVBCAPS_WRITEONLY;
    vbdesc.dwFVF = D3DFVF_TLVERTEX;
    vbdesc.dwNumVertices = data.dwVertexCount;
    if (dwFlags & D3DEXECUTE_CLIPPED) vbdesc.dwCaps |= D3DVBCAPS_DONOTCLIP;
    hr = IDirect3D7_CreateVertexBuffer(d3d7, &vbdesc, &vbuf7, 0);
    if (FAILED(hr)) goto err0;
    vbuf = COM_INTERFACE_CAST(IDirectDrawSurfaceImpl, IDirect3DVertexBuffer7, IDirect3DVertexBuffer, vbuf7);

    /* start parsing */
    ins = (LPBYTE)desc.lpData + data.dwInstructionOffset;
    verts = (LPBYTE)desc.lpData + data.dwVertexOffset;

    TRACE("execute start, at %p\n", ins);
    while (*ins != D3DOP_EXIT) {
	LPD3DINSTRUCTION cur = (LPD3DINSTRUCTION)ins;
	ins += sizeof(D3DINSTRUCTION);
	switch (cur->bOpcode) {
	case D3DOP_TRIANGLE:
	    TRACE(" TRIANGLE (%d)\n", cur->wCount);
	    for (u=0; u<cur->wCount; u++) {
		LPD3DTRIANGLE prim = (LPD3DTRIANGLE)ins;
		ins += sizeof(D3DTRIANGLE);
		TRACE("  vertices=(%d,%d,%d), flags=0x%x\n",
		      prim->u1.v1, prim->u2.v2, prim->u3.v3,
		      prim->wFlags);
		/* seems easiest to ignore the flags and always draw a triangle list. */
		IDirect3DDevice3_DrawIndexedPrimitiveVB(dev3, D3DPT_TRIANGLELIST, vbuf,
							(LPWORD)prim, 3, D3DDP_DONOTUPDATEEXTENTS |
							((dwFlags & D3DEXECUTE_CLIPPED) ? D3DDP_DONOTCLIP : 0));
	    }
	    break;
	case D3DOP_STATERENDER:
	    TRACE(" STATERENDER (%d)\n", cur->wCount);
	    for (u=0; u<cur->wCount; u++) {
		LPD3DSTATE stat = (LPD3DSTATE)ins;
		ins += sizeof(D3DSTATE);
		IDirect3DDevice3_SetRenderState(dev3,
						stat->u1.drstRenderStateType,
						stat->u2.dwArg[0]);
	    }
	    break;
	case D3DOP_PROCESSVERTICES:
	    TRACE(" PROCESSVERTICES (%d)\n", cur->wCount);
	    for (u=0; u<cur->wCount; u++) {
		LPD3DPROCESSVERTICES info = (LPD3DPROCESSVERTICES)ins;

		ins += sizeof(D3DPROCESSVERTICES);
		TRACE("  start=%d, dest=%d, count=%ld, flags=0x%lx\n",
		      info->wStart, info->wDest, info->dwCount, info->dwFlags);
		if (info->dwFlags & D3DPROCESSVERTICES_UPDATEEXTENTS) FIXME("UPDATEEXTENTS\n");
		if (info->dwFlags & D3DPROCESSVERTICES_NOCOLOR) FIXME("NOCOLOR\n");

		switch (info->dwFlags & D3DPROCESSVERTICES_OPMASK) {
		case D3DPROCESSVERTICES_COPY:
		    {
			LPD3DTLVERTEX data;
			IDirect3DVertexBuffer7_Lock(vbuf, DDLOCK_WRITEONLY, (LPVOID*)&data, NULL);
			memcpy(data + info->wDest, ((LPD3DTLVERTEX*)verts) + info->wStart,
			       sizeof(D3DTLVERTEX) * info->dwCount);
			IDirect3DVertexBuffer7_Unlock(vbuf);
		    }
		    break;
		case D3DPROCESSVERTICES_TRANSFORMLIGHT:
		case D3DPROCESSVERTICES_TRANSFORM:
		default:
		    FIXME("unhandled op mask %ld\n", info->dwFlags & D3DPROCESSVERTICES_OPMASK);
		    continue;
		}
	    }
	    break;
	default:
	    FIXME("unimplemented opcode %d\n", cur->bOpcode);
	}
    }
    TRACE("execute end\n");

    IDirect3DVertexBuffer7_Release(vbuf7);
err0:
    IDirect3DExecuteBuffer_Unlock(lpExeBuffer);

    return D3D_OK;
}

static HRESULT WINAPI
Direct3DDevice1_AddViewport(LPDIRECT3DDEVICE iface,
			    LPDIRECT3DVIEWPORT lpView)
{
    /* IDirect3DViewportImpl has only one vtable */
    return IDirect3DDevice3_AddViewport(CONVERT3(iface),
					(LPDIRECT3DVIEWPORT3)lpView);
}

static HRESULT WINAPI
Direct3DDevice1_EnumTextureFormats(LPDIRECT3DDEVICE iface,
				   LPD3DENUMTEXTUREFORMATSCALLBACK lpCB,
				   LPVOID lpContext)
{
    return IDirect3DDevice2_EnumTextureFormats(CONVERT2(iface), lpCB, lpContext);
}

static HRESULT WINAPI
Direct3DDevice1_BeginScene(LPDIRECT3DDEVICE iface)
{
    return IDirect3DDevice7_BeginScene(CONVERT7(iface));
}

static HRESULT WINAPI
Direct3DDevice1_EndScene(LPDIRECT3DDEVICE iface)
{
    return IDirect3DDevice7_EndScene(CONVERT7(iface));
}

ICOM_VTABLE(IDirect3DDevice) Main_IDirect3DDevice1_VTable =
{
    Direct3DDevice1_QueryInterface,
    Direct3DDevice1_AddRef,
    Direct3DDevice1_Release,
    Direct3DDevice1_Initialize,
    Direct3DDevice1_GetCaps,
    (void*)0xdead7001, /* SwapTextureHandles */
    Direct3DDevice1_CreateExecuteBuffer,
    (void*)0xdead7003, /* GetStats */
    Direct3DDevice1_Execute,
    Direct3DDevice1_AddViewport,
    (void*)0xdead7005, /* DeleteViewport */
    (void*)0xdead7006, /* NextViewport */
    (void*)0xdead7007, /* Pick */
    (void*)0xdead7008, /* GetPickRecords */
    Direct3DDevice1_EnumTextureFormats,
    (void*)0xdead7009, /* CreateMatrix */
    (void*)0xdead700A, /* SetMatrix */
    (void*)0xdead700B, /* GetMatrix */
    (void*)0xdead700C, /* DeleteMatrix */
    Direct3DDevice1_BeginScene,
    Direct3DDevice1_EndScene,
    (void*)0xdead700D /* GetDirect3D */
};
