/* Direct3D object implementations
   Copyright 2000-2001 TransGaming Technologies Inc.
 */

#include "config.h"

#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

#include "d3d.h"
#include "ddraw.h"
#include "winerror.h"

#include "ddraw_private.h"
#include "mesa_private.h"
#include "d3dvbuffer/main.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

/*******************************************************************************
 *				IDirect3D
 */
static HRESULT WINAPI MESA_IDirect3DImpl_QueryInterface(
    LPDIRECT3D iface,REFIID refiid,LPVOID *obj
) {
    ICOM_THIS(IDirect3DImpl,iface);
    /* FIXME: Not sure if this is correct */

    TRACE("(%p)->(%s,%p)\n",This,debugstr_guid(refiid),obj);
    if (( IsEqualGUID( &IID_IDirectDraw,  refiid ) ) ||
	( IsEqualGUID (&IID_IDirectDraw2, refiid ) ) ||
	( IsEqualGUID( &IID_IDirectDraw4, refiid ) )
    ) {
	*obj = This->ddraw;
	IDirect3D_AddRef(iface);
	TRACE("  Creating IDirectDrawX interface (%p)\n", *obj);
	return S_OK;
    }
    if (( IsEqualGUID( &IID_IDirect3D, refiid ) ) ||
	( IsEqualGUID( &IID_IUnknown,  refiid ) ) ) {
	*obj = This;
	IDirect3D_AddRef(iface);
	TRACE("  Creating IDirect3D interface (%p)\n", *obj);
	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D2, refiid ) ) {
	IDirect3D2Impl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D_AddRef(iface);
	ICOM_VTBL(d3d) = &mesa_d3d2vt;
	*obj = d3d;
	TRACE("  Creating IDirect3D2 interface (%p)\n", *obj);
	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D3, refiid ) ) {
	IDirect3D3Impl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D_AddRef(iface);
	IDirectDraw_AddRef((LPDIRECTDRAW)d3d->ddraw);
	ICOM_VTBL(d3d) = &mesa_d3d3vt;
	*obj = d3d;
	TRACE("  Creating IDirect3D3 interface (%p)\n", *obj);
	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D7, refiid ) ) {
	IDirect3D7Impl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D_AddRef(iface);
	IDirectDraw_AddRef((LPDIRECTDRAW)d3d->ddraw);
	ICOM_VTBL(d3d) = &mesa_d3d7vt;
	*obj = d3d;
	TRACE("  Creating IDirect3D7 interface (%p)\n", *obj);
	return S_OK;
    }
    FIXME("(%p):interface for IID %s NOT found!\n",This,debugstr_guid(refiid));
    return OLE_E_ENUM_NOMORE;
}

static ULONG WINAPI MESA_IDirect3DImpl_Release(LPDIRECT3D iface)
{
    ICOM_THIS(IDirect3DImpl,iface);
    TRACE("(%p)->() decrementing from %lu.\n", This, This->ref );

    if (!--(This->ref)) {
	IDirectDraw2_Release((IDirectDraw2*)This->ddraw);
	HeapFree(GetProcessHeap(),0,This);
	return S_OK;
    }
    return This->ref;
}

