/*	Legacy Direct3DViewport interface
 *
 * Copyright 2001 TransGaming Technologies Inc.
 */

#include "config.h"

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

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

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

static ICOM_VTABLE(IDirect3DViewport3) IDirect3DViewport3_VTable;

/* Non-interface functions */
HRESULT
Direct3DViewport3_Construct(IDirect3DViewportImpl* This,
			    IDirectDrawImpl* pDD)
{
    This->ref = 1;
    This->d3d_owner = pDD;
    This->bd = NULL;

    This->v.dvMinZ = 0.0;
    This->v.dvMaxZ = 1.0;

    This->clip_matrix._11 = 1.0;
    This->clip_matrix._22 = 1.0;
    This->clip_matrix._33 = 1.0;
    This->clip_matrix._44 = 1.0;

    ICOM_INIT_INTERFACE(This, IDirect3DViewport3,
			IDirect3DViewport3_VTable);

    /* Direct3D_AddViewport(This->d3d_owner, This); */

    return D3D_OK;
}

HRESULT
Direct3DViewport3_Create(IDirectDrawImpl *pDD,
			 LPDIRECT3DVIEWPORT3 *ppd3dv)
{
    IDirect3DViewportImpl* This;
    HRESULT hr;

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

    hr = Direct3DViewport3_Construct(This, pDD);
    if (FAILED(hr))
	HeapFree(GetProcessHeap(), 0, This);
    else
	*ppd3dv = ICOM_INTERFACE(This, IDirect3DViewport3);

    return hr;
}

static void Direct3DViewport3_Destroy(IDirect3DViewportImpl* This)
{
    /* Direct3D_RemoveViewport(This->d3d_owner, This); */
    HeapFree(GetProcessHeap(), 0, This);
}

void Direct3DViewport3_ForceDestroy(IDirect3DViewportImpl* This)
{
    WARN("destroying 3D material %p with refcnt %lu\n", This, This->ref);
    Direct3DViewport3_Destroy(This);
}

/* public non-interface functions */

void Direct3DViewport_Unbind(IDirect3DViewportImpl* This,
			     IDirect3DDeviceImpl* device)
{
    LPDIRECT3DDEVICE7 dev = ICOM_INTERFACE(device, IDirect3DDevice7);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 0, {1} };
    D3DHAL_DP2SETTRANSFORM xfrm;

    IDirect3DLightImpl* light;

    This->bound = FALSE;

    /* unload clip matrix */
    xfrm.xfrmType = 0;
    memset(&xfrm.matrix, 0, sizeof(xfrm.matrix));
    device->d3dp.xfrm[0] = xfrm.matrix;
    device->emit_dp2(device->ip_ctx, &cmd, &xfrm, sizeof(xfrm), NULL, 0);

    for (light = This->lights; light != NULL; light = light->next_view)
    {
	IDirect3DDevice7_LightEnable(dev, light->view_index, FALSE);
    }
}

void Direct3DViewport_Bind(IDirect3DViewportImpl* This,
			   IDirect3DDeviceImpl* device)
{
    LPDIRECT3DDEVICE7 dev = ICOM_INTERFACE(device, IDirect3DDevice7);
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 1, {1} };
    D3DHAL_DP2SETTRANSFORM xfrm;

    IDirect3DLightImpl* light;

    This->bound = TRUE;

    IDirect3DDevice7_SetViewport(dev, &This->v);

    /* load clip matrix */
    xfrm.xfrmType = 0;
    xfrm.matrix = This->clip_matrix;
    device->d3dp.xfrm[0] = xfrm.matrix;
    device->emit_dp2(device->ip_ctx, &cmd, &xfrm, sizeof(xfrm), NULL, 0);

    for (light = This->lights; light != NULL; light = light->next_view)
    {
	IDirect3DDevice7_SetLight(dev, light->view_index, &light->l);
	IDirect3DDevice7_LightEnable(dev, light->view_index, TRUE);
    }
}

/* Interface functions */
ULONG WINAPI Direct3DViewport3_Release(LPDIRECT3DVIEWPORT3 iface)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);

    if (--This->ref == 0)
    {
	Direct3DViewport3_Destroy(This);
	return 0;
    }

    return This->ref;
}

