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

#include "config.h"

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

#include "d3d9_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

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

ULONG WINAPI Direct3DCubeTexture9_AddRef(LPDIRECT3DCUBETEXTURE9 iface)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref - This->intref;
}

ULONG WINAPI Direct3DCubeTexture9_Release(LPDIRECT3DCUBETEXTURE9 iface)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  unsigned u;

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

  for (u=0; u<6*This->Levels; u++) {
    IDirect3DSurface9Impl* surf = (IDirect3DSurface9Impl*)This->surf[u];
    if (This->parent->demolish_texture)
      This->parent->demolish_texture(This->parent->ip_ctx,
				     (DWORD)&surf->t);
    IDirect3DSurface9_Release(This->surf[u]);
  }

  /* take the texture out of the linked list */
  if (This->prev_device)
    This->prev_device->next_device = This->next_device;
  else /* if there is no previous, this must be head */
    This->parent->textures = This->next_device;

  if (This->next_device)
    This->next_device->prev_device = This->prev_device;

  HeapFree(GetProcessHeap(), 0, This);
  return 0;
}

HRESULT WINAPI Direct3DCubeTexture9_GetDevice(LPDIRECT3DCUBETEXTURE9 iface,
					      IDirect3DDevice9** ppDevice)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  IDirect3DDevice9* pDevice;

  TRACE("(%p)->(%p)\n", iface, ppDevice);
  pDevice = (IDirect3DDevice9*)This->parent;
  IDirect3DDevice9_AddRef(pDevice);
  *ppDevice = pDevice;

  TRACE("=> %p\n", *ppDevice);
  return D3D_OK;
}

static
DWORD WINAPI Direct3DCubeTexture9_SetPriority( LPDIRECT3DCUBETEXTURE9 iface, DWORD dwNewPriority )
{
  FIXME( "(%p)->(%lu): stub\n", iface, dwNewPriority );
  return 0;
}

static
DWORD WINAPI Direct3DCubeTexture9_GetPriority( LPDIRECT3DCUBETEXTURE9 iface )
{
  FIXME( "(%p): stub\n", iface );
  return 0;
}


VOID WINAPI Direct3DCubeTexture9_PreLoad(LPDIRECT3DCUBETEXTURE9 iface)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXBLT, 0, {1} };
  D3DHAL_DP2TEXBLT data;

  TRACE("(%p)->()\n", iface);
  This->UpdateTexture(This, NULL);
  data.dwDDDestSurface = 0;
  data.dwDDSrcSurface = TEXTURE_HANDLE(This);
  data.pDest.x = 0;
  data.pDest.y = 0;
  data.rSrc.left   = 0;
  data.rSrc.top    = 0;
  data.rSrc.right  = This->EdgeLength;
  data.rSrc.bottom = This->EdgeLength;
  data.dwFlags = 0;
  This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

D3DRESOURCETYPE WINAPI Direct3DCubeTexture9_GetType(LPDIRECT3DCUBETEXTURE9 iface)
{
  TRACE("(%p)->()\n", iface);
  return D3DRTYPE_CUBETEXTURE;
}

DWORD WINAPI Direct3DCubeTexture9_SetLOD(LPDIRECT3DCUBETEXTURE9 iface, DWORD LODNew)
{
  FIXME("(%p)->(%ld): stub\n", iface, LODNew);
  return 0;
}

DWORD WINAPI Direct3DCubeTexture9_GetLOD(LPDIRECT3DCUBETEXTURE9 iface)
{
  FIXME("(%p)->(): stub\n", iface);
  return 0;
}

DWORD WINAPI Direct3DCubeTexture9_GetLevelCount(LPDIRECT3DCUBETEXTURE9 iface)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return This->Levels;
}

HRESULT WINAPI Direct3DCubeTexture9_GetLevelDesc(LPDIRECT3DCUBETEXTURE9 iface,
						 UINT Level,
						 D3DSURFACE_DESC* pDesc)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->(%d,%p)\n", iface, Level, pDesc);
  return IDirect3DSurface9_GetDesc(This->surf[Level], pDesc);
}

