/*
 * Direct3D8 textures
 *  Copyright (c) 2002, TransGaming Technologies Inc.
*/

#include "config.h"

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

#include "d3d8_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(ddraw);

static D3D_Light default_light = {
  {D3DLIGHT_DIRECTIONAL,
   {1,1,1,0},
   {0,0,0,0},
   {0,0,0,0},
   {0,0,0},
   {0,0,1},
   0,0,0,0,0,0,0},
  0, 0, {0,0,0,0}
};

/* table to convert from D3D8 world transform to D3D7 world transform */
static UINT wxfrm[4] = {1,4,5,6};

HRESULT WINAPI Direct3DDevice8_SetTransform(LPDIRECT3DDEVICE8 iface,
					    D3DTRANSFORMSTATETYPE dtstType,
					    CONST D3DMATRIX* lpD3DMatrix)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETTRANSFORM, 0, {1} };
  D3DHAL_DP2SETTRANSFORM xfrm;

  TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
  if (!dtstType) return D3DERR_INVALIDCALL;

  if (dtstType >= 256) {
    unsigned i = dtstType - 256;
    if (i >= 4) return D3DERR_INVALIDCALL;
    dtstType = wxfrm[dtstType-256];
  }

  if (dtstType >= D3DTS_TEXTURE0) {
    unsigned i = dtstType - D3DTS_TEXTURE0;
    if (i >= 8) return D3DERR_INVALIDCALL;
    This->d3dp.txfrm[i] = *lpD3DMatrix;
  }
  else if (dtstType < 8) {
    This->d3dp.xfrm[dtstType] = *lpD3DMatrix;
  }
  xfrm.xfrmType = dtstType;
  xfrm.matrix = *lpD3DMatrix;
  return This->emit_dp2(This->ip_ctx, &cmd, &xfrm, sizeof(xfrm), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetTransform(LPDIRECT3DDEVICE8 iface,
					    D3DTRANSFORMSTATETYPE dtstType,
					    D3DMATRIX* lpD3DMatrix)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%d,%p)\n", This, dtstType, lpD3DMatrix);
  if (!dtstType) return D3DERR_INVALIDCALL;

  if (dtstType >= 256) {
    unsigned i = dtstType - 256;
    if (i >= 4) return D3DERR_INVALIDCALL;
    dtstType = wxfrm[dtstType-256];
  }

  if (dtstType >= D3DTS_TEXTURE0) {
    unsigned i = dtstType - D3DTS_TEXTURE0;
    if (i >= 8) return D3DERR_INVALIDCALL;
    *lpD3DMatrix = This->d3dp.txfrm[i];
  }
  else if (dtstType < 8) {
    *lpD3DMatrix = This->d3dp.xfrm[dtstType];
  }
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetViewport(LPDIRECT3DDEVICE8 iface,
					   CONST D3DVIEWPORT8* lpData)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { 0, 0, {1} };
  D3DHAL_DP2VIEWPORTINFO view;
  D3DHAL_DP2ZRANGE zrange;
  HRESULT hr;

  TRACE("(%p)->(%p)\n", This, lpData);
  memcpy(&This->d3dp.viewport, lpData, sizeof(D3DVIEWPORT7));
  view.dwX = lpData->X;
  view.dwY = lpData->Y;
  view.dwWidth  = lpData->Width;
  view.dwHeight = lpData->Height;
  zrange.dvMinZ = lpData->MinZ;
  zrange.dvMaxZ = lpData->MaxZ;
  cmd.bCommand = D3DDP2OP_VIEWPORTINFO;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &view, sizeof(view), NULL, 0);
  /* FIXME: do we also need to do anything about WINFO? */
  if (SUCCEEDED(hr)) {
      cmd.bCommand = D3DDP2OP_ZRANGE;
      hr = This->emit_dp2(This->ip_ctx, &cmd, &zrange, sizeof(zrange), NULL, 0);
  }
  return hr;
}

HRESULT WINAPI Direct3DDevice8_GetViewport(LPDIRECT3DDEVICE8 iface,
					   D3DVIEWPORT8* lpData)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", This, lpData);
  memcpy(lpData, &This->d3dp.viewport, sizeof(D3DVIEWPORT7));
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetMaterial(LPDIRECT3DDEVICE8 iface,
					   CONST D3DMATERIAL8* lpMat)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETMATERIAL, 0, {1} };

  TRACE("(%p)->(%p)\n", This, lpMat);
