/*
 * Direct3D9 vertex buffers
 * Copyright (c) 2003, TransGaming Technologies Inc.
 */

#include "config.h"

#include "winbase.h"
#include "d3d9.h"
#include "wine/debug.h"

#include "d3d9_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

#if defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM_FULL)
#  define USE_HAL(This) (TRUE)
#elif defined(WINE_D3D9_ENABLE_HAL_ALLOCATE_AGP_MEM)
#  define USE_HAL(This) ((This)->t.more.dwFVF)
#else
#  define USE_HAL(This) (FALSE)
#endif

HRESULT WINAPI Direct3DVertexBuffer9_QueryInterface(LPDIRECT3DVERTEXBUFFER9 iface,
						    REFIID riid,
						    LPVOID* obj)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);
  TRACE("(%p)->(%s,%p)\n", iface, debugstr_guid(riid), obj);
  if (IsEqualGUID(&IID_IUnknown, riid) ||
      IsEqualGUID(&IID_IDirect3DResource9, riid) ||
      IsEqualGUID(&IID_IDirect3DVertexBuffer9, riid)) {
    This->ref++;
    *obj = This;
    return S_OK;
  }
  else {
    FIXME("(%p)->(%s): no interface", iface, debugstr_guid(riid));
    return E_NOINTERFACE;
  }
}

ULONG WINAPI Direct3DVertexBuffer9_AddRef(LPDIRECT3DVERTEXBUFFER9 iface)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref - This->intref;
}

ULONG WINAPI Direct3DVertexBuffer9_Release(LPDIRECT3DVERTEXBUFFER9 iface)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);
  DDHAL_DESTROYSURFACEDATA data;
  UINT u;

  TRACE("(%p)->()\n", iface);
  if (--This->ref) return This->ref - This->intref;

  if (USE_HAL(This)) {
    /* destroy HAL surface */
    data.lpDD = This->parent->local.lpGbl;
    data.lpDDSurface = &This->t.lcl;
    data.ddRVal = 0;
    data.DestroySurface = This->parent->local.lpGbl->lpDDCBtmp->HALDDExeBuf.DestroyExecuteBuffer;
    data.DestroySurface(&data);
  }

  /* clear any remaining references to us */
  for (u=0; u<16; u++)
    if (This->parent->state.streamsource[u] == iface) {
      This->parent->state.streamsource[u] = NULL;
      This->parent->state.streamstride[u] = 0;
      This->parent->d3dp.stream[u].data_t = NULL;
      This->parent->d3dp.stream[u].stride = 0;
    }

  if (This->ptr) HeapFree(buffer_heap, 0, This->ptr);
  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

HRESULT WINAPI Direct3DVertexBuffer9_Lock(LPDIRECT3DVERTEXBUFFER9 iface,
					  UINT OffsetToLock,
					  UINT SizeToLock,
					  void** ppbData,
					  DWORD Flags)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);
  LPBYTE ptr = (LPBYTE)This->t.gbl.fpVidMem;

  TRACE("(%p)->(%d,%d,%p,0x%08lx)\n", iface,
	OffsetToLock, SizeToLock, ppbData, Flags);

  *ppbData = NULL;
  if (USE_HAL(This)) {
    DDHAL_LOCKDATA data;
    HRESULT hr;

    data.lpDD = This->parent->local.lpGbl;
    data.lpDDSurface = &This->t.lcl;
    data.bHasRect = FALSE;
    data.lpSurfData = NULL;

    /* FIXME: This is not perfect. There should be error checking on some combinations.*/
    data.dwFlags = Flags; /* D3DLOCK values are the same as their DDLOCK counterparts */

    data.ddRVal = DD_OK;
    data.Lock = This->parent->local.lpGbl->lpDDCBtmp->HALDDExeBuf.LockExecuteBuffer;
    hr = data.Lock(&data);
    if (hr == DDHAL_DRIVER_HANDLED) {
      if (FAILED(data.ddRVal)) {
        ERR("failed to lock vertex buffer\n");
        return data.ddRVal;
      }
      ptr = data.lpSurfData;
    }
  }

  *ppbData = ptr + OffsetToLock;

  TRACE("returning: bits %p\n", *ppbData);

  return D3D_OK;
}