static HRESULT WINAPI MESA_IDirect3DImpl_EnumDevices(
    LPDIRECT3D iface, LPD3DENUMDEVICESCALLBACK cb, LPVOID context
) {
    ICOM_THIS(IDirect3DImpl,iface);
    FIXME("(%p)->(%p,%p),stub!\n",This,cb,context);

    /* Call functions defined in d3ddevices.c */
    if (!d3denum_OpenGL(cb, context))
	return D3D_OK;

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DImpl_CreateLight(
    LPDIRECT3D iface, LPDIRECT3DLIGHT *lplight, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3DImpl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lplight, lpunk);

    /* Call the creation function that is located in d3dlight.c */
    *lplight = d3dlight_create_dx3(This);

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DImpl_CreateMaterial(
    LPDIRECT3D iface, LPDIRECT3DMATERIAL *lpmaterial, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3DImpl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lpmaterial, lpunk);
    /* Call the creation function that is located in d3dviewport.c */
    *lpmaterial = d3dmaterial_create(This);
    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DImpl_CreateViewport(
    LPDIRECT3D iface, LPDIRECT3DVIEWPORT *lpviewport, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3DImpl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lpviewport, lpunk);

    /* Call the creation function that is located in d3dviewport.c */
    *lpviewport = d3dviewport_create(This);

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3DImpl_FindDevice(
    LPDIRECT3D iface, LPD3DFINDDEVICESEARCH lpfinddevsrc,
    LPD3DFINDDEVICERESULT lpfinddevrst)
{
    ICOM_THIS(IDirect3DImpl,iface);
    FIXME("(%p)->(%p,%p): stub\n", This, lpfinddevsrc, lpfinddevrst);

    return D3D_OK;
}

ICOM_VTABLE(IDirect3D) mesa_d3dvt =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    MESA_IDirect3DImpl_QueryInterface,
    IDirect3DImpl_AddRef,
    MESA_IDirect3DImpl_Release,
    IDirect3DImpl_Initialize,
    MESA_IDirect3DImpl_EnumDevices,
    MESA_IDirect3DImpl_CreateLight,
    MESA_IDirect3DImpl_CreateMaterial,
    MESA_IDirect3DImpl_CreateViewport,
    MESA_IDirect3DImpl_FindDevice
};

/*******************************************************************************
 *				IDirect3D2
 */
static HRESULT WINAPI MESA_IDirect3D2Impl_QueryInterface(
    LPDIRECT3D2 iface,REFIID refiid,LPVOID *obj) {
    ICOM_THIS(IDirect3D2Impl,iface);

    /* FIXME: Not sure if this is correct */

    TRACE("(%p)->(%s,%p)\n",This,debugstr_guid(refiid),obj);
    if ( ( IsEqualGUID( &IID_IDirectDraw,  refiid ) ) ||
	 ( IsEqualGUID( &IID_IDirectDraw2, refiid ) ) ||
	 ( IsEqualGUID( &IID_IDirectDraw4, refiid ) ) ) {
	*obj = This->ddraw;
	IDirect3D2_AddRef(iface);

	TRACE("  Creating IDirectDrawX interface (%p)\n", *obj);

	return S_OK;
    }
    if ( ( IsEqualGUID( &IID_IDirect3D2, refiid ) ) ||
	 ( IsEqualGUID( &IID_IUnknown,   refiid ) ) ) {
	*obj = This;
	IDirect3D2_AddRef(iface);

	TRACE("  Creating IDirect3D2 interface (%p)\n", *obj);

	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D, refiid ) ) {
	IDirect3DImpl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D2_AddRef(iface);
	ICOM_VTBL(d3d) = &mesa_d3dvt;
	*obj = d3d;
	TRACE("  Creating IDirect3D interface (%p)\n", *obj);
	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D3, refiid ) ) {
	IDirect3D3Impl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D2_AddRef(iface);
	IDirectDraw_AddRef((LPDIRECTDRAW)d3d->ddraw);
	ICOM_VTBL(d3d) = &mesa_d3d3vt;
	*obj = d3d;
	TRACE("  Creating IDirect3D3 interface (%p)\n", *obj);
	return S_OK;
    }
    if ( IsEqualGUID( &IID_IDirect3D7, refiid ) ) {
	IDirect3D7Impl*  d3d;

	d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
	d3d->ref = 1;
	d3d->ddraw = This->ddraw;
	IDirect3D2_AddRef(iface);
	IDirectDraw_AddRef((LPDIRECTDRAW)d3d->ddraw);
	ICOM_VTBL(d3d) = &mesa_d3d7vt;
	*obj = d3d;
	TRACE("  Creating IDirect3D7 interface (%p)\n", *obj);
	return S_OK;
    }
    FIXME("(%p):interface for IID %s NOT found!\n",This,debugstr_guid(refiid));
    return OLE_E_ENUM_NOMORE;
}