#if 0
  _dump_colorvalue("Diffuse", lpMat->u.diffuse);
  _dump_colorvalue("Ambient", lpMat->u1.ambient);
  _dump_colorvalue("Specular", lpMat->u2.specular);
  _dump_colorvalue("Emissive", lpMat->u3.emissive);
  TRACE(" Power : %f\n", lpMat->u4.power);
#endif
  return This->emit_dp2(This->ip_ctx, &cmd,
			(D3DMATERIAL7*)lpMat, sizeof(This->d3dp.material), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetMaterial(LPDIRECT3DDEVICE8 iface,
					   D3DMATERIAL8* lpMat)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", This, lpMat);
  *(D3DMATERIAL7*)lpMat = This->d3dp.material;
  return D3D_OK;
}

static HRESULT CheckExpandLights(IDirect3DDevice8Impl *This, DWORD dwIndex)
{
  if (dwIndex >= This->d3dp.numlights) {
    DWORD newlights = dwIndex+1, cnt;
    D3DHAL_DP2COMMAND cmd = { D3DDP2OP_CREATELIGHT, 0, {1} };
    D3DHAL_DP2CREATELIGHT data;
    D3D_Light *newlight;
    HRESULT hr;

    newlight = HeapReAlloc(GetProcessHeap(),0,This->d3dp.light,sizeof(D3D_Light)*newlights);
    if (!newlight) {
      ERR("out of memory\n");
      return DDERR_OUTOFMEMORY;
    }
    for (cnt=This->d3dp.numlights; cnt<newlights; cnt++)
      memcpy(&newlight[cnt], &default_light, sizeof(D3D_Light));
    data.dwIndex = dwIndex;
    hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
    if (FAILED(hr)) {
      ERR("CREATELIGHT failed (%ld)\n", hr);
      return hr;
    }
    This->d3dp.light = newlight;
    This->d3dp.numlights = newlights;
  }
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetLight(LPDIRECT3DDEVICE8 iface,
					DWORD dwIndex,
					CONST D3DLIGHT8* lpLight)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETLIGHT, 0, {1} };
  D3DHAL_DP2SETLIGHT data;
  HRESULT hr;

  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpLight);
  hr = CheckExpandLights(This, dwIndex);
  if (FAILED(hr)) return hr;
  This->d3dp.light[dwIndex].l = *(D3DLIGHT7*)lpLight;
  data.dwIndex = dwIndex;
  data.dwDataType = D3DHAL_SETLIGHT_DATA;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data),
			&This->d3dp.light[dwIndex].l, sizeof(D3DLIGHT7));
}

