//
// Created by slinky on 5/7/26.
//

#include "Scene.h"

#include "Components.h"
#include "fmt/format.h"

Scene::Scene() {
    root = _registry.create();
    attach_component<Components::Transform>(root);
    attach_component<Components::Relationship>(root);
}

entt::entity Scene::create_game_object() {
    return create_game_object(root);
}

entt::entity Scene::create_game_object(entt::entity parent) {
    if (parent == entt::null) {
        parent = root;
    }

    const entt::entity entity = _registry.create();
    attach_component<Components::Transform>(entity);
    attach_component<Components::Relationship>(entity);
    auto&[name] = attach_component<Components::Tag>(entity);
    name = fmt::format("entity {0}", static_cast<int>(entity));

    add_child(parent, entity);

    return entity;
}

void Scene::update_transforms() {
    std::vector<entt::entity> children = get_children(root);
    for (auto child: children) {
        update_transforms(child);
    }
}

void Scene::update_transforms(entt::entity entity) {
    auto& transform = fetch_component<Components::Transform>(entity);

    if (!transform.dirty)
        return;

    glm::mat4 translation =
        glm::translate(glm::mat4(1.0f), transform.position);

    glm::mat4 rotationX =
        glm::rotate(glm::mat4(1.0f), transform.rotation.x, glm::vec3(1,0,0));

    glm::mat4 rotationY =
        glm::rotate(glm::mat4(1.0f), transform.rotation.y, glm::vec3(0,1,0));

    glm::mat4 rotationZ =
        glm::rotate(glm::mat4(1.0f), transform.rotation.z, glm::vec3(0,0,1));

    glm::mat4 scaling =
        glm::scale(glm::mat4(1.0f), transform.scale);

    glm::mat4 localModel =
        translation *
        rotationZ *
        rotationY *
        rotationX *
        scaling;

    auto& rel = fetch_component<Components::Relationship>(entity);
    if (rel.parent != entt::null) {
        auto& parentTransform = fetch_component<Components::Transform>(rel.parent);
        transform.model = parentTransform.model * localModel;
    } else {
        transform.model = localModel;
    }

    transform.dirty = false;

    std::vector<entt::entity> children = get_children(entity);
    for (auto child: children) {
        update_transforms(child);
    }
}

void Scene::draw_scene(ShaderProgram *program) {
    program->bind();
    program->setMat4("projection", activeCamera->projection());
    program->setMat4("view", activeCamera->view());
    program->setVec3("viewPosition", activeCamera->position());

    auto dirLights = _registry.view<Components::DirectionalLight>();
    for (const auto e : dirLights) {
        auto dirLight = dirLights.get<Components::DirectionalLight>(e);
        program->setVec3("lightDirection",  dirLight.direction);
        program->setVec3("lightAmbient",  dirLight.ambient);
        program->setVec3("lightDiffuse", dirLight.diffuse);
        program->setVec3("lightSpecular", dirLight.specular);
        break;
    }

    auto view = _registry.view<Components::Transform, Components::Drawable>();
    for (const auto e : view) {
        const auto& transform = view.get<Components::Transform>(e);
        program->setMat4("model", transform.model);

        const auto& drawable = view.get<Components::Drawable>(e);
        const Model* model = drawable.model.get();
        for (const auto& mesh: drawable.model->meshes) {
            unsigned int materialId = mesh->materialId;
            if (materialId >= model->materials.size()) {
                materialId = 0;
            }

            const std::shared_ptr<Material> mat = model->materials[materialId];

            program->setVec3("phongAmbient",  mat->phong.ambient);
            program->setVec3("phongDiffuse",  mat->phong.diffuse);
            program->setVec3("phongSpecular", mat->phong.specular);
            program->setFloat("phongShininess", mat->phong.shininess);

            glActiveTexture(GL_TEXTURE0);
            const auto diffuse = mat->diffuse;
            diffuse->bind();
            program->setInt("diffuseMap", 0);

            glActiveTexture(GL_TEXTURE1);
            const auto specular = mat->specular;
            specular->bind();
            program->setInt("specularMap", 1);

            glBindVertexArray(mesh.get()->vao);
            glDrawElements(GL_TRIANGLES, mesh->numIndices, GL_UNSIGNED_INT, 0);
        }
    }
}

entt::entity Scene::get_root() const {
    return root;
}

std::vector<entt::entity> Scene::get_children(entt::entity parent) {
    std::vector<entt::entity> children{};
    auto child = fetch_component<Components::Relationship>(parent).first_child;
    while (child != entt::null) {
        children.push_back(child);
        const auto& relationship = fetch_component<Components::Relationship>(child);
        child = relationship.next_sibling;
    }
    return children;
}

void Scene::add_child(entt::entity parent, entt::entity child) {
    auto& parentRelationship = fetch_component<Components::Relationship>(parent);

    if (parentRelationship.first_child == entt::null) {
        parentRelationship.first_child = child;
        auto& childRelationship = fetch_component<Components::Relationship>(child);
        childRelationship.parent = parent;
        return;
    }

    entt::entity lastSibling = parentRelationship.first_child;
    auto* lastSiblingRelationship = &fetch_component<Components::Relationship>(lastSibling);
    while (lastSiblingRelationship->next_sibling != entt::null) {
        lastSibling = lastSiblingRelationship->next_sibling;
        lastSiblingRelationship = &fetch_component<Components::Relationship>(lastSibling);
    }
    lastSiblingRelationship->next_sibling = child;

    auto& childRelationship = fetch_component<Components::Relationship>(child);
    childRelationship.prev_sibling = lastSibling;
    childRelationship.next_sibling = entt::null;
    childRelationship.parent = parent;
}

void Scene::set_active_camera(Camera *camera) {
    activeCamera = camera;
}
