/*
 * 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 Direct3DTexture9_QueryInterface(LPDIRECT3DTEXTURE9 iface,
					       REFIID riid,
					       LPVOID* obj)
{
  ICOM_THIS(IDirect3DTexture9Impl, 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_IDirect3DTexture9, riid)) {
    This->ref++;
    *obj = This;
    return S_OK;
  }
  else {
    FIXME("(%p)->(%s): no interface", iface, debugstr_guid(riid));
    return E_NOINTERFACE;
  }
}

ULONG WINAPI Direct3DTexture9_AddRef(LPDIRECT3DTEXTURE9 iface)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return ++This->ref - This->intref;
}

ULONG WINAPI Direct3DTexture9_Release(LPDIRECT3DTEXTURE9 iface)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  unsigned u;

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

  for (u=0; u<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 Direct3DTexture9_GetDevice(LPDIRECT3DTEXTURE9 iface,
					  IDirect3DDevice9** ppDevice)
{
  ICOM_THIS(IDirect3DTexture9Impl, 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 Direct3DTexture9_SetPriority( LPDIRECT3DTEXTURE9 iface, DWORD dwNewPriority )
{
  FIXME( "(%p)->(%lu): stub\n", iface, dwNewPriority );
  return 0;
}

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


VOID WINAPI Direct3DTexture9_PreLoad(LPDIRECT3DTEXTURE9 iface)
{
  ICOM_THIS(IDirect3DTexture9Impl, 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->Width;
  data.rSrc.bottom = This->Height;
  data.dwFlags = 0;
  This->parent->emit_dp2(This->parent->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

D3DRESOURCETYPE WINAPI Direct3DTexture9_GetType(LPDIRECT3DTEXTURE9 iface)
{
  TRACE("(%p)->()\n", iface);
  return D3DRTYPE_TEXTURE;
}

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

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

DWORD WINAPI Direct3DTexture9_GetLevelCount(LPDIRECT3DTEXTURE9 iface)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  TRACE("(%p)->()\n", iface);
  return This->Levels;
}

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

HRESULT WINAPI Direct3DTexture9_GetSurfaceLevel(LPDIRECT3DTEXTURE9 iface,
						UINT Level,
						IDirect3DSurface9** ppSurfaceLevel)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  TRACE("(%p)->(%d,%p)\n", iface, Level, ppSurfaceLevel);
  *ppSurfaceLevel = This->surf[Level];
  IDirect3DSurface9_AddRef(*ppSurfaceLevel);
  return D3D_OK;
}

HRESULT WINAPI Direct3DTexture9_LockRect(LPDIRECT3DTEXTURE9 iface,
					 UINT Level,
					 D3DLOCKED_RECT* pLockedRect,
					 CONST RECT* pRect,
					 DWORD Flags)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  HRESULT hr;
  TRACE("(%p)->(%d,%p,%p,%lx)\n", iface, Level,
	pLockedRect, pRect, Flags);
  hr = IDirect3DSurface9_LockRect(This->surf[Level],
				  pLockedRect,
				  pRect,
				  Flags);
  return hr;
}

HRESULT WINAPI Direct3DTexture9_UnlockRect(LPDIRECT3DTEXTURE9 iface,
					   UINT Level)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  TRACE("(%p)->(%d)\n", iface, Level);
  return IDirect3DSurface9_UnlockRect(This->surf[Level]);
}

HRESULT WINAPI Direct3DTexture9_AddDirtyRect(LPDIRECT3DTEXTURE9 iface,
					     CONST RECT* pDirtyRect)
{
  ICOM_THIS(IDirect3DTexture9Impl, iface);
  TRACE("(%p)->(%p)\n", iface, pDirtyRect);
  return This->AddDirtyRect(This, This->main, pDirtyRect);
}

static ICOM_VTABLE(IDirect3DTexture9) D3DT9_Vtbl =
{
  ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
  Direct3DTexture9_QueryInterface,
  Direct3DTexture9_AddRef,
  Direct3DTexture9_Release,
  Direct3DTexture9_GetDevice,
  (void*)0xdead4005, /* SetPrivateData */
  (void*)0xdead4006, /* GetPrivateData */
  (void*)0xdead4007, /* FreePrivateData */
  Direct3DTexture9_SetPriority,
  Direct3DTexture9_GetPriority,
  Direct3DTexture9_PreLoad,
  Direct3DTexture9_GetType,
  Direct3DTexture9_SetLOD,
  Direct3DTexture9_GetLOD,
  Direct3DTexture9_GetLevelCount,
  (void*)0xdead400f, /* SetAutoGenFilterType */
  (void*)0xdead4010, /* GetAutoGenFilterType */
  (void*)0xdead4011, /* GenerateMipSubLevels */
  Direct3DTexture9_GetLevelDesc,
  Direct3DTexture9_GetSurfaceLevel,
  Direct3DTexture9_LockRect,
  Direct3DTexture9_UnlockRect,
  Direct3DTexture9_AddDirtyRect
};

HRESULT Direct3DTexture9Impl_AddDirtyRect(IDirect3DTexture9Impl* This,
					  LPDIRECT3DSURFACE9 surf,
					  CONST RECT* pDirtyRect)
{
  TRACE("(%p)->(%p,%p)\n", This, surf, pDirtyRect);
  if (surf != This->main) return D3D_OK;
  if (!((IDirect3DSurface9Impl*)This->main)->t.gbl_more.dwDriverReserved) return D3D_OK;
  if (pDirtyRect)
    UnionRect(&This->DirtyRect, &This->DirtyRect, pDirtyRect);
  else
    SetRect(&This->DirtyRect, 0, 0, This->Width, This->Height);
  This->IsDirty = TRUE;
  This->parent->dirty_textures = TRUE;
  return D3D_OK;
}