HRESULT WINAPI Direct3DDevice8_GetLight(LPDIRECT3DDEVICE8 iface,
					DWORD dwIndex,
					D3DLIGHT8* lpLight)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpLight);
  if (dwIndex < This->d3dp.numlights)
    *(D3DLIGHT7*)lpLight = This->d3dp.light[dwIndex].l;
  else
    *(D3DLIGHT7*)lpLight = default_light.l;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_LightEnable(LPDIRECT3DDEVICE8 iface,
					   DWORD dwIndex,
					   BOOL bEnable)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETLIGHT, 0, {1} };
  D3DHAL_DP2SETLIGHT data;
  DWORD oldflags;
  HRESULT hr;

  TRACE("(%p)->(%ld,%d)\n", This, dwIndex, bEnable);
  hr = CheckExpandLights(This, dwIndex);
  if (FAILED(hr)) return hr;
  oldflags = This->d3dp.light[dwIndex].flags;
  if (bEnable) {
    This->d3dp.light[dwIndex].flags |= D3DLIGHT_ACTIVE;
    data.dwDataType = D3DHAL_SETLIGHT_ENABLE;
  } else {
    This->d3dp.light[dwIndex].flags &= ~D3DLIGHT_ACTIVE;
    data.dwDataType = D3DHAL_SETLIGHT_DISABLE;
  }
  data.dwIndex = dwIndex;
  if (This->d3dp.light[dwIndex].flags == oldflags) return D3D_OK;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetLightEnable(LPDIRECT3DDEVICE8 iface,
					      DWORD dwIndex,
					      LPBOOL lpbEnable)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, lpbEnable);
  if (dwIndex < This->d3dp.numlights)
    *lpbEnable = This->d3dp.light[dwIndex].flags & D3DLIGHT_ACTIVE;
  else
    *lpbEnable = FALSE;
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetClipPlane(LPDIRECT3DDEVICE8 iface,
					    DWORD dwIndex,
					    CONST float* pPlane)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_SETCLIPPLANE, 0, {1} };
  D3DHAL_DP2SETCLIPPLANE data;

  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, pPlane);
  memcpy(&This->d3dp.clipplane[dwIndex], pPlane, sizeof(float)*4);
  data.dwIndex = dwIndex;
  memcpy(&data.plane, pPlane, sizeof(float)*4);
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetClipPlane(LPDIRECT3DDEVICE8 iface,
					    DWORD dwIndex,
					    float* pPlane)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%ld,%p)\n", This, dwIndex, pPlane);
  memcpy(pPlane, &This->d3dp.clipplane[dwIndex], sizeof(float)*4);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetRenderState(LPDIRECT3DDEVICE8 iface,
					      D3DRENDERSTATETYPE d3dType,
					      DWORD dwState)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_RENDERSTATE, 0, {1} };
  D3DHAL_DP2RENDERSTATE stat;

  TRACE("(%p)->(%d,%08lx)\n", This, d3dType, dwState);
  if (d3dType >= 256) {
    WARN("out-of-range render state (%d)\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  /* according to the HAL docs, it seems the driver is
   * responsible for updating the render state array
   * to what it's capable of handling */
  stat.RenderState = d3dType;
  stat.u.dwState = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetRenderState(LPDIRECT3DDEVICE8 iface,
					      D3DRENDERSTATETYPE d3dType,
					      LPDWORD lpdwState)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  if (d3dType >= 256) {
    WARN("out-of-range render state (%d)\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  *lpdwState = This->d3dp.renderstate[d3dType];
  TRACE("(%p)->(%d,%p) => %08lx\n", This, d3dType, lpdwState, *lpdwState);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_BeginStateBlock(LPDIRECT3DDEVICE8 iface)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3D8StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)\n", This);
  if (This->current->state_block) return D3DERR_INVALIDCALL;

  block = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(D3D8StateBlock));

  hr = This->alloc_state_block(This->ip_ctx, &block->state_block);
  if (FAILED(hr)) {
    HeapFree(GetProcessHeap(), 0, block);
    return hr;
  }

  data.dwOperation = D3DHAL_STATESETBEGIN;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) This->current = block;
  else HeapFree(GetProcessHeap(), 0, block);
  return hr;
}

HRESULT WINAPI Direct3DDevice8_EndStateBlock(LPDIRECT3DDEVICE8 iface,
					     LPDWORD lpdwBlock)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3D8StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;

  TRACE("(%p)->(%p)\n", This, lpdwBlock);
  if (!This->current->state_block) return D3DERR_INVALIDCALL;

  block = This->current;
  *lpdwBlock = (DWORD)block;
  This->current = &This->state;
  TRACE(" => %ld\n", *lpdwBlock);

  data.dwOperation = D3DHAL_STATESETEND;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  return This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_ApplyStateBlock(LPDIRECT3DDEVICE8 iface,
					       DWORD dwBlock)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3D8StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->(%ld)\n", This, dwBlock);
  if (This->current->state_block) return D3DERR_INVALIDCALL;

  block = (D3D8StateBlock*)dwBlock;

  data.dwOperation = D3DHAL_STATESETEXECUTE;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) {
    /* update current state to match new device state */
    unsigned u;
    for (u=0; u<8; u++) if (block->dirty_tex & (1<<u)) {
      if (This->state.tex[u] != block->tex[u]) {
        if (block->tex[u]) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)block->tex[u]);
        if (This->state.tex[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)This->state.tex[u]);
        This->state.tex[u] = block->tex[u];
      }
    }
    This->dirty_textures = TRUE; /* just in case */
    for (u=0; u<16; u++) if (block->dirty_stream & (1<<u)) {
      if (This->state.streamsource[u] != block->streamsource[u]) {
        if (block->streamsource[u]) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)block->streamsource[u]);
        if (This->state.streamsource[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)This->state.streamsource[u]);
        This->state.streamsource[u] = block->streamsource[u];
      }
      This->state.streamstride[u] = block->streamstride[u];
      This->d3dp.stream[u].data_t = &((IDirect3DVertexBuffer8Impl*)(block->streamsource[u]))->t;
      This->d3dp.stream[u].stride = block->streamstride[u];
    }
    if (block->dirty_indices) {
      if (This->state.indices != block->indices) {
        if (block->indices) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)block->indices);
        if (This->state.indices) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)This->state.indices);
        This->state.indices = block->indices;
      }
      This->state.baseindex = block->baseindex;
    }
    /* we could also do states that the driver doesn't know about here */
  }
  return hr;
}