ULONG WINAPI Direct3DViewport3_AddRef(LPDIRECT3DVIEWPORT3 iface)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);

    return ++This->ref;
}

HRESULT WINAPI
Direct3DViewport3_QueryInterface(LPDIRECT3DVIEWPORT3 iface, REFIID riid,
				LPVOID* ppObj)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%s,%p)\n", This, debugstr_guid(riid), ppObj);

    if (IsEqualGUID(&IID_IUnknown, riid)
	|| IsEqualGUID(&IID_IDirect3DViewport3, riid)
	|| IsEqualGUID(&IID_IDirect3DViewport2, riid)
	|| IsEqualGUID(&IID_IDirect3DViewport, riid))
    {
	This->ref++;
	*ppObj = ICOM_INTERFACE(This, IDirect3DViewport3);
	return S_OK;
    }
    else
	return E_NOINTERFACE;
}

HRESULT WINAPI
Direct3DViewport3_Initialize(LPDIRECT3DVIEWPORT3 iface, LPDIRECT3D pd3d)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, pd3d);
    return DDERR_ALREADYINITIALIZED;
}

HRESULT WINAPI
Direct3DViewport3_GetViewport(LPDIRECT3DVIEWPORT3 iface, LPD3DVIEWPORT lpView)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpView);
    lpView->dwX      = This->v.dwX;
    lpView->dwY      = This->v.dwY;
    lpView->dwWidth  = This->v.dwWidth;
    lpView->dwHeight = This->v.dwHeight;
    lpView->dvScaleX = This->v.dwWidth/2.0; /* ? */
    lpView->dvScaleY = This->v.dwHeight/2.0; /* ? */
    lpView->dvMaxX   = 1.0; /* ? */
    lpView->dvMaxY   = 1.0; /* ? */
    lpView->dvMinZ   = This->v.dvMinZ;
    lpView->dvMaxZ   = This->v.dvMaxZ;
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_SetViewport(LPDIRECT3DVIEWPORT3 iface, LPD3DVIEWPORT lpView)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpView);
    This->v.dwX      = lpView->dwX;
    This->v.dwY      = lpView->dwY;
    This->v.dwWidth  = lpView->dwWidth;
    This->v.dwHeight = lpView->dwHeight;
    /* FIXME: dvScaleX/Y? */
    /* dvMinZ/dvMaxZ has different semantics in DX7,
     * so don't change This->v.dvMinZ/dvMaxZ here */
    TRACE(" z min=%f, max=%f\n", lpView->dvMinZ, lpView->dvMaxZ);
    TRACE(" scale x=%f, y=%f\n", lpView->dvScaleX, lpView->dvScaleY);
    TRACE(" max x=%f, y=%f\n", lpView->dvMaxX, lpView->dvMaxY);

    if (This->dev_owner && This->dev_owner->view == This)
	IDirect3DDevice7_SetViewport(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				     &This->v);

    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_TransformVertices(LPDIRECT3DVIEWPORT3 iface,
				    DWORD dwVertexCount,
				    LPD3DTRANSFORMDATA lpData,
				    DWORD dwFlags,
				    LPDWORD lpOffScreen)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    D3DDRAWPRIMITIVESTRIDEDDATA src, dst;
    D3DCLIPSTATUS clip;
    DWORD vop;
    HRESULT hr;

    TRACE("(%p)->(%ld,%p,%lx,%p)\n", This, dwVertexCount, lpData, dwFlags, lpOffScreen);
    TRACE(" clip=%03lx\n", lpData->dwClip);
    TRACE(" in-extents: (%ld,%ld)-(%ld,%ld)\n",
	  lpData->drExtent.u1.x1, lpData->drExtent.u2.y1,
	  lpData->drExtent.u3.x2, lpData->drExtent.u4.y2);
    src.position.lpvData = lpData->lpIn;
    src.position.dwStride = lpData->dwInSize;
    dst.position.lpvData = lpData->lpOut;
    dst.position.dwStride = lpData->dwOutSize;
    clip.dwFlags = D3DCLIPSTATUS_STATUS | D3DCLIPSTATUS_EXTENTS2;
    clip.dwStatus = 0; /* FIXME */
    clip.minx = lpData->drExtent.u1.x1;
    clip.maxx = lpData->drExtent.u3.x2;
    clip.miny = lpData->drExtent.u2.y1;
    clip.maxy = lpData->drExtent.u4.y2;
    clip.minz = 0;
    clip.maxz = 0;
    vop = D3DVOP_TRANSFORM;
    if (dwFlags & D3DTRANSFORM_CLIPPED) vop |= D3DVOP_CLIP;
    hr = ProcessStridedVertices(&dst, D3DFVF_XYZRHW, vop, 0, dwVertexCount,
				&src, D3DFVF_XYZ, 0,
				ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				D3DPV_DONOTCOPYDATA, lpData->lpHOut, &clip,
				&This->clip_matrix);
    lpData->drExtent.u1.x1 = floorf(clip.minx);
    lpData->drExtent.u3.x2 = ceilf(clip.maxx);
    lpData->drExtent.u2.y1 = floorf(clip.miny);
    lpData->drExtent.u4.y2 = ceilf(clip.maxy);
    lpData->dwClipIntersection = (clip.dwStatus & D3DSTATUS_CLIPINTERSECTIONALL) >> 24;
    lpData->dwClipUnion = (clip.dwStatus & D3DSTATUS_CLIPUNIONALL);
    *lpOffScreen = (clip.dwStatus & D3DSTATUS_CLIPINTERSECTIONALL) >> 24;
    TRACE(" clip intersection=%03lx union=%03lx\n", lpData->dwClipIntersection, lpData->dwClipUnion);
    TRACE(" out-extents: (%ld,%ld)-(%ld,%ld)\n",
	  lpData->drExtent.u1.x1, lpData->drExtent.u2.y1,
	  lpData->drExtent.u3.x2, lpData->drExtent.u4.y2);
    return hr;
}

