#include "logging.hpp"
#include "scene/scene_camera.hpp"
#include <pch.hpp>

#include <renderer/render_command.hpp>
#include <renderer/uniform_buffer.hpp>
#include <renderer/editor_camera.hpp>
#include <renderer/vertex_array.hpp>
#include <renderer/renderer3d.hpp>
#include <renderer/shader.hpp>
#include <renderer/buffer.hpp>
#include <instrumentor.hpp>
#include <ref_scope.hpp>

#include <glm/ext/quaternion_common.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/trigonometric.hpp>
#include <glm/fwd.hpp>
#include <cstdint>

#include <glad/glad.h>

namespace OpenEngine {

    // SHOULD BE MOVED ==================================

    Ref<Mesh> CreateQuad(uint32_t id, bool back_face)
    {
        OE_PROFILE_FUNCTION();
        OE_CORE_DEBUG("Creating quad with id {}.", id);

        std::vector<MeshVertex> vertices;
        std::vector<uint32_t> indices;
        if (back_face) {
            vertices = {
                // Front face (normal 0, 0, 1)
                {{-0.5f, -0.5f,  0.0f}, {0,0,1}, {0,0}},
                {{-0.5f,  0.5f,  0.0f}, {0,0,1}, {0,1}},
                {{ 0.5f,  0.5f,  0.0f}, {0,0,1}, {1,1}},
                {{ 0.5f, -0.5f,  0.0f}, {0,0,1}, {1,0}},

                // Back face (normal 0, 0, -1)
                {{-0.5f, -0.5f, -0.0f}, {0,0,-1}, {0,0}},
                {{-0.5f,  0.5f, -0.0f}, {0,0,-1}, {0,1}},
                {{ 0.5f,  0.5f, -0.0f}, {0,0,-1}, {1,1}},
                {{ 0.5f, -0.5f, -0.0f}, {0,0,-1}, {1,0}}
            };

            indices = {
                // Front face (z=0.5)
                0, 2, 1, 0, 3, 2,
                // Back face (z=-0.5)
                4, 5, 6,  4, 6, 7
            };
        } else {
            vertices = {
                // Front face (normal 0, 0, 1)
                {{-0.5f, -0.5f,  0.0f}, {0,0,1}, {0,0}},
                {{-0.5f,  0.5f,  0.0f}, {0,0,1}, {0,1}},
                {{ 0.5f,  0.5f,  0.0f}, {0,0,1}, {1,1}},
                {{ 0.5f, -0.5f,  0.0f}, {0,0,1}, {1,0}}
            };

            indices = {
                // Front face (z=0.5)
                0, 2, 1, 0, 3, 2
            };
        }

        return CreateMesh(vertices, indices);
    }

    Ref<Mesh> CreateCube(uint32_t id)
    {
        OE_PROFILE_FUNCTION();
        OE_CORE_DEBUG("Creating cube with id {}.", id);
        std::vector<MeshVertex> vertices = {
            // Front face (normal 0, 0, 1)
            {{-0.5f, -0.5f,  0.5f}, {0,0,1}, {0,0}},
            {{-0.5f,  0.5f,  0.5f}, {0,0,1}, {0,1}},
            {{ 0.5f,  0.5f,  0.5f}, {0,0,1}, {1,1}},
            {{ 0.5f, -0.5f,  0.5f}, {0,0,1}, {1,0}},

            // Back face (normal 0, 0, -1)
            {{-0.5f, -0.5f, -0.5f}, {0,0,-1}, {0,0}},
            {{-0.5f,  0.5f, -0.5f}, {0,0,-1}, {0,1}},
            {{ 0.5f,  0.5f, -0.5f}, {0,0,-1}, {1,1}},
            {{ 0.5f, -0.5f, -0.5f}, {0,0,-1}, {1,0}},

            // Left face (normal -1, 0, 0)
            {{-0.5f, -0.5f, -0.5f}, {-1,0,0}, {0,0}},
            {{-0.5f,  0.5f, -0.5f}, {-1,0,0}, {0,1}},
            {{-0.5f,  0.5f,  0.5f}, {-1,0,0}, {1,1}},
            {{-0.5f, -0.5f,  0.5f}, {-1,0,0}, {1,0}},

            // Right face (normal 1, 0, 0)
            {{ 0.5f, -0.5f, -0.5f}, {1,0,0}, {0,0}},
            {{ 0.5f,  0.5f, -0.5f}, {1,0,0}, {0,1}},
            {{ 0.5f,  0.5f,  0.5f}, {1,0,0}, {1,1}},
            {{ 0.5f, -0.5f,  0.5f}, {1,0,0}, {1,0}},

            // Top face (normal 0, 1, 0)
            {{-0.5f,  0.5f, -0.5f}, {0,1,0}, {0,0}},
            {{-0.5f,  0.5f,  0.5f}, {0,1,0}, {0,1}},
            {{ 0.5f,  0.5f,  0.5f}, {0,1,0}, {1,1}},
            {{ 0.5f,  0.5f, -0.5f}, {0,1,0}, {1,0}},

            // Bottom face (normal 0, -1, 0)
            {{-0.5f, -0.5f, -0.5f}, {0,-1,0}, {0,0}},
            {{-0.5f, -0.5f,  0.5f}, {0,-1,0}, {0,1}},
            {{ 0.5f, -0.5f,  0.5f}, {0,-1,0}, {1,1}},
            {{ 0.5f, -0.5f, -0.5f}, {0,-1,0}, {1,0}},
        };

        std::vector<uint32_t> indices = {
            // Front face (z=-0.5)
            0, 2, 1, 0, 3, 2,
            // Back face (z=0.5)
            4, 5, 6,  4, 6, 7,
            // Right face
            12, 13, 14, 12, 14, 15,
            // Left face
            8, 10, 9, 8, 11, 10,
            // Top face
            18, 16, 17, 18, 19, 16,
            // Bottom face
            20, 22, 21, 20, 23, 22
        };

        return CreateMesh(vertices, indices);
    }