HRESULT WINAPI Direct3DDevice8_CaptureStateBlock(LPDIRECT3DDEVICE8 iface,
						 DWORD dwBlock)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3D8StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->(%ld)\n", This, dwBlock);
  if (This->current->state_block) return D3DERR_INVALIDCALL;

  block = (D3D8StateBlock*)dwBlock;

  data.dwOperation = D3DHAL_STATESETCAPTURE;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (SUCCEEDED(hr)) {
    /* update state block to match current state */
    unsigned u;
    for (u=0; u<8; u++) if (block->dirty_tex & (1<<u)) {
      if (This->state.tex[u] != block->tex[u]) {
        if (This->state.tex[u]) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)This->state.tex[u]);
        if (block->tex[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->tex[u]);
        block->tex[u] = This->state.tex[u];
      }
    }
    for (u=0; u<16; u++) if (block->dirty_stream & (1<<u)) {
      if (This->state.streamsource[u] != block->streamsource[u]) {
        if (This->state.streamsource[u]) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)This->state.streamsource[u]);
        if (block->streamsource[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->streamsource[u]);
        block->streamsource[u] = This->state.streamsource[u];
      }
      block->streamstride[u] = This->state.streamstride[u];
    }
    if (block->dirty_indices) {
      if (This->state.indices != block->indices) {
        if (This->state.indices) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)This->state.indices);
        if (block->indices) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->indices);
        block->indices = This->state.indices;
      }
      block->baseindex = This->state.baseindex;
    }
    /* we could also do states that the driver doesn't know about here */
  }
  return hr;
}

HRESULT WINAPI Direct3DDevice8_DeleteStateBlock(LPDIRECT3DDEVICE8 iface,
						DWORD dwBlock)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3D8StateBlock* block;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_STATESET, 0, {1} };
  D3DHAL_DP2STATESET data;
  HRESULT hr;

  TRACE("(%p)->(%ld)\n", This, dwBlock);

  block = (D3D8StateBlock*)dwBlock;

  data.dwOperation = D3DHAL_STATESETDELETE;
  data.dwParam = block->state_block;
  data.sbType = D3DSBT_ALL;
  hr = This->emit_dp2(This->ip_ctx, &cmd, &data, sizeof(data), NULL, 0);
  if (FAILED(hr)) return hr;
  hr = This->free_state_block(This->ip_ctx, dwBlock);
  if (SUCCEEDED(hr)) {
    /* release referenced objects */
    unsigned u;
    for (u=0; u<8; u++)
      if (block->tex[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->tex[u]);
    for (u=0; u<16; u++)
      if (block->streamsource[u]) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->streamsource[u]);
    if (block->indices) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)block->indices);
    HeapFree(GetProcessHeap(), 0, block);
  }
  return hr;
}

HRESULT WINAPI Direct3DDevice8_CreateStateBlock(LPDIRECT3DDEVICE8 iface,
						D3DSTATEBLOCKTYPE type, DWORD *pToken)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  HRESULT hr;

  TRACE("(%p)->(0x%08x, %p)\n", iface, type, pToken);
  hr = IDirect3DDevice8_BeginStateBlock(iface);
  if (FAILED(hr)) return hr;

#define RESET_CURRENT_RENDER_STATE(state) \
    IDirect3DDevice8_SetRenderState(iface, state, This->d3dp.renderstate[state])