HRESULT WINAPI Direct3DVertexBuffer9_Unlock(LPDIRECT3DVERTEXBUFFER9 iface)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);

  TRACE("(%p)->()\n", iface);

  if (USE_HAL(This)) {
    DDHAL_UNLOCKDATA data;
    HRESULT hr;

    data.lpDD = This->parent->local.lpGbl;
    data.lpDDSurface = &This->t.lcl;
    data.ddRVal = DD_OK;
    data.Unlock = This->parent->local.lpGbl->lpDDCBtmp->HALDDExeBuf.UnlockExecuteBuffer;
    hr = data.Unlock(&data);
  }

  return D3D_OK;
}

HRESULT WINAPI Direct3DVertexBuffer9_GetDesc(LPDIRECT3DVERTEXBUFFER9 iface,
					     D3DVERTEXBUFFER_DESC* pDesc)
{
  ICOM_THIS(IDirect3DVertexBuffer9Impl, iface);

  TRACE("(%p)->(%p)\n", iface, pDesc);
  pDesc->Format = D3DFMT_VERTEXDATA;
  pDesc->Type = D3DRTYPE_VERTEXBUFFER;
  pDesc->Usage = This->Usage;
  pDesc->Pool = D3DPOOL_DEFAULT;
  if (This->t.lcl.ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY) pDesc->Pool = D3DPOOL_SYSTEMMEM;
  if (This->t.more.ddsCapsEx.dwCaps2 & DDSCAPS2_TEXTUREMANAGE) pDesc->Pool = D3DPOOL_MANAGED;
  pDesc->Size = This->t.gbl.u4.dwLinearSize;
  pDesc->FVF = This->t.more.dwFVF;
  return D3D_OK;
}

static ICOM_VTABLE(IDirect3DVertexBuffer9) D3DVB9_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DVertexBuffer9_QueryInterface,
  Direct3DVertexBuffer9_AddRef,
  Direct3DVertexBuffer9_Release,
  (void*)0xdead5004, /* GetDevice */
  (void*)0xdead5005, /* SetPrivateData */
  (void*)0xdead5006, /* GetPrivateData */
  (void*)0xdead5007, /* FreePrivateData */
  (void*)0xdead5008, /* SetPriority */
  (void*)0xdead500a, /* GetPriority */
  (void*)0xdead500b, /* PreLoad */
  (void*)0xdead500c, /* GetType */
  Direct3DVertexBuffer9_Lock,
  Direct3DVertexBuffer9_Unlock,
  Direct3DVertexBuffer9_GetDesc
};

