#include "rse_graphics.h"

#include <SDL3/SDL_error.h>
#include <SDL3/SDL_log.h>
#include <SDL3/SDL_render.h>
#include <cglm/util.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <vulkan/vulkan_core.h>

#include "descriptor_builder.h"
#include "font_manager.h"
#include "graphics_context.h"
#include "mesh_controller.h"
#include "pipeline_builder.h"
#include "src/renderer.h"
#include "src/vulkan_buffers.h"
#include "utilities/commons.h"
#include "utilities/errors_common.h"
#include "vulkan_base.h"
#include "vulkan_commons.h"
#include "vulkan_image.h"
#include "window.h"

struct rse_graphics_context_t
{
    struct graphics_context_t* context;
};

#define NS_TO_S(time) (time/1000000000)
static void model_view_projection_update(struct graphics_context_t* context)
{
    vec3 eye = {0};
    vec3 center = {0};
    vec3 up = {0};
    vec3 rotation_vec = {0.0f, 0.0f, 1.0f};
    float fovy;
    float z_near;
    float z_far;
    double diff_time = 0.0;
    struct timespec now = {0};
    static struct timespec last_time = {0};

    eye[2] = -5.0f;

    up[1] = 1.0f;

    fovy = glm_rad(45.0f);
    z_near = 0.1f;
    z_far = 20.0f;

    if (last_time.tv_nsec == 0) {
        timespec_get(&last_time, TIME_UTC);
    }

    timespec_get(&now, TIME_UTC);

    diff_time = NS_TO_S((double)(now.tv_nsec - last_time.tv_nsec)) + (now.tv_sec - last_time.tv_sec);

    glm_mat4_identity(context->mvp_data.model);

    glm_rotate(context->mvp_data.model, diff_time * glm_rad(90.0f), rotation_vec);
    glm_lookat(eye, center, up, context->mvp_data.view);
    glm_perspective(
        fovy,
        context->swapchain_data.swapchain_extent.width / (float)context->swapchain_data.swapchain_extent.height,
        z_near,
        z_far,
        context->mvp_data.proj);
}

static rse_err_t render_static_mesh(struct graphics_context_t* context, uint32_t renderer_id)
{
    VkCommandBuffer command_buffer = context->vulkan_handles.command_buffers[context->current_frame];
    VkDeviceSize offsets[] = {0};
    uint32_t mvp_offset = 0;
    uint32_t mvp_size = sizeof(struct mvp_data_t);

    (void)renderer_id;

    vkCmdPushConstants(command_buffer,
                       context->pipelines_data.pipeline_layouts[0],
                       VK_SHADER_STAGE_VERTEX_BIT,
                       mvp_offset,
                       mvp_size,
                       &context->mvp_data);
    model_view_projection_update(context);
    vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, context->pipelines_data.pipelines[0]);
    vkCmdBindIndexBuffer(command_buffer,
                         context->render_targets[RENDER_TARGET_3D].index_buffer.buffer,
                         0,
                         VK_INDEX_TYPE_UINT16);
    vkCmdBindVertexBuffers(command_buffer,
                           0,
                           1,
                           &context->render_targets[RENDER_TARGET_3D].vertex_buffer.buffer,
                           offsets);
    /* FIXME: Currently we can only bind one descriptor set */
    vkCmdBindDescriptorSets(command_buffer,
                            VK_PIPELINE_BIND_POINT_GRAPHICS,
                            context->pipelines_data.pipeline_layouts[0],
                            0,
                            1,
                            &context->descriptor_data.descriptor_sets[0],
                            0,
                            NULL);

    vkCmdDrawIndexedIndirect(command_buffer,
                             context->render_targets[RENDER_TARGET_3D].draw_indirect_command_buffer.buffer,
                             0,
                             context->mesh_data.mesh_count,
                             sizeof(VkDrawIndexedIndirectCommand));

    return RSE_ERROR_NO_ERROR;
}

rse_err_t rse_graphics_init(struct rse_graphics_context_t** context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;
    struct rse_graphics_context_t* ctx_ptr = NULL;

    if (*context != NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Graphics already initialized!");
        return RSE_ERROR_ALREADY_INITIALIZED;
    }

    rse_malloc(*context, sizeof(struct rse_graphics_context_t));
    ctx_ptr = *context;
    rse_malloc(ctx_ptr->context, sizeof(struct graphics_context_t));
    rse_memset(ctx_ptr->context, 0, sizeof(struct graphics_context_t));

    STATUS_CHECK(window_init(&ctx_ptr->context->window_handle, &ctx_ptr->context->is_framebuffer_resized));
    STATUS_CHECK(vulkan_init(ctx_ptr->context));

    return status;
}