#define RESET_CURRENT_TEXTURE_STAGE_STATE(state) \
    IDirect3DDevice8_SetTextureStageState(iface, stage, state, This->d3dp.texstagestate[stage][state])

  if (type == D3DSBT_ALL)
  {
    int stage, stream;
    int clipplane;

    TRACE("doing all\n");

    /* current render states */
    RESET_CURRENT_RENDER_STATE(D3DRS_CULLMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SPECULARENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZVISIBLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGCOLOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_RANGEFOGENABLE);

    RESET_CURRENT_RENDER_STATE(D3DRS_DEBUGMONITORTOKEN);
    RESET_CURRENT_RENDER_STATE(D3DRS_POSITIONORDER);
    RESET_CURRENT_RENDER_STATE(D3DRS_NORMALORDER);
    /* others in pixel state / vertex state */

    /* current textures */
    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
      IDirect3DDevice8_SetTexture(iface, stage, This->state.tex[stage]);

    /* FIXME: current palette */
    FIXME("reset palette\n");

    /* current streams */
    for (stream = 0; stream < 16; stream++)
    {
      IDirect3DDevice8_SetStreamSource(iface, stream, This->state.streamsource[stream],
                                       This->state.streamstride[stream]);
    }

    /* current viewport */
    IDirect3DDevice8_SetViewport(iface, (D3DVIEWPORT8*)&(This->d3dp.viewport));

    /* current transforms */
    IDirect3DDevice8_SetTransform(iface, D3DTS_PROJECTION, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_PROJECTION]));
    IDirect3DDevice8_SetTransform(iface, D3DTS_VIEW, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_VIEW]));
    IDirect3DDevice8_SetTransform(iface, D3DTS_WORLD, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD]));
    IDirect3DDevice8_SetTransform(iface, D3DTS_WORLD1, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD1]));
    IDirect3DDevice8_SetTransform(iface, D3DTS_WORLD2, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD2]));
    IDirect3DDevice8_SetTransform(iface, D3DTS_WORLD3, &(This->d3dp.xfrm[D3DTRANSFORMSTATE_WORLD3]));
    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
      IDirect3DDevice8_SetTransform(iface, D3DTS_TEXTURE0+stage, &(This->d3dp.txfrm[stage]));

    /* current clipplanes */
    for (clipplane = 0; clipplane < This->caps->MaxUserClipPlanes; clipplane++)
    {
      IDirect3DDevice8_SetClipPlane(iface, clipplane, (float*)&(This->d3dp.clipplane[clipplane]));
    }

    /* current material */
    IDirect3DDevice8_SetMaterial(iface, (D3DMATERIAL8*)&(This->d3dp.material));

    /* FIXME: current pixel shader */
    /* FIXME: current pixel shader constants */
  }

  if (type == D3DSBT_PIXELSTATE || type == D3DSBT_ALL)
  {
    int stage;

    TRACE("doing pixel\n");

    /* FIXME: pixel shaders! */
    /* FIXME: current pixel shader constants */
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHABLENDENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHAFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHAREF);
    RESET_CURRENT_RENDER_STATE(D3DRS_ALPHATESTENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_BLENDOP);
    RESET_CURRENT_RENDER_STATE(D3DRS_COLORWRITEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_DESTBLEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_DITHERENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_EDGEANTIALIAS);
    RESET_CURRENT_RENDER_STATE(D3DRS_FILLMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGDENSITY);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGSTART);
    RESET_CURRENT_RENDER_STATE(D3DRS_LASTPIXEL);
    RESET_CURRENT_RENDER_STATE(D3DRS_LINEPATTERN);
    RESET_CURRENT_RENDER_STATE(D3DRS_SHADEMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SRCBLEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILFAIL);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILPASS);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILREF);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILWRITEMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_STENCILZFAIL);
    RESET_CURRENT_RENDER_STATE(D3DRS_TEXTUREFACTOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP0);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP1);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP2);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP3);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP4);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP5);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP6);
    RESET_CURRENT_RENDER_STATE(D3DRS_WRAP7);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZBIAS);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZFUNC);
    RESET_CURRENT_RENDER_STATE(D3DRS_ZWRITEENABLE);

    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
    {
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ADDRESSU);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ADDRESSV);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ADDRESSW);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG0);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG1);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAARG2);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_ALPHAOP);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BORDERCOLOR);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVLOFFSET);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVLSCALE);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT00);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT01);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT10);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_BUMPENVMAT11);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG0);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG1);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLORARG2);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_COLOROP);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MAGFILTER);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MAXANISOTROPY);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MAXMIPLEVEL);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MINFILTER);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MIPFILTER);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_MIPMAPLODBIAS);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_RESULTARG);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXCOORDINDEX);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXTURETRANSFORMFLAGS);
    }
  }

  if (type == D3DSBT_VERTEXSTATE || type == D3DSBT_ALL)
  {
    int stage, light;
    DWORD vertexshader;

    TRACE("doing vertex\n");

    RESET_CURRENT_RENDER_STATE(D3DRS_AMBIENT);
    RESET_CURRENT_RENDER_STATE(D3DRS_AMBIENTMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_CLIPPING);
    RESET_CURRENT_RENDER_STATE(D3DRS_CLIPPLANEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_COLORVERTEX);
    RESET_CURRENT_RENDER_STATE(D3DRS_DIFFUSEMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_EMISSIVEMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGDENSITY);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGEND);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGSTART);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGTABLEMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_FOGVERTEXMODE);
    RESET_CURRENT_RENDER_STATE(D3DRS_INDEXEDVERTEXBLENDENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_LIGHTING);
    RESET_CURRENT_RENDER_STATE(D3DRS_LOCALVIEWER);
    RESET_CURRENT_RENDER_STATE(D3DRS_MULTISAMPLEANTIALIAS);
    RESET_CURRENT_RENDER_STATE(D3DRS_MULTISAMPLEMASK);
    RESET_CURRENT_RENDER_STATE(D3DRS_NORMALIZENORMALS);
    RESET_CURRENT_RENDER_STATE(D3DRS_PATCHEDGESTYLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_PATCHSEGMENTS);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_A);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_B);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALE_C);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSCALEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE_MAX);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSIZE_MIN);
    RESET_CURRENT_RENDER_STATE(D3DRS_POINTSPRITEENABLE);
    RESET_CURRENT_RENDER_STATE(D3DRS_SOFTWAREVERTEXPROCESSING);
    RESET_CURRENT_RENDER_STATE(D3DRS_SPECULARMATERIALSOURCE);
    RESET_CURRENT_RENDER_STATE(D3DRS_TWEENFACTOR);
    RESET_CURRENT_RENDER_STATE(D3DRS_VERTEXBLEND);

    for (stage = 0; stage < This->caps->MaxTextureBlendStages; stage++)
    {
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXCOORDINDEX);
      RESET_CURRENT_TEXTURE_STAGE_STATE(D3DTSS_TEXTURETRANSFORMFLAGS);
    }

    /* lights: */
    for (light = 0; light < This->d3dp.numlights; light++)
    {
      IDirect3DDevice8_SetLight(iface, light, (D3DLIGHT8*)&(This->d3dp.light[light].l));
      IDirect3DDevice8_LightEnable(iface, light, (This->d3dp.light[light].flags & D3DLIGHT_ACTIVE));
    }

    /* vertex shader */
    IDirect3DDevice8_GetVertexShader(iface, &vertexshader);
    IDirect3DDevice8_SetVertexShader(iface, vertexshader);
    /* current vertex shader constants */
    IDirect3DDevice8_SetVertexShaderConstant(iface, 0, This->d3dp.vertex_const,
                                             This->caps->MaxVertexShaderConst);
  }
