#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_engine_context_c.h"
#include "twn_camera.h"
#include "twn_types.h"

#include <SDL2/SDL.h>
#include <stb_ds.h>

#ifdef EMSCRIPTEN
#include <GLES2/gl2.h>
#else
#include <glad/glad.h>
#endif

#include <stddef.h>
#include <tgmath.h>


/* TODO: have a default initialized one */
Matrix4 camera_projection_matrix;
Matrix4 camera_look_at_matrix;


void render_queue_clear(void) {
    text_cache_reset_arena(&ctx.text_cache);
    
    /* since i don't intend to free the queues, */
    /* it's faster and simpler to just "start over" */
    /* and start overwriting the existing data */
    arrsetlen(ctx.render_queue_2d, 0);

    for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i)
        arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0);
}


/* rectangle */
void draw_rectangle(Rect rect, Color color) {
    RectPrimitive rectangle = {
        .rect = rect,
        .color = color,
    };

    Primitive2D primitive = {
        .type = PRIMITIVE_2D_RECT,
        .rect = rectangle,
    };

    arrput(ctx.render_queue_2d, primitive);
}


void draw_9slice(const char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) {
    const float bt = (float)border_thickness; /* i know! */
    const float bt2 = bt * 2; /* combined size of the two borders in an axis */


    Rect top_left = {
        .x = rect.x,
        .y = rect.y,
        .w = bt,
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, top_left),
        m_opt(texture_region, ((Rect) { 0, 0, bt, bt })),
        m_opt(color, color),
    );


    Rect top_center = {
        .x = rect.x + bt,
        .y = rect.y,
        .w = rect.w - bt2, /* here bt2 represents the top left and right corners */
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, top_center),
        m_opt(texture_region, ((Rect) { bt, 0, (float)texture_w - bt2, bt })),
        m_opt(color, color),
    );


    Rect top_right = {
        .x = rect.x + (rect.w - bt),
        .y = rect.y,
        .w = bt,
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, top_right),
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, 0, bt, bt })),
        m_opt(color, color),
    );


    Rect center_left = {
        .x = rect.x,
        .y = rect.y + bt,
        .w = bt,
        .h = rect.h - bt2, /* here bt2 represents the top and bottom left corners */
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, center_left),
        m_opt(texture_region, ((Rect) { 0, bt, bt, (float)texture_h - bt2 })),
        m_opt(color, color),
    );


    Rect center_right = {
        .x = rect.x + (rect.w - bt),
        .y = rect.y + bt,
        .w = bt,
        .h = rect.h - bt2, /* here bt2 represents the top and bottom right corners */
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, center_right),
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, bt, bt, (float)texture_h - bt2 })),
        m_opt(color, color),
    );


    Rect bottom_left = {
        .x = rect.x,
        .y = rect.y + (rect.h - bt),
        .w = bt,
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, bottom_left),
        m_opt(texture_region, ((Rect) { 0, (float)texture_h - bt, bt, bt })),
        m_opt(color, color),
    );


    Rect bottom_center = {
        .x = rect.x + bt,
        .y = rect.y + (rect.h - bt),
        .w = rect.w - bt2, /* here bt2 represents the bottom left and right corners */
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, bottom_center),
        m_opt(texture_region, ((Rect) { bt, (float)texture_h - bt, (float)texture_w - bt2, bt })),
        m_opt(color, color),
    );


    Rect bottom_right = {
        .x = rect.x + (rect.w - bt),
        .y = rect.y + (rect.h - bt),
        .w = bt,
        .h = bt,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, bottom_right),
        m_opt(texture_region, ((Rect) { (float)texture_w - bt, (float)texture_h - bt, bt, bt })),
        m_opt(color, color),
    );


    Rect center = {
        .x = rect.x + bt,
        .y = rect.y + bt,
        .w = rect.w - bt2,
        .h = rect.h - bt2,
    };

    m_sprite(
        m_set(path, texture_path),
        m_set(rect, center),
        m_opt(texture_region, ((Rect) { bt, bt, (float)texture_w - bt2, (float)texture_h - bt2 })),
        m_opt(color, color),
    );
}


static void render_2d(void) {
    use_2d_pipeline();

    const size_t render_queue_len = arrlenu(ctx.render_queue_2d);

    size_t batch_count = 0;

    for (size_t i = 0; i < render_queue_len; ++i) {
        const Primitive2D *current = &ctx.render_queue_2d[i];

        switch (current->type) {
            case PRIMITIVE_2D_SPRITE: {
                const struct SpriteBatch batch =
                    collect_sprite_batch(current, render_queue_len - i);

                /* TODO: what's even the point? just use OR_EQUAL comparison */
                set_depth_range((double)batch_count / UINT16_MAX, 1.0);
                render_sprites(current, batch);

                i += batch.size - 1; ++batch_count;
                break;
            }
            case PRIMITIVE_2D_RECT:
                render_rectangle(&current->rect);
                break;
            case PRIMITIVE_2D_CIRCLE:
                render_circle(&current->circle);
                break;
            case PRIMITIVE_2D_TEXT:
                render_text(&current->text);
                break;
            default:
                SDL_assert(false);
        }
    }
}


static void render_space(void) {
    /* nothing to do, abort */
    /* as space pipeline isn't used we can have fewer changes and initialization costs */
    if (hmlenu(ctx.uncolored_mesh_batches) == 0)
        return;

    use_space_pipeline();
    apply_fog();

    for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) {
        draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value,
                                            ctx.uncolored_mesh_batches[i].key);
    }

    pop_fog();
}


void render(void) {
    textures_update_atlas(&ctx.texture_cache);

    /* fit rendering context onto the resizable screen */
    if (ctx.window_size_has_changed) {
        if ((float)ctx.window_dims.x / (float)ctx.window_dims.y > (float)ctx.base_render_width / (float)ctx.base_render_height) {
            float ratio = (float)ctx.window_dims.y / (float)ctx.base_render_height;
            int w = (int)((float)ctx.base_render_width * ratio);
            setup_viewport(
                ctx.window_dims.x / 2 - w / 2,
                0,
                w,
                ctx.window_dims.y
            );
        } else {
            float ratio = (float)ctx.window_dims.x / (float)ctx.base_render_width;
            int h = (int)((float)ctx.base_render_height * ratio);
            setup_viewport(
                0,
                ctx.window_dims.y / 2 - h / 2,
                ctx.window_dims.x,
                h
            );
        }
    }

    render_space();
    render_skybox(); /* after space, as to use depth buffer for early rejection */
    render_2d();
    swap_buffers();
    clear_draw_buffer();
}


void draw_camera(const Camera *const camera) {
    /* TODO: skip recaulculating if it's the same? */
    camera_projection_matrix = camera_perspective(camera);
    camera_look_at_matrix    = camera_look_at(camera);
}
