#include "ModelComponent.h"
#include "stb_image.h"
#include "../Engine.h"


#include <iostream>
#include <sstream>
#include <map>
#include <algorithm>
#include "../utils/AssetManager.h"
#include <glm/gtc/type_ptr.hpp>

// Constructor: initialize default values.
ModelComponent::ModelComponent()
    : modelPath("./assets/models/sponza.obj"),
      diffuseColor(1.0f, 1.0f, 1.0f),
      specularColor(1.0f, 1.0f, 1.0f),
      shininess(32.0f)
{
    // Load the model on construction.
    LoadModel(modelPath);
}

ModelComponent::~ModelComponent() {
    // Clean up each submesh's OpenGL buffers.
    for (auto &mesh : meshes) {
        if (mesh.VAO) glDeleteVertexArrays(1, &mesh.VAO);
        if (mesh.VBO) glDeleteBuffers(1, &mesh.VBO);
        if (mesh.EBO) glDeleteBuffers(1, &mesh.EBO);
        // Do not delete mesh.diffuseTexture here since it's managed by the Asset Manager.
        // if(mesh.diffuseTexture) glDeleteTextures(1, &mesh.diffuseTexture);
    }
}


void ModelComponent::SetupMesh(SubMesh &mesh) {
    glGenVertexArrays(1, &mesh.VAO);
    glGenBuffers(1, &mesh.VBO);
    glGenBuffers(1, &mesh.EBO);
    
    glBindVertexArray(mesh.VAO);
    
    glBindBuffer(GL_ARRAY_BUFFER, mesh.VBO);
    glBufferData(GL_ARRAY_BUFFER, mesh.vertices.size() * sizeof(Vertex), &mesh.vertices[0], GL_STATIC_DRAW);
    
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices.size() * sizeof(GLuint), &mesh.indices[0], GL_STATIC_DRAW);
    
    // Vertex attributes:
    // Positions.
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
    glEnableVertexAttribArray(0);
    // Normals.
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Normal));
    glEnableVertexAttribArray(1);
    // TexCoords.
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, TexCoords));
    glEnableVertexAttribArray(2);
    // Tangents.
    glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, Tangent));
    glEnableVertexAttribArray(3);
    
    glBindVertexArray(0);
}

bool ModelComponent::LoadDiffuseTexture(const std::string &filepath, GLuint &textureID) {
    int w, h, channels;
    unsigned char* data = stbi_load(filepath.c_str(), &w, &h, &channels, 0);
    if (!data) {
        std::cout << "Failed to load diffuse texture: " << filepath << std::endl;
        return false;
    }
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);
    GLenum format = (channels == 3) ? GL_RGB : GL_RGBA;
    glTexImage2D(GL_TEXTURE_2D, 0, format, w, h, 0, format, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    stbi_image_free(data);
    return true;
}

bool ModelComponent::LoadNormalTexture(const std::string &filepath) {
    // Similar to LoadDiffuseTexture but for normalTexture; not used in this submesh example.
    return true;
}

// Loads a model using tiny_obj_loader and groups faces by material.
bool ModelComponent::LoadModel(const std::string &filepath) {
    // Use the AssetManager to load the raw model data.
    ModelAsset asset;
    if (!AssetManager::Get().LoadModel(filepath, asset))
        return false;

    // Save the model path.
    modelPath = asset.modelPath;

    // Clear any existing submeshes.
    meshes.clear();

    // Copy the cached raw submesh data.
    // Note: We do not copy OpenGL buffers (VAO, VBO, EBO) since they are instance-specific.
    for (const SubMesh &cachedMesh : asset.meshes) {
        SubMesh mesh = cachedMesh;
        // Ensure buffer IDs are reset.
        mesh.VAO = mesh.VBO = mesh.EBO = 0;
        meshes.push_back(mesh);
    }

    for (auto &mesh : meshes) {
        SetupMesh(mesh);
    }

    return true;
}


void ModelComponent::Draw() {
    // Render each submesh.
    for (auto &mesh : meshes) {
        // Bind the diffuse texture if available.
        if (mesh.diffuseTexture) {
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, mesh.diffuseTexture);
        }
        glBindVertexArray(mesh.VAO);
        glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(mesh.indices.size()), GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
    }
}