#undef RESET_CURRENT_RENDER_STATE
#undef RESET_CURRENT_TEXTURE_STAGE_STATE

  hr = IDirect3DDevice8_EndStateBlock(iface, pToken);

  return hr;
}

HRESULT WINAPI Direct3DDevice8_GetTexture(LPDIRECT3DDEVICE8 iface,
					  DWORD dwStage,
					  LPDIRECT3DBASETEXTURE8* lplpTexture)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  *lplpTexture = This->state.tex[dwStage];
  TRACE("(%p)->(%ld,%p) => %p\n", This, dwStage, lplpTexture, *lplpTexture);
  if (*lplpTexture) IDirect3DBaseTexture8_AddRef(*lplpTexture);
  return D3D_OK;
}

ULONG WINAPI IDirect3DResource8_AddRefInternal(LPDIRECT3DRESOURCE8 iface)
{
  ICOM_THIS(IDirect3DResource8Impl, iface);

  TRACE("(%p)->()\n", iface);
  This->intref++;
  return IDirect3DResource8_AddRef(iface);
}

ULONG WINAPI IDirect3DResource8_ReleaseInternal(LPDIRECT3DRESOURCE8 iface)
{ 
  ICOM_THIS(IDirect3DResource8Impl, iface);

  TRACE("(%p)->()\n", iface);
  This->intref--;
  return IDirect3DResource8_Release(iface);
}
  
