#include "render.h"
#include "engine_global.h"
#include "scene.h"
#include "resources.h"
#include "utils/debug.h"
#include "utils/list.h"

static List g_render_objects = {}; //!< List of all registered rendering objects
static List g_shaders = {};
static Matrix g_light_direction = {};

void InitializeRenderingSubsystem() {
    g_render_objects = list_from_type(Renderable); // Allocate list of renderable typeclass wrappers
    g_light_direction = QuaternionToMatrix(QuaternionFromEuler(-65.f, 45.f, 0.f));
    g_shaders = list_from_type(ShaderResource);
}

void CleanupRenderingSubsystem() {
    list_empty(&g_render_objects); // deallocate renderable list, rendering subsystem is not responsible for freeing underlying memory
}

void AddRenderable(Renderable renderable) {
    ASSERT_RETURN(list_find(&g_render_objects, &renderable) == g_render_objects.len,, "AddRenderable: Argument is already registered, cannot register twice");
    list_add(&g_render_objects, &renderable);
}

void RemoveRenderable(Renderable renderable) {
    size_t idx = list_find(&g_render_objects, &renderable);
    ASSERT_RETURN(idx != g_render_objects.len,, "RemoveRenderable: Argument is not registered, cannot unregister.");
    list_erase(&g_render_objects, idx);
}

void SetLightDirection(Transform transform) {
    g_light_direction = TransformGetMatrix(transform);
}

static
void Internal_UpdateShader(Shader *shader) {
    int const light_dir_loc = GetShaderLocation(*shader, "lightDirection");
    if(light_dir_loc != -1)
        SetShaderValue(*shader, light_dir_loc, &MATRIX_FORWARD(g_light_direction), SHADER_UNIFORM_VEC3);
}

void RenderNextFrame() {
    list_foreach(ShaderResource *,shader, &g_shaders)
        Internal_UpdateShader(shader->resource);
    BeginDrawing();
    ClearBackground(DARKGRAY);
    // get current camera
    CameraNode *camera = GetMainScene()->main_camera;
    // can't draw anything if there is no camera
    if(camera != NULL) {
        // render 3D scene
        BeginMode3D(CameraNodeGetCamera(camera));
        list_foreach(Renderable *,object, &g_render_objects)
            object->tc->draw(object->data);
        DrawGrid(100, 1.f); // TODO: Remove this (or make it a scene node entity)
        EndMode3D();
    }
    DrawFPS(20, 20);
    EndDrawing();
}

void Internal_OnShaderLoaded(ShaderResource resource) {
    list_add(&g_shaders, &resource);
    int light_direction_loc = GetShaderLocation(*resource.resource, "lightDirection");
    int ambient_level_loc = GetShaderLocation(*resource.resource, "ambient");
    SetShaderValue(*resource.resource, light_direction_loc, &MATRIX_FORWARD(g_light_direction), SHADER_UNIFORM_VEC3);
    SetShaderValue(*resource.resource, ambient_level_loc, &WHITE, SHADER_UNIFORM_VEC4);
}

void Internal_OnShaderUnloaded(ShaderResource resource) {
    size_t const found = list_find(&g_shaders, &resource);
    if(found != g_shaders.len)
        list_erase(&g_shaders, found);
}
