/**
 * @file mesh_controller.h
 * @author Piotr Krygier (piotrkrygier@everyonencancode.xyz)
 * @brief Graphics context for Red Scarf Engine
 * @version 0.1
 * @date 2025-09-23
 *
 * @copyright Copyright (c) 2025
 *
 */
#include <SDL3/SDL_log.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>

#include "src/graphics_context.h"
#include "src/vulkan_commons.h"
#include "utilities/cgltf.h"
#include "utilities/commons.h"
#include "utilities/entity.h"
#include "utilities/errors_common.h"
#include "utilities/file_utils.h"
#include "vulkan_buffers.h"

rse_err_t mesh_create(struct graphics_context_t* context,
                      struct vertex_t* vertices,
                      uint16_t* indices,
                      size_t vertices_count,
                      size_t indices_count,
                      rse_id_t* mesh_id)
{
    rse_err_t status = RSE_ERROR_NO_ERROR;

    if (mesh_id == NULL) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Provided mesh id handle is NULL");
        return RSE_ERROR_NULL_POINTER;
    }

    *mesh_id = context->mesh_data.mesh_count;

    if (context->mesh_data.mesh_count > MAX_MESH_NUMBER) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Could not allocate new mesh, reached maximum.");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    STATUS_CHECK(add_vertices(context, *mesh_id, vertices_count, vertices, indices_count, indices));
    context->mesh_data.mesh_count++;

    return status;
}

rse_err_t mesh_create_from_file(struct graphics_context_t* context, const char* file_name, rse_id_t* mesh_id)
{
    struct vertex_t* vertices = NULL;
    uint16_t* indices = NULL;
    size_t indices_count = 0U;
    size_t mesh_index = 0U;
    size_t primitive_index = 0U;
    size_t attribute_index = 0U;
    cgltf_mesh* mesh = NULL;
    cgltf_primitive* primitive = NULL;
    cgltf_attribute* attribute = NULL;
    size_t* position_data_offset = NULL;
    size_t* color_data_offset = NULL;
    size_t* texture_coord_data_offset = NULL;
    size_t* indices_data_offset = 0U;
    size_t data_count = 0U;
    size_t data_index = 0U;
    size_t tmp_offset = 0U;
    uint8_t* bin = NULL;

    (void)context;
    (void)mesh_id;

    cgltf_data* data = NULL;
    rse_err_t status = RSE_ERROR_NO_ERROR;

    STATUS_CHECK(file_load_gltf(file_name, &data));

    SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Attribute name: %s", data->meshes->primitives[0].attributes[3].name);
    SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Accessor: %ld", data->meshes->primitives[0].attributes[3].data->count);

    for (mesh_index = 0U; mesh_index < data->meshes_count; ++mesh_index) {
        mesh = &data->meshes[mesh_index];

        for (primitive_index = 0U; primitive_index < mesh->primitives_count; ++primitive_index) {
            primitive = &mesh->primitives[primitive_index];

            for (attribute_index = 0; attribute_index < primitive->attributes_count; ++attribute_index) {
                attribute = &primitive->attributes[attribute_index];

                if (primitive->indices == NULL) {
                    SDL_LogCritical(SDL_LOG_CATEGORY_GPU,
                                    "No indices for mesh found. Currently only indexed drawing is supported");
                    return RSE_ERROR_INVALID_PARAM;
                } else {
                    rse_malloc(indices_data_offset, sizeof(size_t));
                    *indices_data_offset = primitive->indices->buffer_view->offset;
                    indices_count = primitive->indices->count;
                    rse_malloc(indices, (sizeof(uint16_t) * indices_count));
                }

                tmp_offset = attribute->data->buffer_view->offset;

                switch (attribute->type) {
                    case cgltf_attribute_type_position:
                        rse_malloc(position_data_offset, sizeof(size_t));
                        *position_data_offset = tmp_offset;
                        data_count = attribute->data->count;
                        break;
                    case cgltf_attribute_type_color:
                        rse_malloc(color_data_offset, sizeof(size_t));
                        *color_data_offset = tmp_offset;
                        break;
                    case cgltf_attribute_type_texcoord:
                        rse_malloc(texture_coord_data_offset, sizeof(size_t));
                        *texture_coord_data_offset = tmp_offset;
                        break;
                    default:
                        break;
                }
            }
        }

        if (position_data_offset == NULL) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failed to retrieve position data for mesh %s", file_name);
            status = RSE_ERROR_INVALID_PARAM;

            goto exit;
        }

        if (data_count == 0) {
            SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Failure: data count for mesh is 0");
            status = RSE_ERROR_INVALID_PARAM;

            goto exit;
        }

        rse_malloc(vertices, sizeof(struct vertex_t) * data_count);
        bin = (uint8_t*)data->bin;

        for (data_index = 0U; data_index < data_count; ++data_index) {
            rse_memcpy(vertices[data_index].pos,
                       (bin + *position_data_offset + (sizeof(vec3) * data_index)),
                       sizeof(vec3));
            if (color_data_offset != NULL) {
                rse_memcpy(vertices[data_index].color,
                           (bin + *color_data_offset + (sizeof(vec3) * data_index)),
                           sizeof(vec3));
            } else {
                vertices[data_index].color[0] = 1.0;
                vertices[data_index].color[1] = 1.0;
                vertices[data_index].color[2] = 1.0;
            }

            if (texture_coord_data_offset != NULL) {
                rse_memcpy(vertices[data_index].tex_coords,
                           (bin + *texture_coord_data_offset + (sizeof(vec2) * data_index)),
                           sizeof(vec2));
            }
        }

        rse_memcpy(indices, (bin + *indices_data_offset), (sizeof(uint16_t) * indices_count));

        STATUS_CHECK(mesh_create(context, vertices, indices, data_count, indices_count, mesh_id));
    }

exit:
    rse_free(position_data_offset);
    rse_free(color_data_offset);
    rse_free(texture_coord_data_offset);
    rse_free(vertices);
    cgltf_free(data);

    return status;
}

rse_err_t mesh_create_instance(struct graphics_context_t* context,
                               entity_t entity,
                               struct instance_data_t instance_data)
{
    if (context->mesh_data.mesh_bucket[entity].instances_count >= MAX_INSTANCE_NUMBER) {
        SDL_LogCritical(SDL_LOG_CATEGORY_GPU, "Could not allocate new mesh, reached maximum.");
        return RSE_ERROR_INTERNAL_ERROR;
    }

    return mesh_add_instance(context, entity, &instance_data);
}