HRESULT WINAPI Direct3DDevice8_SetTexture(LPDIRECT3DDEVICE8 iface,
					  DWORD dwStage,
					  LPDIRECT3DBASETEXTURE8 lpTexture)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  LPDIRECT3DBASETEXTURE8 oldtex;
  IDirect3DBaseTexture8Impl* tex = (IDirect3DBaseTexture8Impl*)lpTexture;
  /* in reality, Direct3D allocates "surface handles" that it
   * uses for the state here, but since we don't have the memory
   * restrictions that real drivers do (and we're very lazy too),
   * we'll just cast real pointers here, at least for now */
  DWORD dwState = 0;
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
  D3DHAL_DP2TEXTURESTAGESTATE stat;

  TRACE("(%p)->(%ld,%p)\n", This, dwStage, lpTexture);

  oldtex = This->current->tex[dwStage];
  if ((lpTexture == oldtex) && (This->current->dirty_tex & (1<<dwStage))) return D3D_OK;

  /* maintain reference count - but the reference count should be hidden 
   * from the app (as per testing on NT2k DX8.1) so an internal refcount
   * is used */
  if (lpTexture) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)lpTexture);
  This->current->tex[dwStage] = lpTexture;
  This->current->dirty_tex |= 1<<dwStage;
  if (oldtex) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)oldtex);

  /* find texture object's first surface */
  if (lpTexture)
    dwState = TEXTURE_HANDLE(tex);

  if (!This->current->state_block) {
    This->d3dp.texstagestate[dwStage][D3DTSS_TEXTUREMAP] = dwState;
    if (lpTexture && tex->IsDirty) This->dirty_textures = TRUE;
    TRACE("saving\n");
  }
  TRACE("val=%08lx\n", dwState);
  stat.wStage = dwStage;
  stat.TSState = D3DTSS_TEXTUREMAP;
  stat.dwValue = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_GetTextureStageState(LPDIRECT3DDEVICE8 iface,
						    DWORD dwStage,
						    D3DTEXTURESTAGESTATETYPE d3dType,
						    LPDWORD lpdwState)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  DWORD dwState;

  /* tables to convert from D3D7 texture filters to D3D8 texture filters */
  static const DWORD magfilter[6] = {0, 1, 2, 4, 5, 3};
  static const DWORD minfilter[4] = {0, 1, 2, 3};
  static const DWORD mipfilter[4] = {0, 0, 1, 2};

  if (d3dType >= 32) {
    WARN("out-of-range texture stage state %d\n", d3dType);
    return D3DERR_INVALIDCALL;
  }
  dwState = This->d3dp.texstagestate[dwStage][d3dType];

  /* convert from D3D7 (HAL) texture filters to D3D8 texture filters */
  switch (d3dType) {
  case D3DTSS_MAGFILTER:
    dwState = magfilter[dwState];
    break;
  case D3DTSS_MINFILTER:
    dwState = minfilter[dwState];
    break;
  case D3DTSS_MIPFILTER:
    dwState = mipfilter[dwState];
    break;
  default:
    break;
  }

  TRACE("(%p)->(%ld,%d,%p) => %ld\n", This, dwStage, d3dType, lpdwState, dwState);
  *lpdwState = dwState;

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetTextureStageState(LPDIRECT3DDEVICE8 iface,
						    DWORD dwStage,
						    D3DTEXTURESTAGESTATETYPE d3dType,
						    DWORD dwState)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  D3DHAL_DP2COMMAND cmd = { D3DDP2OP_TEXTURESTAGESTATE, 0, {1} };
  D3DHAL_DP2TEXTURESTAGESTATE stat;

  /* tables to convert from D3D8 texture filters to D3D7 texture filters */
  static const DWORD magfilter[6] = {0, 1, 2, 5, 3, 4};
  static const DWORD minfilter[6] = {0, 1, 2, 3, 3, 3};
  static const DWORD mipfilter[6] = {1, 2, 3, 3, 3, 3};

  TRACE("(%p)->(%ld,%d,%ld)\n", This, dwStage, d3dType, dwState);

  if (d3dType >= 32) {
    WARN("out-of-range texture stage state %d\n", d3dType);
    return D3DERR_INVALIDCALL;
  }

  /* convert from D3D8 texture filters to D3D7 (HAL) texture filters */
  switch (d3dType) {
  case D3DTSS_MAGFILTER:
    dwState = magfilter[dwState];
    break;
  case D3DTSS_MINFILTER:
    dwState = minfilter[dwState];
    break;
  case D3DTSS_MIPFILTER:
    dwState = mipfilter[dwState];
    break;
  default:
    break;
  }

  /* the doc implies that the app is allowed to set any state it wants,
   * _then_ check (with ValidateDevice) whether the hardware groks the
   * resulting state vector, so I guess no checks should be performed here
   */
  if (!This->current->state_block)
    This->d3dp.texstagestate[dwStage][d3dType] = dwState;
  stat.wStage = dwStage;
  stat.TSState = d3dType;
  stat.dwValue = dwState;
  return This->emit_dp2(This->ip_ctx, &cmd, &stat, sizeof(stat), NULL, 0);
}