    // ==================================================
    struct Renderer3DData
    {
        Ref<Shader> color_shader_3d;

        struct CameraData
        {
            glm::mat4 view_projection;
            glm::vec3 view_position;
        };
        CameraData camera_buffer;
        Ref<UniformBuffer> camera_uniform_buffer;
        
        struct TransformData
        {
            glm::mat4 transform;
        };
        TransformData transform_buffer;
        Ref<UniformBuffer> transform_uniform_buffer;

        Material material_buffer;
        Ref<UniformBuffer> material_uniform_buffer;

        Ref<UniformBuffer> id_uniform_buffer;
    };

    static Renderer3DData renderer_data;

    void Renderer3D::Init()
    {
        OE_PROFILE_FUNCTION();
        
        glEnable(GL_CULL_FACE);
        glCullFace(GL_BACK);

        renderer_data.color_shader_3d = Shader::Create("assets/shaders/color3d.glsl");
        renderer_data.camera_uniform_buffer = UniformBuffer::Create(sizeof(Renderer3DData::CameraData), 0);
        renderer_data.transform_uniform_buffer = UniformBuffer::Create(sizeof(Renderer3DData::TransformData), 1);
        renderer_data.material_uniform_buffer = UniformBuffer::Create(sizeof(Material), 2);
        renderer_data.id_uniform_buffer = UniformBuffer::Create(sizeof(uint32_t), 3);

        glEnable(GL_DEBUG_OUTPUT);
        glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
        glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity,
                    GLsizei length, const GLchar* message, const void* userParam) {
                OE_CORE_ERROR("GL: {0}", message);
                }, nullptr);
    }
    
    void Renderer3D::Shutdown()
    {
        renderer_data.color_shader_3d.reset();
        renderer_data.camera_uniform_buffer.reset();
        renderer_data.transform_uniform_buffer.reset();
        renderer_data.material_uniform_buffer.reset();
        renderer_data.id_uniform_buffer.reset();
    }
    
    void Renderer3D::BeginScene(const SceneCamera& camera, const glm::mat4& transform)
    {
        OE_PROFILE_FUNCTION();

        renderer_data.camera_buffer.view_projection = camera.GetProjection() * glm::inverse(transform);
        renderer_data.camera_buffer.view_position = transform[3];

		renderer_data.camera_uniform_buffer->SetData(&renderer_data.camera_buffer, sizeof(Renderer3DData::CameraData));
    }

    void Renderer3D::BeginScene(const EditorCamera& camera)
    {
        OE_PROFILE_FUNCTION();

        renderer_data.camera_buffer.view_projection = camera.GetViewProjection();
        renderer_data.camera_buffer.view_position = camera.GetPosition();

		renderer_data.camera_uniform_buffer->SetData(&renderer_data.camera_buffer, sizeof(Renderer3DData::CameraData));
    }

    void Renderer3D::EndScene()
    {

    }

    void Renderer3D::DrawMesh(const Ref<Mesh>& mesh, Material& material,
            const glm::mat4& transform)
    {
        OE_PROFILE_FUNCTION();

        renderer_data.color_shader_3d->Bind();

        renderer_data.transform_buffer.transform = transform;
        renderer_data.transform_uniform_buffer->SetData(&renderer_data.transform_buffer, sizeof(Renderer3DData::TransformData));

        renderer_data.material_buffer = material;
        renderer_data.material_uniform_buffer->SetData(&renderer_data.material_buffer, sizeof(Material));

        mesh->vertex_array->Bind();

        RenderCommand::DrawIndexed(mesh->vertex_array, mesh->indices.size());
    }

    void Renderer3D::DrawMesh(const Ref<Mesh>& mesh, Material& material,
            const glm::mat4& transform,
            uint32_t entity_id)
    {
        OE_PROFILE_FUNCTION();

        renderer_data.color_shader_3d->Bind();

        renderer_data.transform_buffer.transform = transform;
        renderer_data.transform_uniform_buffer->SetData(&renderer_data.transform_buffer, sizeof(Renderer3DData::TransformData));

        renderer_data.material_buffer = material;
        renderer_data.material_uniform_buffer->SetData(&renderer_data.material_buffer, sizeof(Material));

        renderer_data.id_uniform_buffer->SetData(&entity_id, sizeof(uint32_t));

        mesh->vertex_array->Bind();

        RenderCommand::DrawIndexed(mesh->vertex_array, mesh->indices.size());
    }

    void Renderer3D::DrawModel(const Ref<Model3D>& model,
            const glm::mat4& transform,
            uint32_t entity_id)
    {
        OE_PROFILE_FUNCTION();

        renderer_data.color_shader_3d->Bind();

        renderer_data.transform_buffer.transform = transform;
        renderer_data.transform_uniform_buffer->SetData(&renderer_data.transform_buffer, sizeof(Renderer3DData::TransformData));

        for (auto& mesh : *model) {
            renderer_data.material_buffer = mesh.material;
            renderer_data.material_uniform_buffer->SetData(&renderer_data.material_buffer, sizeof(Material));

            renderer_data.id_uniform_buffer->SetData(&entity_id, sizeof(uint32_t));

            mesh.vertex_array->Bind();

            RenderCommand::DrawIndexed(mesh.vertex_array, mesh.indices.size());
        }
    }
}