HRESULT WINAPI
Direct3DViewport3_LightElements(LPDIRECT3DVIEWPORT3 iface,
				DWORD dwElementCount,
				LPD3DLIGHTDATA lpData)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    /* this function was never implemented by MS */
    ERR("(%p)->(%ld,%p): unimplemented\n", This, dwElementCount, lpData);
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_SetBackground(LPDIRECT3DVIEWPORT3 iface,
				D3DMATERIALHANDLE hMat)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, (IDirect3DMaterialImpl*)hMat);
    This->bm = (IDirect3DMaterialImpl*)hMat;
    return DD_OK;
}


HRESULT WINAPI
Direct3DViewport3_GetBackground(LPDIRECT3DVIEWPORT3 iface,
				LPD3DMATERIALHANDLE lphMat, LPBOOL lpValid)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p,%p)\n", This, lphMat, lpValid);

    *lphMat = (D3DMATERIALHANDLE)This->bm;
    *lpValid = FALSE; /* ? */
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_SetBackgroundDepth(LPDIRECT3DVIEWPORT3 iface,
				LPDIRECTDRAWSURFACE lpDDSurface)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, (IDirectDrawSurfaceImpl*)lpDDSurface);
    This->bd = (IDirectDrawSurfaceImpl*)lpDDSurface;
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_Clear(LPDIRECT3DVIEWPORT3 iface, DWORD dwCount, LPD3DRECT lpRects,
			DWORD dwFlags)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    DWORD dwColor = 0;
    TRACE("(%p)->(%ld,%p,%08lx)\n", This, dwCount, lpRects, dwFlags);
    if ((dwFlags & D3DCLEAR_TARGET) && This->bm) {
	dwColor = D3DRGBA(This->bm->m.u.diffuse.u1.r,
			  This->bm->m.u.diffuse.u2.g,
			  This->bm->m.u.diffuse.u3.b,
			  This->bm->m.u.diffuse.u4.a);
    }
    if (This->dev_owner && This->dev_owner->view == This)
    {
		IDirect3DDevice7_Clear(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
			       dwCount,lpRects,dwFlags,dwColor,This->v.dvMaxZ,0);

		/* Blit the background depth if it exists */
		if (This->bd && This->dev_owner->zbuffer && (dwFlags & D3DCLEAR_ZBUFFER ))
		{
			IDirectDrawSurface3_Blt(ICOM_INTERFACE(This->dev_owner->zbuffer, IDirectDrawSurface3),
									NULL,
									ICOM_INTERFACE(This->bd, IDirectDrawSurface3),
									NULL, 0, 0);
		}
	}

    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_AddLight(LPDIRECT3DVIEWPORT3 iface, LPDIRECT3DLIGHT lpLight)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    IDirect3DLightImpl* light = ICOM_OBJECT(IDirect3DLightImpl,
					    IDirect3DLight,
					    lpLight);
    TRACE("(%p)->(%p)\n", This, lpLight);
    if (light->view_owner != NULL) return D3DERR_LIGHTHASVIEWPORT;
    /* allocate light - is the previous allocation still free? */
    if (This->lmask & (1 << light->view_index)) {
	/* it's taken, allocate another
	 * (hmm, we could use a better algorithm than this)
	 */
	unsigned u;
	for (u=0; u<32 && (This->lmask & (1 << u)); u++);
	if (u >= 32) {
	    ERR("too many allocated lights, can't attach!\n");
	    return D3D_OK;
	}
        light->view_index = u;
    }
    This->lmask |= 1 << light->view_index;

    /* attach to linked list */
    light->prev_view = NULL;
    if (This->lights)
      This->lights->prev_view = light;
    light->next_view = This->lights;
    This->lights = light;

    light->view_owner = This;
    if (This->dev_owner && This->dev_owner->view == This) {
	IDirect3DDevice7_SetLight(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				  light->view_index, &light->l);
	IDirect3DDevice7_LightEnable(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				     light->view_index, TRUE);
    }
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_DeleteLight(LPDIRECT3DVIEWPORT3 iface, LPDIRECT3DLIGHT lpLight)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    IDirect3DLightImpl* light = ICOM_OBJECT(IDirect3DLightImpl,
					    IDirect3DLight,
					    lpLight);
    TRACE("(%p)->(%p)\n", This, lpLight);
    if (light->view_owner != This) return D3DERR_LIGHTNOTINTHISVIEWPORT;
    if (This->dev_owner && This->dev_owner->view == This) {
	IDirect3DDevice7_LightEnable(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				     light->view_index, FALSE);
    }
    This->lmask &= ~(1 << light->view_index);

    /* detach from linked list */
    if (light->prev_view)
      light->prev_view->next_view = light->next_view;
    else
      This->lights = light->next_view;
    if (light->next_view)
      light->next_view->prev_view = light->prev_view;

    light->view_owner = NULL;
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_NextLight(LPDIRECT3DVIEWPORT3 iface, LPDIRECT3DLIGHT lpDirect3DLight, LPDIRECT3DLIGHT *lplpDirect3DLight, DWORD dwFlags)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    IDirect3DLightImpl* light = ICOM_OBJECT(IDirect3DLightImpl,
					    IDirect3DLight,
					    lpDirect3DLight);
    TRACE("(%p)->(%p,%p,%08lx)\n", This, lpDirect3DLight, lplpDirect3DLight, dwFlags);
    if (light->view_owner != This) return D3DERR_LIGHTNOTINTHISVIEWPORT;

    *lplpDirect3DLight = ICOM_INTERFACE(light->next_view, IDirect3DLight);
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_GetViewport2(LPDIRECT3DVIEWPORT3 iface, LPD3DVIEWPORT2 lpView)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpView);
    lpView->dwX          = This->v.dwX;
    lpView->dwY          = This->v.dwY;
    lpView->dwWidth      = This->v.dwWidth;
    lpView->dwHeight     = This->v.dwHeight;
    lpView->dvClipWidth  = 2.0/This->clip_matrix._11;
    lpView->dvClipHeight = 2.0/This->clip_matrix._22;
    lpView->dvClipX      = -(This->clip_matrix._41 + 1.0)*lpView->dvClipWidth/2.0;
    lpView->dvClipY      = -(This->clip_matrix._42 - 1.0)*lpView->dvClipHeight/2.0;
    lpView->dvMinZ       = This->v.dvMinZ;
    lpView->dvMaxZ       = This->v.dvMaxZ;
    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_SetViewport2(LPDIRECT3DVIEWPORT3 iface, LPD3DVIEWPORT2 lpView)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%p)\n", This, lpView);
    This->v.dwX      = lpView->dwX;
    This->v.dwY      = lpView->dwY;
    This->v.dwWidth  = lpView->dwWidth;
    This->v.dwHeight = lpView->dwHeight;
    This->v.dvMinZ   = lpView->dvMinZ;
    This->v.dvMaxZ   = lpView->dvMaxZ;
    TRACE("minz=%f, maxz=%f\n", lpView->dvMinZ, lpView->dvMaxZ);
    TRACE("clip volume: x=%f,y=%f,w=%f,h=%f\n",
	  lpView->dvClipX, lpView->dvClipY,
	  lpView->dvClipWidth, lpView->dvClipHeight);

