/*
	GameEngine and Editor
	by John Ryland
	Copyright (c) 2023
*/

////////////////////////////////////////////////////////////////////////////////////
//	Model

#include "Model.h"

#include <unordered_map>
#include <stdexcept>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/hash.hpp>
//#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>

using Vec2 = Vulkan::Attribute<glm::vec2, VK_FORMAT_R32G32_SFLOAT>;
using Vec3 = Vulkan::Attribute<glm::vec3, VK_FORMAT_R32G32B32_SFLOAT>;

struct Vertex
{
    Vec3 m_position;
    Vec3 m_color;
    Vec2 m_textureCoordinate;

    template <typename V>
    void Visit(V& visitor) const
    {
        visitor.Visit(m_position);
        visitor.Visit(m_color);
        visitor.Visit(m_textureCoordinate);
    }
};

namespace Render {

struct Model::ModelData
{
    std::vector<Vertex>      vertices;
    std::vector<uint32_t>    indices;
};

Model::Model()
    : m_model(new ModelData)
{
}

Model::~Model()
{
    delete m_model;
}

std::vector<VkVertexInputBindingDescription> Model::GetVertexBindingDescriptions()
{
    return Vulkan::GetBindings<Vertex>();
}

std::vector<VkVertexInputAttributeDescription> Model::GetVertexAttributeDescriptions()
{
    return Vulkan::GetAttributes<Vertex>();
}

void Model::Load(const char* fileName)
{
    tinyobj::attrib_t                attrib;
    std::vector<tinyobj::shape_t>    shapes;
    std::vector<tinyobj::material_t> materials;
    std::string                      warn, err;
    std::unordered_map<Vertex, uint32_t, Vulkan::Hash<Vertex>, Vulkan::IsEqual<Vertex>> uniqueVertices {};

    if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, fileName))
    {
        throw std::runtime_error(warn + err);
    }

    for (const auto& shape : shapes)
    {
        for (const auto& index : shape.mesh.indices)
        {
            Vertex vertex {};
            vertex.m_position.value = { attrib.vertices[3 * index.vertex_index + 0],
                                        attrib.vertices[3 * index.vertex_index + 1],
                                        attrib.vertices[3 * index.vertex_index + 2] };
            vertex.m_textureCoordinate.value = { attrib.texcoords[2 * index.texcoord_index + 0],
                                                    1.0f - attrib.texcoords[2 * index.texcoord_index + 1] };
            vertex.m_color.value = { 1.0f, 1.0f, 1.0f };

            if (uniqueVertices.count(vertex) == 0)
            {
                uniqueVertices[vertex] = static_cast<uint32_t>(m_model->vertices.size());
                m_model->vertices.push_back(vertex);
            }
            m_model->indices.push_back(uniqueVertices[vertex]);
        }
    }
}

void Model::GetVertexData(const uint8_t* &data, uint64_t& size)
{
    data = (const uint8_t*)m_model->vertices.data();
    size = (uint64_t)(sizeof(Vertex) * m_model->vertices.size());
}

void Model::GetIndexData(const uint8_t* &data, uint64_t& size)
{
    data = (const uint8_t*)m_model->indices.data();
    size = (uint64_t)(sizeof(uint32_t) * m_model->indices.size());
}

uint32_t Model::GetIndexCount()
{
    return (uint32_t)m_model->indices.size();
}

} // Render namespace
