#include "Texture.h"

#include "Image.h"
#ifdef WIN32
#    include <d3d11.h>
#    include <d3d12.h>

#    include "d3dx12.h"
#endif

using namespace Framework;

// Contents of the Texture class
// Constructor
Texture::Texture()
    : ReferenceCounter()
{
    bild = 0;
    lastGr = Point(0, 0);
    id = -1;
    changed = 0;
}

// Destructor
Texture::~Texture()
{
    if (bild) bild->release();
}

// Sets a pointer to the image that contains the texture
//  b: The pointer to the image
void Texture::setImageZ(Image* b)
{
    if (bild != b) changed = 1;
    if (bild) bild->release();
    bild = b;
}

// Sets the image that contains the texture by copying it
//  b: The image to be copied
void Texture::setImage(Image* b)
{
    if (!b) return;
    if (bild != b) changed = 1;
    if (!bild || bild->getWidth() != b->getWidth()
        || bild->getHeight() != b->getHeight())
    {
        if (!bild) bild = new Image();
        bild->newImage(b->getWidth(), b->getHeight(), 0);
    }
    bild->drawImage(0, 0, bild->getWidth(), bild->getHeight(), *b);
    b->release();
}

// Returns a pointer to the image
Image* Texture::getImage() const
{
    return bild ? dynamic_cast<Image*>(bild->getThis()) : 0;
}

// Returns a pointer to the image without increased reference counter
Image* Texture::zImage() const
{
    return bild;
}

// Returns the id of the texture if it was registered in a TextureList.
// (see Framework::zTextureRegister())
int Texture::getId() const
{
    return id;
}

// Updates the texture. The pixels of the current image are copied into
// the graphics memory
bool DX9Texture::updateTextur()
{
    return 1;
}

// Returns true if updateTextur needs to be called
bool DX9Texture::needsUpdate() const
{
    return 0;
}

DX11Texture::DX11Texture(
    ID3D11Device* device, ID3D11DeviceContext* context, Critical& deviceLock)
    : Texture(),
      txt(0),
      view(0),
      device(device),
      context(context),
      renderTarget(0),
      useMips(1),
      deviceLock(deviceLock)
{}

DX11Texture::~DX11Texture()
{
#ifdef WIN32
    if (txt) txt->Release();
    if (view) view->Release();
#endif
}

