#include "d3d9_texture.h"

#include "d3d9_util.h"

namespace dxvk {

  // Direct3DTexture9

  D3D9Texture2D::D3D9Texture2D(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc,
          HANDLE*                   pSharedHandle)
    : D3D9Texture2DBase( pDevice, pDesc, D3DRTYPE_TEXTURE, pSharedHandle ) { }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    if (riid == __uuidof(ID3D9VkInteropTexture)) {
      *ppvObject = ref(m_texture.GetVkInterop());
      return S_OK;
    }

    if (logQueryInterfaceError(__uuidof(IDirect3DTexture9), riid)) {
      Logger::warn("D3D9Texture2D::QueryInterface: Unknown interface query");
      Logger::warn(str::format(riid));
    }

    return E_NOINTERFACE;
  }


  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9Texture2D::GetType() {
    return D3DRTYPE_TEXTURE;
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::GetSurfaceLevel(UINT Level, IDirect3DSurface9** ppSurfaceLevel) {
    InitReturnPtr(ppSurfaceLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppSurfaceLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppSurfaceLevel = ref(GetSubresource(Level));
    return D3D_OK;
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::LockRect(UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->LockRect(pLockedRect, pRect, Flags);
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::UnlockRect(UINT Level) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->UnlockRect();
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture2D::AddDirtyRect(CONST RECT* pDirtyRect) {
    if (pDirtyRect) {
      D3DBOX box = { UINT(pDirtyRect->left), UINT(pDirtyRect->top), UINT(pDirtyRect->right), UINT(pDirtyRect->bottom), 0, 1 };
      m_texture.AddDirtyBox(&box, 0);
    } else {
      m_texture.AddDirtyBox(nullptr, 0);
    }

    // Some games keep using the pointer returned in LockRect() after calling Unlock()
    // and purely rely on AddDirtyRect to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    if (m_texture.IsManaged())
      m_texture.SetAllNeedUpload();

    m_parent->TouchMappedTexture(&m_texture);
    return D3D_OK;
  }


  // Direct3DVolumeTexture9


  D3D9Texture3D::D3D9Texture3D(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc)
    : D3D9Texture3DBase( pDevice, pDesc, D3DRTYPE_VOLUMETEXTURE, nullptr ) { }


  HRESULT STDMETHODCALLTYPE D3D9Texture3D::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DVolumeTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    if (riid == __uuidof(ID3D9VkInteropTexture)) {
      *ppvObject = ref(m_texture.GetVkInterop());
      return S_OK;
    }

    if (logQueryInterfaceError(__uuidof(IDirect3DVolumeTexture9), riid)) {
      Logger::warn("D3D9Texture3D::QueryInterface: Unknown interface query");
      Logger::warn(str::format(riid));
    }

    return E_NOINTERFACE;
  }


  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9Texture3D::GetType() {
    return D3DRTYPE_VOLUMETEXTURE;
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture3D::GetLevelDesc(UINT Level, D3DVOLUME_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture3D::GetVolumeLevel(UINT Level, IDirect3DVolume9** ppVolumeLevel) {
    InitReturnPtr(ppVolumeLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppVolumeLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppVolumeLevel = ref(GetSubresource(Level));
    return D3D_OK;
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture3D::LockBox(UINT Level, D3DLOCKED_BOX* pLockedBox, CONST D3DBOX* pBox, DWORD Flags) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->LockBox(pLockedBox, pBox, Flags);
  }


  HRESULT STDMETHODCALLTYPE D3D9Texture3D::UnlockBox(UINT Level) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->UnlockBox();
  }

  HRESULT STDMETHODCALLTYPE D3D9Texture3D::AddDirtyBox(CONST D3DBOX* pDirtyBox) {
    m_texture.AddDirtyBox(pDirtyBox, 0);

    // Some games keep using the pointer returned in LockBox() after calling Unlock()
    // and purely rely on AddDirtyBox to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    if (m_texture.IsManaged())
      m_texture.SetAllNeedUpload();

    m_parent->TouchMappedTexture(&m_texture);
    return D3D_OK;
  }


  // Direct3DCubeTexture9


  D3D9TextureCube::D3D9TextureCube(
          D3D9DeviceEx*             pDevice,
    const D3D9_COMMON_TEXTURE_DESC* pDesc)
    : D3D9TextureCubeBase( pDevice, pDesc, D3DRTYPE_CUBETEXTURE, nullptr ) { }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::QueryInterface(REFIID riid, void** ppvObject) {
    if (ppvObject == nullptr)
      return E_POINTER;

    *ppvObject = nullptr;

    if (riid == __uuidof(IUnknown)
     || riid == __uuidof(IDirect3DResource9)
     || riid == __uuidof(IDirect3DBaseTexture9)
     || riid == __uuidof(IDirect3DCubeTexture9)) {
      *ppvObject = ref(this);
      return S_OK;
    }

    if (riid == __uuidof(ID3D9VkInteropTexture)) {
      *ppvObject = ref(m_texture.GetVkInterop());
      return S_OK;
    }

    if (logQueryInterfaceError(__uuidof(IDirect3DCubeTexture9), riid)) {
      Logger::warn("D3D9TextureCube::QueryInterface: Unknown interface query");
      Logger::warn(str::format(riid));
    }

    return E_NOINTERFACE;
  }


  D3DRESOURCETYPE STDMETHODCALLTYPE D3D9TextureCube::GetType() {
    return D3DRTYPE_CUBETEXTURE;
  }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::GetLevelDesc(UINT Level, D3DSURFACE_DESC *pDesc) {
    if (unlikely(Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(Level)->GetDesc(pDesc);
  }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::GetCubeMapSurface(D3DCUBEMAP_FACES Face, UINT Level, IDirect3DSurface9** ppSurfaceLevel) {
    InitReturnPtr(ppSurfaceLevel);

    if (unlikely(Level >= m_texture.ExposedMipLevels() || Face >= D3DCUBEMAP_FACES(6)))
      return D3DERR_INVALIDCALL;

    if (unlikely(ppSurfaceLevel == nullptr))
      return D3DERR_INVALIDCALL;

    *ppSurfaceLevel = ref(GetSubresource(m_texture.CalcSubresource(UINT(Face), Level)));
    return D3D_OK;
  }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::LockRect(D3DCUBEMAP_FACES Face, UINT Level, D3DLOCKED_RECT* pLockedRect, CONST RECT* pRect, DWORD Flags) {
    if (unlikely(Face > D3DCUBEMAP_FACE_NEGATIVE_Z || Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(m_texture.CalcSubresource(UINT(Face), Level))->LockRect(pLockedRect, pRect, Flags);
  }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::UnlockRect(D3DCUBEMAP_FACES Face, UINT Level) {
    if (unlikely(Face > D3DCUBEMAP_FACE_NEGATIVE_Z || Level >= m_texture.ExposedMipLevels()))
      return D3DERR_INVALIDCALL;

    return GetSubresource(m_texture.CalcSubresource(UINT(Face), Level))->UnlockRect();
  }


  HRESULT STDMETHODCALLTYPE D3D9TextureCube::AddDirtyRect(D3DCUBEMAP_FACES Face, CONST RECT* pDirtyRect) {
    if (pDirtyRect) {
      D3DBOX box = { UINT(pDirtyRect->left), UINT(pDirtyRect->top), UINT(pDirtyRect->right), UINT(pDirtyRect->bottom), 0, 1 };
      m_texture.AddDirtyBox(&box, Face);
    } else {
      m_texture.AddDirtyBox(nullptr, Face);
    }

    // Some games keep using the pointer returned in LockRect() after calling Unlock()
    // and purely rely on AddDirtyRect to notify D3D9 that contents have changed.
    // We have no way of knowing which mip levels were actually changed.
    if (m_texture.IsManaged()) {
      for (uint32_t m = 0; m < m_texture.ExposedMipLevels(); m++) {
        m_texture.SetNeedsUpload(m_texture.CalcSubresource(Face, m), true);
      }
    }

    m_parent->TouchMappedTexture(&m_texture);
    return D3D_OK;
  }

}