#if 0
    /* Hack. Unfortunately this is visible in GetViewport.
     * I'll have to check that against windows. */
    if (This->v.dvMinZ == 0 && This->v.dvMaxZ == 0)
	This->v.dvMaxZ = 1;
#endif

    This->clip_matrix._11 = 2.0/lpView->dvClipWidth;
    This->clip_matrix._22 = 2.0/lpView->dvClipHeight;
    This->clip_matrix._41 = -1.0 - 2.0*lpView->dvClipX/lpView->dvClipWidth;
    This->clip_matrix._42 = 1.0 - 2.0*lpView->dvClipY/lpView->dvClipHeight;
    This->clip_matrix._33 = 1.0/(This->v.dvMaxZ - This->v.dvMinZ);
    This->clip_matrix._43 = -This->v.dvMinZ/(This->v.dvMaxZ - This->v.dvMinZ);

    if (This->dev_owner && This->dev_owner->view == This) {
	D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 1, {1} };
	D3DHAL_DP2SETTRANSFORM xfrm;

	IDirect3DDevice7_SetViewport(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
				     &This->v);
	/* load clip matrix */
	xfrm.xfrmType = 0;
	xfrm.matrix = This->clip_matrix;
	This->dev_owner->d3dp.xfrm[0] = xfrm.matrix;
	This->dev_owner->emit_dp2(This->dev_owner->ip_ctx, &cmd, &xfrm, sizeof(xfrm), NULL, 0);
    }

    return DD_OK;
}