HRESULT WINAPI Direct3DDevice8_ValidateDevice(LPDIRECT3DDEVICE8 iface,
					      DWORD* pNumPasses)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  TRACE("(%p)->(%p)\n", This, pNumPasses);
  return This->validate_device(This->ip_ctx, pNumPasses);
}

HRESULT WINAPI Direct3DDevice8_SetStreamSource(LPDIRECT3DDEVICE8 iface,
					       UINT StreamNumber,
					       LPDIRECT3DVERTEXBUFFER8 pStreamData,
					       UINT Stride)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  LPDIRECT3DVERTEXBUFFER8 olddata;
  IDirect3DVertexBuffer8Impl* buffer = (IDirect3DVertexBuffer8Impl *)pStreamData;

  TRACE("(%p)->(%d,%p,%d)\n", This, StreamNumber, pStreamData, Stride);

  olddata = This->current->streamsource[StreamNumber];

  if (pStreamData != olddata) {
    if (pStreamData) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)pStreamData);
    This->current->streamsource[StreamNumber] = pStreamData;
    if (olddata) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)olddata);
  }
  This->current->streamstride[StreamNumber] = Stride;
  This->current->dirty_stream |= 1<<StreamNumber;

  This->d3dp.stream[StreamNumber].data_t = &(buffer->t);
  This->d3dp.stream[StreamNumber].stride = Stride;

  if (dx8_hal) {
    /* FIXME: should emit D3DHAL_DP2SETSTREAMSOURCE */
  }

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetStreamSource(LPDIRECT3DDEVICE8 iface,
					       UINT StreamNumber,
					       LPDIRECT3DVERTEXBUFFER8* ppStreamData,
					       UINT* pStride)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  *ppStreamData = This->state.streamsource[StreamNumber];
  *pStride = This->state.streamstride[StreamNumber];
  TRACE("(%p)->(%d,%p,%p)\n", This, StreamNumber, ppStreamData, pStride);
  if (*ppStreamData) IDirect3DVertexBuffer8_AddRef(*ppStreamData);
  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_SetIndices(LPDIRECT3DDEVICE8 iface,
					  LPDIRECT3DINDEXBUFFER8 pIndexData,
					  UINT BaseIndex)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  LPDIRECT3DINDEXBUFFER8 olddata;

  TRACE("(%p)->(%p,%d)\n", This, pIndexData, BaseIndex);

  olddata = This->current->indices;

  if (pIndexData != olddata) {
    if (pIndexData) IDirect3DResource8_AddRefInternal((LPDIRECT3DRESOURCE8)pIndexData);
    This->current->indices = pIndexData;
    if (olddata) IDirect3DResource8_ReleaseInternal((LPDIRECT3DRESOURCE8)olddata);
  }
  This->current->baseindex = BaseIndex;
  This->current->dirty_indices = TRUE;

  if (dx8_hal) {
    /* FIXME: should emit D3DHAL_DP2SETINDICES */
  }

  return D3D_OK;
}

HRESULT WINAPI Direct3DDevice8_GetIndices(LPDIRECT3DDEVICE8 iface,
					  LPDIRECT3DINDEXBUFFER8* ppIndexData,
					  UINT* pBaseIndex)
{
  ICOM_THIS(IDirect3DDevice8Impl, iface);
  *ppIndexData = This->state.indices;
  *pBaseIndex = This->state.baseindex;
  TRACE("(%p)->(%p,%p)\n", This, ppIndexData, pBaseIndex);
  if (*ppIndexData) IDirect3DIndexBuffer8_AddRef(*ppIndexData);
  return D3D_OK;
}