HRESULT WINAPI Direct3DCubeTexture9_GetCubeMapSurface(LPDIRECT3DCUBETEXTURE9 iface,
						      D3DCUBEMAP_FACES FaceType,
						      UINT Level,
						      IDirect3DSurface9** ppCubeMapSurface)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->(%d,%d,%p)\n", iface, FaceType, Level, ppCubeMapSurface);
  *ppCubeMapSurface = This->surf[FaceType*This->Levels + Level];
  IDirect3DSurface9_AddRef(*ppCubeMapSurface);
  return D3D_OK;
}

HRESULT WINAPI Direct3DCubeTexture9_LockRect(LPDIRECT3DCUBETEXTURE9 iface,
					     D3DCUBEMAP_FACES FaceType,
					     UINT Level,
					     D3DLOCKED_RECT* pLockedRect,
					     CONST RECT* pRect,
					     DWORD Flags)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  HRESULT hr;
  TRACE("(%p)->(%d,%d,%p,%p,%lx)\n", iface, FaceType, Level,
	pLockedRect, pRect, Flags);
  hr = IDirect3DSurface9_LockRect(This->surf[FaceType*This->Levels + Level],
				  pLockedRect,
				  pRect,
				  Flags);
  return hr;
}

HRESULT WINAPI Direct3DCubeTexture9_UnlockRect(LPDIRECT3DCUBETEXTURE9 iface,
					       D3DCUBEMAP_FACES FaceType,
					       UINT Level)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->(%d,%d)\n", iface, FaceType, Level);
  return IDirect3DSurface9_UnlockRect(This->surf[FaceType*This->Levels + Level]);
}

HRESULT WINAPI Direct3DCubeTexture9_AddDirtyRect(LPDIRECT3DCUBETEXTURE9 iface,
						 D3DCUBEMAP_FACES FaceType,
						 CONST RECT* pDirtyRect)
{
  ICOM_THIS(IDirect3DCubeTexture9Impl, iface);
  TRACE("(%p)->(%d,%p)\n", iface, FaceType, pDirtyRect);
  return This->AddDirtyRect(This, This->surf[FaceType*This->Levels], pDirtyRect);
}

static ICOM_VTABLE(IDirect3DCubeTexture9) D3DCT9_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DCubeTexture9_QueryInterface,
  Direct3DCubeTexture9_AddRef,
  Direct3DCubeTexture9_Release,
  Direct3DCubeTexture9_GetDevice,
  (void*)0xdead7005, /* SetPrivateData */
  (void*)0xdead7006, /* GetPrivateData */
  (void*)0xdead7007, /* FreePrivateData */
  Direct3DCubeTexture9_SetPriority,
  Direct3DCubeTexture9_GetPriority,
  Direct3DCubeTexture9_PreLoad,
  Direct3DCubeTexture9_GetType,
  Direct3DCubeTexture9_SetLOD,
  Direct3DCubeTexture9_GetLOD,
  Direct3DCubeTexture9_GetLevelCount,
  (void*)0xdead700f, /* SetAutoGenFilterType */
  (void*)0xdead7010, /* GetAutoGenFilterType */
  (void*)0xdead7011, /* GenerateMipSubLevels */
  Direct3DCubeTexture9_GetLevelDesc,
  Direct3DCubeTexture9_GetCubeMapSurface,
  Direct3DCubeTexture9_LockRect,
  Direct3DCubeTexture9_UnlockRect,
  Direct3DCubeTexture9_AddDirtyRect
};