HRESULT Direct3DTexture9Impl_UpdateTexture(IDirect3DTexture9Impl* This,
					   IDirect3DTexture9Impl* pDest)
{
  LPDIRECT3DSURFACE9* ssurf = This->surf;
  LPDIRECT3DSURFACE9* dsurf = pDest ? pDest->surf : NULL;
  UINT Levels = This->Levels, Skip = 0;
  HRESULT hr;
  if (pDest && pDest->Levels < Levels)
    Skip = Levels - pDest->Levels;
  /* FIXME: some validation */
  hr = Direct3DBaseTexture9_UpdateTexture(This->parent,
					  This->Width, This->Height,
					  Levels, Skip, &This->DirtyRect, FALSE,
					  ssurf, dsurf);
  if (FAILED(hr)) return hr;
  This->IsDirty = FALSE;
  return D3D_OK;
}

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

HRESULT WINAPI Direct3DDevice9_CreateTexture(LPDIRECT3DDEVICE9 iface,
					     UINT Width,
					     UINT Height,
					     UINT Levels,
					     DWORD Usage,
					     D3DFORMAT Format,
					     D3DPOOL Pool,
					     IDirect3DTexture9** ppTexture,
					     HANDLE* pSharedHandle)
{
  ICOM_THIS(IDirect3DDevice9Impl, iface);
  IDirect3DTexture9Impl *pTexture;
  UINT levels = Levels;
  DDSCAPS2 caps;
  unsigned u;
  HRESULT hr;

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

  if (!levels) {
    /* calculate levels */
    UINT Dim = (Width > Height) ? Width : Height;
    levels = 1;
    while (Dim > 1) {
      Dim = Dim / 2;
      levels++;
    }
  }

  pTexture = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IDirect3DTexture9Impl) +
		       (levels-1) * sizeof(LPDIRECT3DSURFACE9));
  pTexture->lpVtbl = &D3DT9_Vtbl;
  pTexture->ref = 1;
  pTexture->intref = 0;
  pTexture->parent = This;
  pTexture->Width  = Width;
  pTexture->Height = Height;
  pTexture->Levels = levels;
  pTexture->Usage  = Usage;
  pTexture->Format = Format;
  pTexture->AddDirtyRect = Direct3DTexture9Impl_AddDirtyRect;
  pTexture->UpdateTexture = Direct3DTexture9Impl_UpdateTexture;
  pTexture->UnloadTexture = Direct3DTexture9Impl_UnloadTexture;

  memset(&caps, 0, sizeof(caps));
  /* FIXME: handle caps bits */
  caps.dwCaps = DDSCAPS_TEXTURE;
  caps.dwCaps2 = 0;
  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 (u=0; u<levels; u++) {
    hr = Direct3DDevice9_CreateSurface(iface, (LPUNKNOWN)pTexture,
				       Width, Height, Format,
				       &caps, &pTexture->surf[u],
				       FALSE);
    if (hr != D3D_OK)
        goto bail_out;
    if (u) {
      Direct3DDevice9_AttachSurface(pTexture->surf[u-1],
				    pTexture->surf[u]);
    } else {
      caps.dwCaps2 |= DDSCAPS2_MIPMAPSUBLEVEL;
    }
    if (Width  > 1) Width  = Width / 2;
    if (Height > 1) Height = Height / 2;
  }

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

  TRACE("created %p\n", pTexture);
  *ppTexture = (LPDIRECT3DTEXTURE9)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;

bail_out:
  levels = u;
  for (u = 0; u < levels; u++)
  {
    IDirect3DSurface9_Release(pTexture->surf[u]);
  }
  HeapFree(GetProcessHeap(), 0, pTexture);
  return hr;
}

HRESULT WINAPI Direct3DBaseTexture9_UpdateTexture(IDirect3DDevice9Impl* dev,
						  UINT Width, UINT Height, UINT Levels,
						  UINT Skip, RECT* pDirtyRect, BOOL force,
						  LPDIRECT3DSURFACE9* src, LPDIRECT3DSURFACE9* dst)
{
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXBLT, 0, {1} };
  D3DHAL_DP2TEXBLT data;
  unsigned u;

  TRACE("(%p)->(%d,%d,%d,%d,%p,%d,%p,%p)\n", dev, Width, Height, Levels, Skip, pDirtyRect, force, src, dst);
  if (IsRectEmpty(pDirtyRect)) {
    if (!force) return D3D_OK;
    data.rSrc.left = 0;
    data.rSrc.top = 0;
    data.rSrc.right = Width;
    data.rSrc.bottom = Height;
  }
  else memcpy(&data.rSrc, pDirtyRect, sizeof(RECT));
  SetRectEmpty(pDirtyRect);

  data.dwDDSrcSurface = 0;
  data.pDest.x = 0;
  data.pDest.y = 0;
  data.dwFlags = 0;

  for (u=0; u<Levels; u++) {
    IDirect3DSurface9Impl* surf = (IDirect3DSurface9Impl*)src[u];
    if (u >= Skip) {
      if (dst) {
        IDirect3DDevice9_UpdateSurface((LPDIRECT3DDEVICE9)dev, src[u], (RECT*)&data.rSrc, dst[u-Skip], &data.pDest);
      } else {
        data.dwDDDestSurface = (DWORD)&surf->t;
        dev->emit_dp2(dev->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
      }
    }
    if (surf->t.gbl.wWidth > 1) {
      data.rSrc.left = data.rSrc.left / 2;
      data.rSrc.right = (data.rSrc.right+1) / 2;
    }
    if (surf->t.gbl.wHeight > 1) {
      data.rSrc.top = data.rSrc.top / 2;
      data.rSrc.bottom = (data.rSrc.bottom+1) / 2;
    }
  }
  return D3D_OK;
}
