#include "DirectXMesh.h"

#include "SceneModel.h"
#include "DrawingContext.h"
#include "DrawingSurface.h"

#include "TriMesh.h"
#include "TriFace.h"
#include "FileLogger.h"

#include <directx/d3dx12.h>

DirectXMesh::DirectXMesh(SceneModel* model)
	: mModel(model)
{

}

void DirectXMesh::update(DrawingContext* context, ID3D12Device* device)
{
    mVertexBuffer.clear();
    mIndexBuffer.clear();

    const auto width = float(context->getSurface()->getWidth());
    const auto height = float(context->getSurface()->getHeight());

    auto model_color = mModel->getSolidMaterial().getFillColor().getAsVectorDouble();
    std::vector<float> color = { float(model_color[0]), float(model_color[1]), float(model_color[2]), float(model_color[3]) };

    auto transform = mModel->getTransform();

    for (const auto& face : dynamic_cast<TriMesh*>(mModel->getMesh())->getFaces())
    {
        for (const auto& loc : face->getNodeLocations(AbstractFace::Orientation::CW))
        {
            auto x = loc.getX() * transform.getScaleX() + transform.getLocation().getX();
            auto y = loc.getY() * transform.getScaleY() + transform.getLocation().getY();
            x = 2 * x - 1;
            y = 2 * y - 1;

            Vertex vert;
            vert.position = DirectX::XMFLOAT3(static_cast<float>(x), static_cast<float>(y), 0.0);
            vert.color = DirectX::XMFLOAT4(color[0], color[1], color[2], color[3]);
            MLOG_INFO("Adding vert: " << x << " | " << y);
            mVertexBuffer.push_back(vert);
        }
    }

    /*
    for (const auto& node : mModel->getMesh()->getNodes())
    {
        auto x = node->getPoint().getX() * transform.getScaleX() + transform.getLocation().getX();
        //x = 2 * x / width - 1.0;
        auto y = node->getPoint().getY() * transform.getScaleY() + transform.getLocation().getY();
        //y = 2 * y / height;
        Vertex vert;
        vert.position = DirectX::XMFLOAT3(x, y, 0.0);
        vert.color = DirectX::XMFLOAT4(color[0], color[1], color[2], color[3]);
        MLOG_INFO("Adding vert: " << x << " | " << y);
        mVertexBuffer.push_back(vert);
    }
    */

    //mIndexBuffer = dynamic_cast<TriMesh*>(mModel->getMesh())->getFaceNodeIds();
    mIndexBuffer = { 0, 1, 2 };
    for (auto id : mIndexBuffer)
    {
        MLOG_INFO("Adding id: " << id);
    }

    createD3dVertexBuffer(device);
    createD3dIndexBuffer(device);

    mVertexBuffer.clear();
    mIndexBuffer.clear();
}

void DirectXMesh::createD3dVertexBuffer(ID3D12Device* device)
{
    // Note: using upload heaps to transfer static data like vert buffers is not 
    // recommended. Every time the GPU needs it, the upload heap will be marshalled 
    // over. Please read up on Default Heap usage. An upload heap is used here for 
    // code simplicity and because there are very few verts to actually transfer.
    CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
    const auto buffer_size = mVertexBuffer.size() * sizeof(Vertex);
    auto desc = CD3DX12_RESOURCE_DESC::Buffer(buffer_size);
    device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&mD3dVertexBuffer));

    UINT8* pDataBegin;
    CD3DX12_RANGE readRange(0, 0);
    mD3dVertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pDataBegin));
    uploadVertexBuffer(pDataBegin);
    mD3dVertexBuffer->Unmap(0, nullptr);

    mVertexBufferView.BufferLocation = mD3dVertexBuffer->GetGPUVirtualAddress();
    mVertexBufferView.StrideInBytes = sizeof(Vertex);
    mVertexBufferView.SizeInBytes = static_cast<UINT>(buffer_size);
}

void DirectXMesh::uploadVertexBuffer(unsigned char* pBuffer) const
{
    memcpy(pBuffer, mVertexBuffer.data(), mVertexBuffer.size() * sizeof(Vertex));
}

void DirectXMesh::createD3dIndexBuffer(ID3D12Device* device)
{
    CD3DX12_HEAP_PROPERTIES heapProps(D3D12_HEAP_TYPE_UPLOAD);
    const auto buffer_size = mIndexBuffer.size() * sizeof(unsigned);
    auto desc = CD3DX12_RESOURCE_DESC::Buffer(buffer_size);
    device->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&mD3dIndexBuffer));

    UINT8* pDataBegin;
    CD3DX12_RANGE readRange(0, 0);
    mD3dIndexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pDataBegin));
    uploadIndexBuffer(pDataBegin);
    mD3dIndexBuffer->Unmap(0, nullptr);

    mIndexBufferView.BufferLocation = mD3dIndexBuffer->GetGPUVirtualAddress();
    mIndexBufferView.SizeInBytes = static_cast<UINT>(buffer_size);
    mIndexBufferView.Format = DXGI_FORMAT_R16_UINT;
}

void DirectXMesh::uploadIndexBuffer(unsigned char* pBuffer) const
{
    memcpy(pBuffer, mIndexBuffer.data(), mIndexBuffer.size() * sizeof(unsigned));
}

const D3D12_VERTEX_BUFFER_VIEW& DirectXMesh::getVertBufferView() const
{
    return mVertexBufferView;
}

const D3D12_INDEX_BUFFER_VIEW& DirectXMesh::getIndexBufferView() const
{
    return mIndexBufferView;
}

SceneModel* DirectXMesh::getModel() const
{
    return mModel;
}