HRESULT Direct3DCubeTexture9Impl_AddDirtyRect(IDirect3DCubeTexture9Impl* This,
					      LPDIRECT3DSURFACE9 surf,
					      CONST RECT* pDirtyRect)
{
  D3DCUBEMAP_FACES FaceType;
  TRACE("(%p)->(%p,%p)\n", This, surf, pDirtyRect);
  if (!((IDirect3DSurface9Impl*)This->main)->t.gbl_more.dwDriverReserved) return D3D_OK;
  for (FaceType=0; FaceType<6; FaceType++)
    if (This->surf[FaceType*This->Levels] == surf) break;
  if (FaceType==6) return D3D_OK;
  if (pDirtyRect)
    UnionRect(&This->DirtyRect[FaceType], &This->DirtyRect[FaceType], pDirtyRect);
  else
    SetRect(&This->DirtyRect[FaceType], 0, 0, This->EdgeLength, This->EdgeLength);
  This->IsDirty = TRUE;
  This->parent->dirty_textures = TRUE;
  return D3D_OK;
}

HRESULT Direct3DCubeTexture9Impl_UpdateTexture(IDirect3DCubeTexture9Impl* This,
					       IDirect3DCubeTexture9Impl* pDest)
{
  LPDIRECT3DSURFACE9* ssurf = This->surf;
  LPDIRECT3DSURFACE9* dsurf = pDest ? pDest->surf : NULL;
  UINT Levels = This->Levels, Skip = 0;
  HRESULT hr;
  unsigned f;

  if (pDest && pDest->Levels < Levels)
    Skip = Levels - pDest->Levels;
  /* FIXME: some validation */
  for (f=0; f<6; f++) {
    hr = Direct3DBaseTexture9_UpdateTexture(This->parent,
					    This->EdgeLength, This->EdgeLength,
					    Levels, Skip, &This->DirtyRect[f], FALSE,
					    ssurf, dsurf);
    if (FAILED(hr)) return hr;
    ssurf += This->Levels;
    if (pDest) dsurf += pDest->Levels;
  }
  This->IsDirty = FALSE;
  return D3D_OK;
}