static ULONG WINAPI MESA_IDirect3D2Impl_Release(LPDIRECT3D2 iface) {
    ICOM_THIS(IDirect3D2Impl,iface);
    TRACE("(%p)->() decrementing from %lu.\n", This, This->ref );

    if (!--(This->ref)) {
	IDirectDraw2_Release((IDirectDraw2*)This->ddraw);
	HeapFree(GetProcessHeap(),0,This);
	return S_OK;
    }
    return This->ref;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_EnumDevices(
    LPDIRECT3D2 iface,LPD3DENUMDEVICESCALLBACK cb, LPVOID context
) {
    ICOM_THIS(IDirect3D2Impl,iface);
    FIXME("(%p)->(%p,%p),stub!\n",This,cb,context);

    /* Call functions defined in d3ddevices.c */
    if (!d3denum_OpenGL(cb, context))
	return D3D_OK;
    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_CreateLight(
    LPDIRECT3D2 iface, LPDIRECT3DLIGHT *lplight, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3D2Impl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lplight, lpunk);

    /* Call the creation function that is located in d3dlight.c */
    *lplight = d3dlight_create(This);

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_CreateMaterial(
    LPDIRECT3D2 iface, LPDIRECT3DMATERIAL2 *lpmaterial, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3D2Impl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lpmaterial, lpunk);

    /* Call the creation function that is located in d3dviewport.c */
    *lpmaterial = d3dmaterial2_create(This);

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_CreateViewport(
    LPDIRECT3D2 iface, LPDIRECT3DVIEWPORT2 *lpviewport, IUnknown *lpunk
) {
    ICOM_THIS(IDirect3D2Impl,iface);
    TRACE("(%p)->(%p,%p): stub\n", This, lpviewport, lpunk);

    /* Call the creation function that is located in d3dviewport.c */
    *lpviewport = d3dviewport2_create(This);

    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_FindDevice(
    LPDIRECT3D2 iface, LPD3DFINDDEVICESEARCH lpfinddevsrc,
    LPD3DFINDDEVICERESULT lpfinddevrst)
{
    ICOM_THIS(IDirect3D2Impl,iface);
    FIXME("(%p)->(%p,%p): stub\n", This, lpfinddevsrc, lpfinddevrst);
    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D2Impl_CreateDevice(
    LPDIRECT3D2 iface, REFCLSID rguid, LPDIRECTDRAWSURFACE surface,
    LPDIRECT3DDEVICE2 *device)
{
    ICOM_THIS(IDirect3D2Impl,iface);

    FIXME("(%p)->(%s,%p,%p): stub\n",This,debugstr_guid(rguid),surface,device);

    if (d3dinst_OpenGL(rguid, (IDirectDrawSurfaceImpl*)
                       COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
                                          IDirectDrawSurface3,
                                          IDirectDrawSurface7, surface),
                       (IDirect3DDeviceImpl**)device, (IDirect3DImpl*)This, &OpenGL_d3dd2vt)) {


	IDirect3D2_AddRef(iface);
	return D3D_OK;
    }

    return DDERR_INVALIDPARAMS;
}

static HRESULT WINAPI MESA_IDirect3D3Impl_CreateDevice(
    LPDIRECT3D2 iface, REFCLSID rguid, LPDIRECTDRAWSURFACE surface,
    LPDIRECT3DDEVICE3 *device, void *unknown)
{
    ICOM_THIS(IDirect3D3Impl,iface);

    FIXME("(%p)->(%s,%p,%p): stub\n",This,debugstr_guid(rguid),surface,device);

    if (d3dinst_OpenGL(rguid, (IDirectDrawSurfaceImpl*)
                       COM_INTERFACE_CAST(IDirectDrawSurfaceImpl,
                                          IDirectDrawSurface3,
                                          IDirectDrawSurface7, surface),
                       (IDirect3DDeviceImpl**)device, (IDirect3DImpl*)This, &OpenGL_d3dd2vt)) {
	IDirect3D3_AddRef(iface);
	return D3D_OK;
    }

    return DDERR_INVALIDPARAMS;
}

ICOM_VTABLE(IDirect3D2) mesa_d3d2vt =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    MESA_IDirect3D2Impl_QueryInterface,
    IDirect3D2Impl_AddRef,
    MESA_IDirect3D2Impl_Release,
    MESA_IDirect3D2Impl_EnumDevices,
    MESA_IDirect3D2Impl_CreateLight,
    MESA_IDirect3D2Impl_CreateMaterial,
    MESA_IDirect3D2Impl_CreateViewport,
    MESA_IDirect3D2Impl_FindDevice,
    MESA_IDirect3D2Impl_CreateDevice
};

static HRESULT WINAPI MESA_IDirect3D3Impl_CreateVertexBuffer(
    LPDIRECT3D3 iface,
    LPD3DVERTEXBUFFERDESC lpD3DVertBufDesc,
    LPDIRECT3DVERTEXBUFFER* lplpD3DVertBuf,
    DWORD dwFlags,
    LPUNKNOWN lpUnk)
{
    if (lpUnk != NULL) return CLASS_E_NOAGGREGATION;
    return
	Main_D3DVertexBuffer_Create((LPDIRECT3D7)iface, lpD3DVertBufDesc,
				    dwFlags,
				    (LPDIRECT3DVERTEXBUFFER7 *)lplpD3DVertBuf);
}

static HRESULT WINAPI MESA_IDirect3D3Impl_EnumZBufferFormats(
    LPDIRECT3D3 iface,
    REFCLSID riidDevice,
    LPD3DENUMPIXELFORMATSCALLBACK lpEnumCallback,
    LPVOID lpContext)
{
  DDPIXELFORMAT fmt;
  fmt.dwSize = sizeof(fmt);
  fmt.dwFlags = DDPF_ZBUFFER;
  fmt.dwFourCC = 0;
  fmt.u1.dwZBufferBitDepth = 16; /* FIXME? */
  fmt.u2.dwRBitMask = fmt.u3.dwGBitMask = fmt.u4.dwBBitMask =
                      fmt.u5.dwRGBZBitMask = 0xffff;

  FIXME(": semi-stub\n");

  if (lpEnumCallback(&fmt,lpContext) == D3DENUMRET_CANCEL)
      return D3D_OK;

  fmt.dwFlags = DDPF_ZBUFFER | DDPF_STENCILBUFFER;
  fmt.u2.dwStencilBitDepth = 8;

  lpEnumCallback(&fmt, lpContext);

  return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D3Impl_EvictManagedTextures(
    LPDIRECT3D3 iface)
{
  FIXME(":stub\n");
  return D3D_OK;
}


#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)	(typeof(mesa_d3d3vt.fun))
#else
# define XCAST(fun)	(void*)
#endif

ICOM_VTABLE(IDirect3D3) mesa_d3d3vt =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    XCAST(QueryInterface)MESA_IDirect3D2Impl_QueryInterface,
    XCAST(AddRef)IDirect3D2Impl_AddRef,
    XCAST(Release)MESA_IDirect3D2Impl_Release,
    XCAST(EnumDevices)MESA_IDirect3D2Impl_EnumDevices,
    XCAST(CreateLight)MESA_IDirect3D2Impl_CreateLight,
    XCAST(CreateMaterial)MESA_IDirect3D2Impl_CreateMaterial,
    XCAST(CreateViewport)MESA_IDirect3D2Impl_CreateViewport,
    XCAST(FindDevice)MESA_IDirect3D2Impl_FindDevice,
    XCAST(CreateDevice)MESA_IDirect3D3Impl_CreateDevice,
    XCAST(CreateVertexBuffer)MESA_IDirect3D3Impl_CreateVertexBuffer,
    XCAST(EnumZBufferFormats)MESA_IDirect3D3Impl_EnumZBufferFormats,
    XCAST(EvictManagedTextures)MESA_IDirect3D3Impl_EvictManagedTextures
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif

static HRESULT WINAPI MESA_IDirect3D7Impl_EnumDevices(
    LPDIRECT3D7 iface,LPD3DENUMDEVICESCALLBACK7 cb, LPVOID context
) {
    ICOM_THIS(IDirect3D7Impl,iface);
    FIXME("(%p)->(%p,%p),stub!\n",This,cb,context);

    /* Call functions defined in d3ddevices.c */
    if (!d3d7enum_OpenGL(cb, context))
	return D3D_OK;
    return D3D_OK;
}

static HRESULT WINAPI MESA_IDirect3D7Impl_CreateVertexBuffer(
    LPDIRECT3D7 iface,
    LPD3DVERTEXBUFFERDESC lpD3DVertBufDesc,
    LPDIRECT3DVERTEXBUFFER7* lplpD3DVertBuf,
    DWORD dwFlags)
{
    return Main_D3DVertexBuffer_Create(iface, lpD3DVertBufDesc, dwFlags,
				       lplpD3DVertBuf);
}

static HRESULT WINAPI MESA_IDirect3D7Impl_CreateDevice(
    LPDIRECT3D7 iface, REFCLSID rguid, LPDIRECTDRAWSURFACE7 surface,
    LPDIRECT3DDEVICE7 *device)
{
    ICOM_THIS(IDirect3D7Impl,iface);

    TRACE("(%p)->(%s,%p,%p): stub\n",This,debugstr_guid(rguid),surface,device);

    if (d3d7inst_OpenGL(rguid, (IDirectDrawSurfaceImpl*)surface, (IDirect3DDeviceImpl**)device, (IDirect3DImpl*)This, &OpenGL_d3dd7vt)) {
	IDirect3D2_AddRef(iface);
	return D3D_OK;
    }

    return DDERR_INVALIDPARAMS;
}

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
# define XCAST(fun)	(typeof(mesa_d3d7vt.fun))
#else
# define XCAST(fun)	(void*)
#endif


ICOM_VTABLE(IDirect3D7) mesa_d3d7vt =
{
    ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
    XCAST(QueryInterface)MESA_IDirect3D2Impl_QueryInterface,
    XCAST(AddRef)IDirect3D2Impl_AddRef,
    XCAST(Release)MESA_IDirect3D2Impl_Release,
    XCAST(EnumDevices)MESA_IDirect3D7Impl_EnumDevices,
    XCAST(CreateDevice)MESA_IDirect3D7Impl_CreateDevice,
    XCAST(CreateVertexBuffer)MESA_IDirect3D7Impl_CreateVertexBuffer,
    XCAST(EnumZBufferFormats)MESA_IDirect3D3Impl_EnumZBufferFormats,
    XCAST(EvictManagedTextures)MESA_IDirect3D3Impl_EvictManagedTextures
};

#if !defined(__STRICT_ANSI__) && defined(__GNUC__)
#undef XCAST
#endif

HRESULT MESA_create_direct3d(LPVOID *obj,IDirectDrawImpl* ddraw) {
    IDirect3DImpl* d3d;

    d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
    d3d->ref = 1;
    d3d->ddraw = (IDirectDrawImpl *) ddraw;
    d3d->private = NULL; /* unused for now */
    IDirectDraw_AddRef((LPDIRECTDRAW)ddraw);
    ICOM_VTBL(d3d) = &mesa_d3dvt;
    *obj = (LPUNKNOWN)d3d;
    TRACE("  Created IDirect3D interface (%p)\n", *obj);

    return S_OK;
}

HRESULT MESA_create_direct3d2(LPVOID *obj,IDirectDrawImpl* ddraw) {
    IDirect3D2Impl* d3d;

    d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
    d3d->ref = 1;
    d3d->ddraw = (IDirectDrawImpl *) ddraw;
    d3d->private = NULL; /* unused for now */
    IDirectDraw_AddRef((LPDIRECTDRAW)ddraw);
    ICOM_VTBL(d3d) = &mesa_d3d2vt;
    *obj = (LPUNKNOWN)d3d;
    TRACE("  Creating IDirect3D2 interface (%p)\n", *obj);

    return S_OK;
}

HRESULT MESA_create_direct3d3(LPVOID *obj,IDirectDrawImpl* ddraw) {
    IDirect3D3Impl* d3d;

    d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
    d3d->ref = 1;
    d3d->ddraw = (IDirectDrawImpl *) ddraw;
    d3d->private = NULL; /* unused for now */

    IDirectDraw_AddRef((LPDIRECTDRAW)ddraw);
    ICOM_VTBL(d3d) = &mesa_d3d3vt;
    *obj = (LPUNKNOWN)d3d;

    TRACE("  Creating IDirect3D3 interface (%p)\n", *obj);

    return S_OK;
}

HRESULT MESA_create_direct3d7(LPVOID *obj,IDirectDrawImpl* ddraw) {
    IDirect3D7Impl* d3d;

    d3d = HeapAlloc(GetProcessHeap(),0,sizeof(*d3d));
    d3d->ref = 1;
    d3d->ddraw = ddraw;
    d3d->private = NULL; /* unused for now */

    IDirectDraw_AddRef((LPDIRECTDRAW)ddraw);
    ICOM_VTBL(d3d) = &mesa_d3d7vt;
    *obj = (LPUNKNOWN)d3d;

    TRACE("  Creating IDirect3D7 interface (%p)\n", *obj);

    return S_OK;
}