HRESULT WINAPI Direct3DDevice9_CreateVertexBuffer(LPDIRECT3DDEVICE9 iface,
						  UINT Length,
						  DWORD Usage,
						  DWORD FVF,
						  D3DPOOL Pool,
						  IDirect3DVertexBuffer9** ppVertexBuffer,
						  HANDLE* pSharedHandle)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  IDirect3DVertexBuffer9Impl *pVtxBuf;
  DDSURFACEDESC2* pDDSD;
  LPDDRAWI_DDRAWSURFACE_LCL local;
  DDHAL_CREATESURFACEDATA data;
  HRESULT hr;

  TRACE("(%p)->(%d,0x%lx,0x%lx,%d,%p,%p)\n", iface, Length,
	Usage, FVF, Pool, ppVertexBuffer, pSharedHandle);

  pVtxBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DVertexBuffer9Impl));
  pVtxBuf->lpVtbl = &D3DVB9_Vtbl;
  pVtxBuf->ref = 1;
  pVtxBuf->intref = 0;
  pVtxBuf->parent = This;
  pVtxBuf->Usage = Usage;

  pDDSD = &pVtxBuf->surface_desc;

  pDDSD->dwSize = sizeof(DDSURFACEDESC2);
  pDDSD->dwFlags = DDSD_CAPS | DDSD_LINEARSIZE | DDSD_FVF;
  pDDSD->u1.dwLinearSize = Length;
  pDDSD->u4.dwFVF = FVF;
  pDDSD->ddsCaps.dwCaps = DDSCAPS_EXECUTEBUFFER;
  pDDSD->ddsCaps.dwCaps2 = DDSCAPS2_VERTEXBUFFER;

  if (Usage & D3DUSAGE_DONOTCLIP) pDDSD->ddsCaps.dwCaps |= D3DVBCAPS_DONOTCLIP;
  if (Usage & D3DUSAGE_WRITEONLY) pDDSD->ddsCaps.dwCaps |= D3DVBCAPS_WRITEONLY;

  if (Usage & D3DUSAGE_DYNAMIC)
    pDDSD->ddsCaps.dwCaps2 |= DDSCAPS2_HINTDYNAMIC;
  else
    pDDSD->ddsCaps.dwCaps2 |= DDSCAPS2_HINTSTATIC;

  switch (Pool) {
  case D3DPOOL_DEFAULT:
    pDDSD->ddsCaps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    break;
  case D3DPOOL_MANAGED:
    pDDSD->ddsCaps.dwCaps2 |= DDSCAPS2_TEXTUREMANAGE;
    break;
  case D3DPOOL_SYSTEMMEM:
    pDDSD->ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    break;
  default:
    break;
  }

  pVtxBuf->t.lcl.lpSurfMore = &pVtxBuf->t.more;
  pVtxBuf->t.lcl.lpGbl = &pVtxBuf->t.gbl;
  pVtxBuf->t.lcl.dwFlags = 0; /* FIXME */
  pVtxBuf->t.lcl.ddsCaps.dwCaps = pDDSD->ddsCaps.dwCaps;
  pVtxBuf->t.more.lpDD_lcl = &This->local;
  pVtxBuf->t.more.ddsCapsEx.dwCaps2 = pDDSD->ddsCaps.dwCaps2;
  pVtxBuf->t.more.ddsCapsEx.dwCaps3 = pDDSD->ddsCaps.dwCaps3;
  pVtxBuf->t.more.ddsCapsEx.dwCaps4 = pDDSD->ddsCaps.dwCaps4;
  pVtxBuf->t.more.pCreatedDDSurfaceDesc2 = &pVtxBuf->surface_desc;
  pVtxBuf->t.more.dwFVF = FVF;
  pVtxBuf->t.gmore = &pVtxBuf->t.gbl_more;
  pVtxBuf->t.gbl.u3.lpDD = This->local.lpGbl;
  pVtxBuf->t.gbl.u4.dwLinearSize = pDDSD->u1.dwLinearSize;

  local = &pVtxBuf->t.lcl;

  if (USE_HAL(pVtxBuf)) {
    data.lpDD = This->local.lpGbl;
    data.lpDDSurfaceDesc = (LPDDSURFACEDESC)pDDSD;
    data.lplpSList = &local;
    data.dwSCnt = 1;
    data.ddRVal = 0;
    data.CreateSurface = This->local.lpGbl->lpDDCBtmp->HALDDExeBuf.CreateExecuteBuffer;
    hr = data.CreateSurface(&data);
  }

  /* if HAL didn't allocate the surface, we will */
  if (pVtxBuf->t.gbl.fpVidMem < 4) {
    TRACE("allocating %d bytes\n", Length);
    pVtxBuf->ptr = HeapAlloc(buffer_heap, HEAP_ZERO_MEMORY, Length);
    pVtxBuf->t.gbl.fpVidMem = (FLATPTR)pVtxBuf->ptr;
  }

  TRACE("created %p\n", pVtxBuf);
  *ppVertexBuffer = (LPDIRECT3DVERTEXBUFFER9)pVtxBuf;
  return D3D_OK;
}