HRESULT Direct3DCubeTexture9Impl_UnloadTexture(IDirect3DCubeTexture9Impl* This)
{
  UINT Level;
  D3DLOCKED_RECT rect;
  /* this makes d3dgl load all mipmap levels into system memory. */
  for (Level=0; Level<This->Levels*6; Level++) {
    IDirect3DSurface9_LockRect(This->surf[Level], &rect, NULL, D3DLOCK_READONLY);
    IDirect3DSurface9_UnlockRect(This->surf[Level]);
  }
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice9_CreateCubeTexture(LPDIRECT3DDEVICE9 iface,
						 UINT EdgeLength,
						 UINT Levels,
						 DWORD Usage,
						 D3DFORMAT Format,
						 D3DPOOL Pool,
						 IDirect3DCubeTexture9** ppTexture,
						 HANDLE* pSharedHandle)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  IDirect3DCubeTexture9Impl *pTexture;
  UINT levels = Levels;
  UINT Edge;
  DDSCAPS2 caps;
  unsigned f, u;
  HRESULT hr;
  IDirect3DSurface9Impl *surf;
  IDirect3DSurface9Impl *next;
  LPATTACHLIST child;

  TRACE("(%p}->(%d,%d,%ld,0x%x,%d,%p,%p)\n", iface, EdgeLength,
	Levels, Usage, Format, Pool, ppTexture, pSharedHandle);

  if (!levels) {
    /* calculate levels */
    Edge = EdgeLength;
    levels = 1;
    while (Edge > 1) {
      Edge = Edge / 2;
      levels++;
    }
  }

  pTexture = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DCubeTexture9Impl) +
		       (6*levels-1) * sizeof(LPDIRECT3DSURFACE9));
  pTexture->lpVtbl = &D3DCT9_Vtbl;
  pTexture->ref = 1;
  pTexture->parent = This;
  pTexture->EdgeLength = EdgeLength;
  pTexture->Levels = levels;
  pTexture->Usage  = Usage;
  pTexture->Format = Format;
  pTexture->AddDirtyRect = Direct3DCubeTexture9Impl_AddDirtyRect;
  pTexture->UpdateTexture = Direct3DCubeTexture9Impl_UpdateTexture;
  pTexture->UnloadTexture = Direct3DCubeTexture9Impl_UnloadTexture;

  memset(&caps, 0, sizeof(caps));
  /* FIXME: handle caps bits */
  caps.dwCaps = DDSCAPS_TEXTURE;
  caps.dwCaps2 = DDSCAPS2_CUBEMAP;
  caps.dwCaps3 = 0;
  caps.dwCaps4 = 0;
  if (Usage & D3DUSAGE_RENDERTARGET) caps.dwCaps |= DDSCAPS_3DDEVICE;
  if (Usage & D3DUSAGE_DEPTHSTENCIL) caps.dwCaps |= DDSCAPS_ZBUFFER;
  if (Usage & D3DUSAGE_WRITEONLY)    caps.dwCaps |= DDSCAPS_WRITEONLY;

  switch (Pool) {
  case D3DPOOL_DEFAULT:
    /* textures in D3DPOOL_DEFAULT are not lockable, they must be
     * accessed via CopyRects or UpdateTexture. Perhaps we could use
     * DDSCAPS2_OPAQUE to let the driver know about this. */
    caps.dwCaps |= DDSCAPS_VIDEOMEMORY;
    break;
  case D3DPOOL_MANAGED:
    caps.dwCaps2 |= DDSCAPS2_TEXTUREMANAGE;
    break;
  case D3DPOOL_SYSTEMMEM:
    caps.dwCaps |= DDSCAPS_SYSTEMMEMORY;
    break;
  default:
    break;
  }

  if (levels > 1) caps.dwCaps |= DDSCAPS_MIPMAP;

  for (f=0; f<6; f++) {
    caps.dwCaps2 &= ~(DDSCAPS2_CUBEMAP_ALLFACES | DDSCAPS2_MIPMAPSUBLEVEL);
    caps.dwCaps2 |= 0x400 << f; /* DDSCAPS2_CUBEMAP_* */
    Edge = EdgeLength;
    for (u=f*levels; u<(f+1)*levels; u++) {
      hr = Direct3DDevice9_CreateSurface(iface, (LPUNKNOWN)pTexture,
					 Edge, Edge, Format,
					 &caps, &pTexture->surf[u],
					 FALSE);
      if (u-f*levels) {
        Direct3DDevice9_AttachSurface(pTexture->surf[u-1],
				      pTexture->surf[u]);
      } else {
        caps.dwCaps2 |= DDSCAPS2_MIPMAPSUBLEVEL;
      }
      if (Edge > 1) Edge = Edge / 2;
    }
  }

  /* create cubemap face chain as in create_texture() in dlls/ddraw/ddraw/main.c */
  surf = (IDirect3DSurface9Impl*)pTexture->surf[5*levels];
  for (f=0; f<6; f++) {
    next = surf;
    surf = (IDirect3DSurface9Impl*)pTexture->surf[f*levels];
    child = surf->t.lcl.lpAttachList;
    if (child) {
      while (child->lpLink)
        child = child->lpLink;
      child->lpLink = &next->sibling;
    }
    else surf->t.lcl.lpAttachList = &next->sibling;
    surf->sibling.lpLink = &next->sibling;
    next->t.lcl.lpAttachListFrom = &surf->sibling;
  }

  pTexture->main = pTexture->surf[0];

  TRACE("created %p\n", pTexture);
  *ppTexture = (LPDIRECT3DCUBETEXTURE9)pTexture;

  /* add texture to linked list */
  if (!This->textures)
  {
    This->textures = (IDirect3DBaseTexture9Impl*)pTexture;
  }
  else
  {
    This->textures->prev_device = (IDirect3DBaseTexture9Impl*)pTexture;
    pTexture->next_device = This->textures;
    This->textures = (IDirect3DBaseTexture9Impl*)pTexture;
  }

  return D3D_OK;
}