#define TEXTURES_COUNT (2U)
rse_err_t rse_graphics_test_function(struct rse_graphics_context_t* rse_context)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;
    struct graphics_context_t* context = rse_context->context;
    rse_id_t textures[TEXTURES_COUNT] = {0};
    struct pipeline_t pipeline;
    rse_id_t descriptor_set_handle = 0;
    struct descriptor_set_layout_bindings_t layout_bindings = {0};
    struct pipeline_infos_t pipeline_infos = {0};
    rse_id_t mesh_id_1, mesh_id_2 = 0;
    rse_id_t font_id = 0;
    rse_id_t static_mesh_renderer = 0U;
    rse_id_t vertex_shader_id, fragment_shader_id;

    STATUS_CHECK(texture_load_from_file(context, "../../test_image.jpg", &textures[0]));
    STATUS_CHECK(texture_load_from_file(context, "../../test_image_2.jpg", &textures[1]));

    STATUS_CHECK(fonts_init());
    STATUS_CHECK(fonts_load_from_file(context, "../../NotoSansMono-Regular.ttf", &font_id));
    STATUS_CHECK(fonts_set_font_size(context, font_id, 16));

    // FIXME: Temporary array of vertices, for testing purposes
    struct vertex_t vertices[] = {{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
                                  {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
                                  {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
                                  {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}};

    struct vertex_t vertices2[] = {{{-0.5f, 0.5f, 0.0f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}},
                                   {{0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}},
                                   {{0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}},
                                   {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}};

    // FIXME: Temporary array of vertices, for testing purposes
    uint16_t indices[] = {0, 1, 2, 2, 3, 0};

    STATUS_CHECK(mesh_create(context, vertices, indices, 4, 6, &mesh_id_1));
    if (RSE_ERROR_NO_ERROR !=
        mesh_create_instance(context,
                             mesh_id_1,
                             (struct instance_data_t){{0.0f, 0.0f, 2.4f}, {0.0f, 0.0f, 0.0}, 1.0f, textures[1]})) {
        exit(1);
    }

    if (RSE_ERROR_NO_ERROR !=
        mesh_create_instance(
            context,
            mesh_id_1,
            (struct instance_data_t){{-0.5f, 0.0f, -1.0f}, {0.0f, 0.0f, glm_rad(10.0f)}, 1.0f, textures[0]})) {
        exit(1);
    }

    STATUS_CHECK(mesh_create(context, vertices2, indices, 4, 6, &mesh_id_2));

    if (RSE_ERROR_NO_ERROR !=
        mesh_create_instance(
            context,
            mesh_id_2,
            (struct instance_data_t){{1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, glm_rad(0.0f)}, 1.0f, textures[0]})) {
        exit(1);
    }

    STATUS_CHECK(buffers_update(context));

    STATUS_CHECK(sampler_create(context, &context->vulkan_handles.sampler));

    STATUS_CHECK(descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1));
    STATUS_CHECK(descriptor_pool_add_type(context, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, TEXTURES_COUNT));
    STATUS_CHECK(descriptor_pool_initialize(context));

    /* Normal rendering */
    STATUS_CHECK(descriptor_create_new_set(context, &descriptor_set_handle));
    STATUS_CHECK(
        descriptor_add_layout(&layout_bindings, 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT));
    STATUS_CHECK(descriptor_add_layout(&layout_bindings,
                                       1,
                                       VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                       TEXTURES_COUNT,
                                       VK_SHADER_STAGE_FRAGMENT_BIT));
    STATUS_CHECK(descriptor_set_finish(context, &layout_bindings, descriptor_set_handle));
    STATUS_CHECK(descriptor_build_sets(context));

    STATUS_CHECK(descriptor_attach_buffer(context,
                                          descriptor_set_handle,
                                          &context->render_targets[RENDER_TARGET_3D].instance_buffer,
                                          0,
                                          VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER));

    STATUS_CHECK(descriptor_attach_images(context,
                                          descriptor_set_handle,
                                          textures,
                                          TEXTURES_COUNT,
                                          context->vulkan_handles.sampler,
                                          1));

    /* Initialize pipeline */
    pipeline_builder_init(&pipeline);

    shader_create_module(context, &vertex_shader_id, "shader.vert.num", VK_SHADER_STAGE_VERTEX_BIT);
    pipeline_add_shader_stage(context, &pipeline, vertex_shader_id);
    shader_create_module(context, &fragment_shader_id, "solid_objects.frag.num", VK_SHADER_STAGE_FRAGMENT_BIT);
    pipeline_add_shader_stage(context, &pipeline, fragment_shader_id);

    pipeline_add_vertex_input_binding(&pipeline, 0, sizeof(struct vertex_t), VK_VERTEX_INPUT_RATE_VERTEX);

    pipeline_add_vertex_input_attribute(&pipeline, 0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, pos));
    pipeline_add_vertex_input_attribute(&pipeline, 0, 1, VK_FORMAT_R32G32B32_SFLOAT, offsetof(struct vertex_t, color));
    pipeline_add_vertex_input_attribute(&pipeline,
                                        0,
                                        2,
                                        VK_FORMAT_R32G32_SFLOAT,
                                        offsetof(struct vertex_t, tex_coords));

    pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_VIEWPORT);
    pipeline_add_dynamic_state(&pipeline, VK_DYNAMIC_STATE_SCISSOR);

    pipeline_add_push_constants(&pipeline, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(struct mvp_data_t));
    pipeline_add_descriptor_sets(context, &pipeline, descriptor_set_handle);

    pipeline_add(context, &pipeline, &pipeline_infos);

    pipelines_build(context, &pipeline_infos);

    renderer_get_new_id(context, &static_mesh_renderer);

    renderer_set_render_function(context, static_mesh_renderer, render_static_mesh);

    return RSE_ERROR_NO_ERROR;
}

int rse_graphics_run(void* arg)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;
    struct rse_graphics_context_t* rse_context = (struct rse_graphics_context_t*)arg;
    struct graphics_context_t* context = rse_context->context;

    STATUS_CHECK(vulkan_run(context));
    STATUS_CHECK(window_loop(context));

    vulkan_deinit(context);

    window_terminate(context->window_handle);
    rse_free(context->debug_overlay.pixels);
    rse_free(context);
    rse_free(rse_context);

    return status;
}
