#include "DXBuffer.h"

#include <iostream>

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

#    include "d3dx12.h"
#endif

using namespace Framework;

// Contents of the DXBuffer class

// Constructor
//  bind: The usage purpose of the buffer. Example: D3D11_BIND_INDEX_BUFFER,
//  D3D11_BIND_VERTEX_BUFFER. eLaen: Length of a single element in bytes
DXBuffer::DXBuffer(int eLen)
    : ReferenceCounter()
{
    data = 0;
    changed = 0;
    len = 0;
    elLen = eLen;
}

// Destructor
DXBuffer::~DXBuffer() {}

// Sets the changed flag so that data is re-copied on the next call to
// 'copy'
void DXBuffer::setChanged()
{
    changed = 1;
}

// Changes the length of the buffer on the next call to 'copy'
//  laen: The length in bytes
void DXBuffer::setLength(int len)
{
    this->len = len;
}

// Sets what will be copied on the next call to 'copy'
//  data: A pointer to the data
void DXBuffer::setData(void* data)
{
    this->data = data;
    changed = 1;
}

// Returns the length of an element in bytes
int DXBuffer::getElementLength() const
{
    return elLen;
}

// Returns the number of elements in the buffer
int DXBuffer::getElementCount() const
{
    return len / elLen;
}

#ifdef WIN32
// Contents of the DX11Buffer class

// Constructor
// eSize: The length of an element in bytes
DX11Buffer::DX11Buffer(int eSize,
    ID3D11Device* device,
    ID3D11DeviceContext* context,
    int bindFlags,
    Critical& deviceLock)
    : DXBuffer(eSize),
      deviceLock(deviceLock)
{
    buffer = 0;
    description = new D3D11_BUFFER_DESC();
    memset(description, 0, sizeof(description));
    description->Usage = D3D11_USAGE_DYNAMIC;
    description->CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    description->BindFlags = bindFlags;

    this->device = device;
    this->context = context;
}

// Destructor
DX11Buffer::~DX11Buffer()
{
    if (buffer) buffer->Release();
    delete description;
}

// Copies the data into the buffer if it has changed
//  zRObj: The object used to communicate with the graphics card
void DX11Buffer::copyToGPU(int byteCount)
{
    if (!len) return;
    if (byteCount < 0) byteCount = len;
    if (description->ByteWidth < (unsigned)len)
    {
        if (buffer) buffer->Release();
        buffer = 0;
    }
    if (!buffer)
    {
        description->ByteWidth = len;
        deviceLock.lock();
        device->CreateBuffer(description, 0, &buffer);
        deviceLock.unlock();
        if (data) changed = 1;
    }
    if (changed)
    {
        HRESULT res;
        D3D11_MAPPED_SUBRESOURCE map;
        if ((description->Usage | D3D11_USAGE_DYNAMIC) == description->Usage)
            res = context->Map(
                buffer, 0, D3D11_MAP::D3D11_MAP_WRITE_DISCARD, 0, &map);
        else
            res = context->Map(buffer, 0, D3D11_MAP::D3D11_MAP_WRITE, 0, &map);
        if (res == S_OK)
        {
            memcpy(map.pData, data, byteCount);
            context->Unmap(buffer, 0);
            changed = 0;
        }
        else
        {
            Logging::error()
                << "Could not update buffer: " << std::hex << res << std::endl;
        }
    }
}

// Returns the buffer
ID3D11Buffer* DX11Buffer::zBuffer() const
{
    return buffer;
}

// Contents of the DXStructuredBuffer class

// Constructor
// eSize: The length of an element in bytes
DX11StructuredBuffer::DX11StructuredBuffer(int eSize,
    ID3D11Device* device,
    ID3D11DeviceContext* context,
    Critical& deviceLock)
    : DX11Buffer(eSize,
          device,
          context,
          D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE,
          deviceLock)
{
    description->MiscFlags = D3D11_RESOURCE_MISC_BUFFER_STRUCTURED;
    description->StructureByteStride = eSize;
    description->Usage = D3D11_USAGE_DEFAULT;
    view = 0;
}

// Destructor
DX11StructuredBuffer::~DX11StructuredBuffer()
{
    if (view) view->Release();
}

// Copies the data into the buffer if it has changed
//  zRObj: The object used to communicate with the graphics card
void DX11StructuredBuffer::copyToGPU(int byteCount)
{
    ID3D11Buffer* old = buffer;
    DX11Buffer::copyToGPU(byteCount);
    if (buffer != old)
    {
        if (view) view->Release();
        D3D11_SHADER_RESOURCE_VIEW_DESC desc = {};
        desc.ViewDimension = D3D11_SRV_DIMENSION_BUFFEREX;
        desc.BufferEx.FirstElement = 0;
        desc.Format = DXGI_FORMAT_UNKNOWN;
        desc.BufferEx.NumElements
            = description->ByteWidth / description->StructureByteStride;
        deviceLock.lock();
        device->CreateShaderResourceView(buffer, &desc, &view);
        deviceLock.unlock();
    }
}

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

#endif