HRESULT WINAPI
Direct3DViewport3_Clear2(LPDIRECT3DVIEWPORT3 iface, DWORD dwCount, LPD3DRECT lpRects,
			 DWORD dwFlags, DWORD dwColor, D3DVALUE dvZ, DWORD dwStencil)
{
    ICOM_THIS(IDirect3DViewportImpl, iface);
    TRACE("(%p)->(%ld,%p,%08lx)\n", This, dwCount, lpRects, dwFlags);
    if (This->dev_owner && This->dev_owner->view == This)
	IDirect3DDevice7_Clear(ICOM_INTERFACE(This->dev_owner, IDirect3DDevice7),
			       dwCount,lpRects,dwFlags,dwColor,dvZ,dwStencil);
    return DD_OK;
}

static ICOM_VTABLE(IDirect3DViewport3) IDirect3DViewport3_VTable =
{
    Direct3DViewport3_QueryInterface,
    Direct3DViewport3_AddRef,
    Direct3DViewport3_Release,
    Direct3DViewport3_Initialize,
    Direct3DViewport3_GetViewport,
    Direct3DViewport3_SetViewport,
    Direct3DViewport3_TransformVertices,
    Direct3DViewport3_LightElements,
    Direct3DViewport3_SetBackground,
    Direct3DViewport3_GetBackground,
    Direct3DViewport3_SetBackgroundDepth,
    (void*)0xdead3008, /* GetBackgroundDepth */
    Direct3DViewport3_Clear,
    Direct3DViewport3_AddLight,
    Direct3DViewport3_DeleteLight,
    Direct3DViewport3_NextLight,
    Direct3DViewport3_GetViewport2,
    Direct3DViewport3_SetViewport2,
    (void*)0xdead300F, /* SetBackgroundDepth2 */
    (void*)0xdead3010, /* GetBackgroundDepth2 */
    Direct3DViewport3_Clear2
};