// Updates the texture. The pixels of the current image are copied into
// the graphics memory
bool DX11Texture::updateTextur()
{
    if (!bild) return 0;
#ifdef WIN32
    if (!txt || lastGr != bild->getSize())
    {
        if (txt) txt->Release();
        txt = 0;
        D3D11_TEXTURE2D_DESC bufferDesc;
        memset(&bufferDesc, 0, sizeof(D3D11_TEXTURE2D_DESC));
        bufferDesc.ArraySize = 1;
        bufferDesc.Width = bild->getWidth();
        bufferDesc.Height = bild->getHeight();
        bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        bufferDesc.BindFlags
            = (renderTarget || useMips ? D3D11_BIND_RENDER_TARGET : 0)
            | D3D11_BIND_SHADER_RESOURCE;
        bufferDesc.CPUAccessFlags
            = renderTarget || useMips ? 0 : D3D11_CPU_ACCESS_WRITE;
        bufferDesc.SampleDesc.Count = 1;
        bufferDesc.MipLevels = useMips ? 0 : 1;
        bufferDesc.Usage = renderTarget || useMips ? D3D11_USAGE_DEFAULT
                                                   : D3D11_USAGE_DYNAMIC;
        bufferDesc.MiscFlags = useMips ? D3D11_RESOURCE_MISC_GENERATE_MIPS : 0;
        deviceLock.lock();
        HRESULT r = device->CreateTexture2D(&bufferDesc, 0, &txt);
        deviceLock.unlock();
        if (r != S_OK) return 0;
    }
    if (!renderTarget && (bild->getNeedRender() || changed))
    {
        changed = 0;
        if (useMips)
        {
            context->UpdateSubresource(txt,
                0,
                0,
                bild->getBuffer(),
                4 * bild->getWidth(),
                4 * bild->getWidth() * bild->getHeight());
        }
        else
        {
            D3D11_MAPPED_SUBRESOURCE buffer;
            context->Map(
                txt, 0, D3D11_MAP::D3D11_MAP_WRITE_DISCARD, 0, &buffer);
            int* bgBuff = bild->getBuffer();
            int tmpBr = 4 * bild->getWidth();
            for (int y = 0, pitch = 0, bry = 0; y < bild->getHeight();
                ++y, pitch += buffer.RowPitch, bry += bild->getWidth())
                memcpy(&((BYTE*)buffer.pData)[pitch],
                    (void*)&(bgBuff[bry]),
                    tmpBr);
            context->Unmap(txt, 0);
        }
    }
    if (!view || lastGr != bild->getSize())
    {
        if (view) view->Release();
        view = 0;
        D3D11_SHADER_RESOURCE_VIEW_DESC resourceDesk;
        memset(&resourceDesk, 0, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
        resourceDesk.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
        resourceDesk.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        resourceDesk.Texture2D.MipLevels = useMips ? -1 : 1;
        deviceLock.lock();
        HRESULT r = device->CreateShaderResourceView(txt, &resourceDesk, &view);
        deviceLock.unlock();
        if (r != S_OK) return 0;
        if (context && useMips) context->GenerateMips(view);
    }
    lastGr = bild->getSize();
#endif
    return 1;
}

// Returns true if updateTextur needs to be called
bool DX11Texture::needsUpdate() const
{
    return !view;
}

// Returns the used shader resource view
DX11Texture::operator ID3D11ShaderResourceView*() const
{
    return view;
}

//! Returns the used texture
DX11Texture::operator ID3D11Texture2D*() const
{
    return txt;
}

//! specifies that this texture is used as a render target
void DX11Texture::setRenderTarget(bool rt)
{
    if (rt) useMips = 0;
    renderTarget = rt;
}

//! specifies if a mip map should be generated
void DX11Texture::setUseMips(bool useMips)
{
    this->useMips = useMips;
}

//! copy the texture to an image
void DX11Texture::copyToImage(Image* zB)
{
#ifdef WIN32
    D3D11_TEXTURE2D_DESC tempBufferDesc;
    memset(&tempBufferDesc, 0, sizeof(D3D11_TEXTURE2D_DESC));
    tempBufferDesc.ArraySize = 1;
    tempBufferDesc.Width = bild->getWidth();
    tempBufferDesc.Height = bild->getHeight();
    tempBufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    tempBufferDesc.BindFlags = 0;
    tempBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    tempBufferDesc.SampleDesc.Count = 1;
    tempBufferDesc.MipLevels = 1;
    tempBufferDesc.Usage = D3D11_USAGE_STAGING;
    ID3D11Texture2D* tmpTxt;
    deviceLock.lock();
    HRESULT r = device->CreateTexture2D(&tempBufferDesc, 0, &tmpTxt);
    deviceLock.unlock();
    if (r != S_OK) throw "could not create resource copy with cpu read access";
    context->CopyResource(tmpTxt, txt);
    zB->newImage(bild->getWidth(), bild->getHeight(), 0);
    D3D11_MAPPED_SUBRESOURCE buffer;
    r = context->Map(tmpTxt, 0, D3D11_MAP::D3D11_MAP_READ, 0, &buffer);
    if (r != S_OK) throw "could not access recource copy";
    int* bgBuff = zB->getBuffer();
    int tmpBr = 4 * zB->getWidth();
    for (int y = 0, pitch = 0, bry = 0; y < zB->getHeight();
        ++y, pitch += buffer.RowPitch, bry += zB->getWidth())
        memcpy((void*)&(bgBuff[bry]), &((BYTE*)buffer.pData)[pitch], tmpBr);
    for (int i = 0; i < zB->getWidth() * zB->getHeight(); i++)
    {
        if (bgBuff[i]) bgBuff[i] |= 0xFF000000;
    }
    context->Unmap(tmpTxt, 0);
    tmpTxt->Release();
#endif
}