#include <cassert>
#include <utility>
#include "pch.h"
#include "Mesh.h"

#include "Effects/Effect.h"

Mesh::Mesh(ID3D11Device *devicePtr, const std::vector<VertexIn> &verticesIn, const std::vector<Uint32> &indices, std::shared_ptr<Material> material, BaseEffect* effectPtr) :
        m_EffectPtr(effectPtr),
        m_InputLayoutPtr(nullptr),
        m_VertexBufferPtr(nullptr),
        m_IndexBufferPtr(nullptr),
        m_VerticesIn(verticesIn),
        m_Indices(indices),
        m_IndicesCount(static_cast<UINT>(m_Indices.size())),
        m_Material(std::move(material)) {
    HRESULT result;
    D3D11_BUFFER_DESC bufferDesc{};
    D3D11_SUBRESOURCE_DATA subresourceData{};

    m_EffectPtr->SetMaterial(m_Material.get());

    //Create vertex layout
    static constexpr uint32_t vertexElementCount{4};
    D3D11_INPUT_ELEMENT_DESC vertexDesc[vertexElementCount]{};

    vertexDesc[0].SemanticName = "POSITION";
    vertexDesc[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    vertexDesc[0].AlignedByteOffset = 0;
    vertexDesc[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

    vertexDesc[1].SemanticName = "TEXCOORD";
    vertexDesc[1].Format = DXGI_FORMAT_R32G32_FLOAT;
    vertexDesc[1].AlignedByteOffset = offsetof(VertexIn, uv);
    vertexDesc[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

    vertexDesc[2].SemanticName = "NORMAL";
    vertexDesc[2].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    vertexDesc[2].AlignedByteOffset = offsetof(VertexIn, normal);
    vertexDesc[2].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

    vertexDesc[3].SemanticName = "TANGENT";
    vertexDesc[3].Format = DXGI_FORMAT_R32G32B32_FLOAT;
    vertexDesc[3].AlignedByteOffset = offsetof(VertexIn, tangent);
    vertexDesc[3].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;

    //Create input layout
    D3DX11_PASS_DESC passDesc{};
    ID3DX11EffectTechnique *techniquePtr = m_EffectPtr->GetTechniquePtr();
    techniquePtr->GetPassByIndex(0)->GetDesc(&passDesc);

    result = devicePtr->CreateInputLayout(
            vertexDesc,
            vertexElementCount,
            passDesc.pIAInputSignature,
            passDesc.IAInputSignatureSize,
            &m_InputLayoutPtr);

    assert(result == S_OK && "Creating input layout failed");

    //Create vertex buffer
    bufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    bufferDesc.ByteWidth = sizeof(VertexIn) * static_cast<uint32_t>(verticesIn.size());
    bufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    bufferDesc.CPUAccessFlags = 0;
    bufferDesc.MiscFlags = 0;
    subresourceData.pSysMem = verticesIn.data();
    result = devicePtr->CreateBuffer(&bufferDesc, &subresourceData, &m_VertexBufferPtr);

    assert(result == S_OK && "Creating vertex buffer failed");

    //Create index buffer
    bufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
    bufferDesc.ByteWidth = sizeof(uint32_t) * m_IndicesCount;
    bufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    bufferDesc.CPUAccessFlags = 0;
    bufferDesc.MiscFlags = 0;
    subresourceData.pSysMem = m_Indices.data();
    result = devicePtr->CreateBuffer(&bufferDesc, &subresourceData, &m_IndexBufferPtr);

    assert(result == S_OK && "Creating index buffer failed");
}

Mesh::~Mesh() {
    m_InputLayoutPtr->Release();
    m_InputLayoutPtr = nullptr;

    m_VertexBufferPtr->Release();
    m_VertexBufferPtr = nullptr;

    m_IndexBufferPtr->Release();
    m_IndexBufferPtr = nullptr;

    delete m_EffectPtr;
    m_EffectPtr = nullptr;
}

void Mesh::Render(ID3D11DeviceContext *deviceContextPtr, const Matrix &worldViewProj) const {
    m_EffectPtr->SetWorldViewProjMatrix(worldViewProj);
    m_EffectPtr->SetWorldMatrix(m_WorldMatrix);
    //1. Set primitive topology
    deviceContextPtr->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    //2. Set input layout
    deviceContextPtr->IASetInputLayout(m_InputLayoutPtr);

    //3. Set vertex buffer
    constexpr UINT stride = sizeof(VertexIn);
    constexpr UINT offset = 0;
    deviceContextPtr->IASetVertexBuffers(0, 1, &m_VertexBufferPtr, &stride, &offset);

    //4. Set index buffer
    deviceContextPtr->IASetIndexBuffer(m_IndexBufferPtr, DXGI_FORMAT_R32_UINT, 0);

    //5. Draw
    D3DX11_TECHNIQUE_DESC techniqueDesc{};
    m_EffectPtr->GetTechniquePtr()->GetDesc(&techniqueDesc);
    for (UINT p{}; p < techniqueDesc.Passes; p++) {
        m_EffectPtr->GetTechniquePtr()->GetPassByIndex(p)->Apply(0, deviceContextPtr);
        deviceContextPtr->DrawIndexed(m_IndicesCount, 0, 0);
    }
}

void Mesh::NextSamplingState() {
    m_EffectPtr->NextSamplingState();
}

void Mesh::SetCameraPos(const Vector3 &pos) const {
    m_EffectPtr->SetCameraPos(pos);
}

void Mesh::SetWorldMatrix(const Matrix &matrix) {
    m_WorldMatrix = matrix;
}

void Mesh::ToggleNormals() {
    m_EffectPtr->ToggleNormals();
}
