#include "logging.hpp"
#include <pch.hpp>

#include <gltf/gltf_model3d.hpp>

#include <fastgltf/core.hpp>
#include <fastgltf/types.hpp>
#include <fastgltf/tools.hpp>
#include <fastgltf/glm_element_traits.hpp>

namespace OpenEngine {

    void PopulateMesh(Mesh& mesh)
    {
        mesh.vertex_array = VertexArray::Create();

        mesh.vertex_buffer = VertexBuffer::Create(mesh.vertices.size() * sizeof(MeshVertex));
        mesh.vertex_buffer->SetData(mesh.vertices.data(), mesh.vertices.size() * sizeof(MeshVertex));

        BufferLayout layout = {
            { ShaderDataType::Float3, "a_Position" },
            { ShaderDataType::Float3, "a_Normal" },
            { ShaderDataType::Float2, "a_TexCoord" }
        };

        mesh.vertex_buffer->SetLayout(layout);
        mesh.vertex_array->AddVertexBuffer(mesh.vertex_buffer);

        mesh.index_buffer = IndexBuffer::Create(mesh.indices.data(), mesh.indices.size());
        mesh.vertex_array->SetIndexBuffer(mesh.index_buffer);
    }

    Ref<Mesh> CreateMesh(const std::vector<MeshVertex>& vertices,
            const std::vector<uint32_t>& indices)
    {
        Ref<Mesh> mesh = CreateRef<Mesh>();

        mesh->vertices = vertices;
        mesh->indices = indices;

        PopulateMesh(*mesh);

        return mesh;
    }

    void GLTFModel3D::loadPrimitive(fastgltf::Asset& asset,
            fastgltf::Primitive& primitive,
            Mesh& out)
    {
        if (primitive.indicesAccessor.has_value()) {
            auto& accessor = asset.accessors[primitive.indicesAccessor.value()];
            out.indices.resize(accessor.count);

            fastgltf::iterateAccessorWithIndex<uint32_t>(asset, accessor,
                    [&](uint32_t index, size_t i) {
                    out.indices[i] = index;
                    });
        }

        {
            auto* position_iterator = primitive.findAttribute("POSITION");
            auto& accessor = asset.accessors[position_iterator->accessorIndex];
            out.vertices.resize(accessor.count);

            fastgltf::iterateAccessorWithIndex<glm::vec3>(asset, accessor,
                    [&](glm::vec3 pos, size_t i) {
                    out.vertices[i].position = pos;
                    });
        }

        if (auto* normal_iterator = primitive.findAttribute("NORMAL");
                normal_iterator != primitive.attributes.end()) {
            auto& accessor = asset.accessors[normal_iterator->accessorIndex];

            fastgltf::iterateAccessorWithIndex<glm::vec3>(asset, accessor,
                    [&](glm::vec3 norm, size_t i) {
                    out.vertices[i].normal = norm;
                    });
        }

        {
            if (primitive.materialIndex.has_value()) {
                auto& material = asset.materials[primitive.materialIndex.value()];
                auto& pbr = material.pbrData;

                auto& color = pbr.baseColorFactor;
                auto& roughness = pbr.roughnessFactor;
                auto& metallic = pbr.metallicFactor;

                out.material.name = material.name;
                out.material.albedo = glm::vec4(color[0], color[1], color[2], color[3]);
                out.material.roughness = roughness;
                out.material.metallic = metallic;
            }
        }
    }

    GLTFModel3D::GLTFModel3D(const char* path)
    {
        fastgltf::Parser parser;

        auto data = fastgltf::GltfDataBuffer::FromPath(path);
        if (data.error() != fastgltf::Error::None)
            OE_CORE_ERROR("Could not parse model");

        auto asset = parser.loadGltfBinary(data.get(), ".",
                fastgltf::Options::LoadExternalBuffers
                );
        if (asset.error() != fastgltf::Error::None)
            OE_CORE_ERROR("Could not optain asset");


        for (auto& mesh : asset->meshes) {
            name = mesh.name;

            for (auto& primitive : mesh.primitives) {
                Mesh prim_mesh;

                loadPrimitive(asset.get(), primitive, prim_mesh);
                PopulateMesh(prim_mesh);

                meshes.emplace_back(prim_mesh);
            }
        }
    };

    const Mesh& GLTFModel3D::GetMesh(int index) const
    {
        return meshes[index];
    }

    const std::vector<Mesh>& GLTFModel3D::GetMeshes() const
    {
        return meshes;
    }

}
