#include "draw.h"
#include "renderer.h"
#include "math.h"
#include "font.h"
#include "scratch.h"
#include "sprite.h"

GLOBAL struct {
    struct renderer_handle solid_white;
} G = { 0 }, DEBUG_ALIAS(G, G_draw);

/* ========================== *
 * Startup
 * ========================== */

struct draw_startup_receipt draw_startup(struct renderer_startup_receipt *renderer_sr,
                                         struct font_startup_receipt *font_sr)
{
    (UNUSED)renderer_sr;
    (UNUSED)font_sr;
    u32 pixel_white = 0xFFFFFFFF;
    G.solid_white = renderer_texture_alloc((struct image_rgba) { .width = 1, .height = 1, .pixels = &pixel_white } );
    return (struct draw_startup_receipt) { 0 };
}

/* ========================== *
 * Texture
 * ========================== */

INTERNAL void draw_sprite_quad_internal(struct renderer_canvas *canvas, struct clip_rect clip, u32 tint, struct quad quad)
{
    struct texture_shader_vertex *vertices = NULL;
    vidx *indices = NULL;
    u32 offset = renderer_canvas_push_vertices(canvas, (u8 **)&vertices, &indices, 4, 6);

    /* Top left */
    vertices[0] = (struct texture_shader_vertex) {
        .pos = quad.p1,
        .uv = { clip.p1.x, clip.p1.y },
        .color = tint
    };
    /* Top right */
    vertices[1] = (struct texture_shader_vertex) {
        .pos = quad.p2,
        .uv = { clip.p2.x, clip.p1.y },
        .color = tint
    };
    /* Bottom right */
    vertices[2] = (struct texture_shader_vertex) {
        .pos = quad.p3,
        .uv = { clip.p2.x, clip.p2.y },
        .color = tint
    };
    /* Bottom left */
    vertices[3] = (struct texture_shader_vertex) {
        .pos = quad.p4,
        .uv = { clip.p1.x, clip.p2.y },
        .color = tint
    };

    /* Top / right triangle */
    indices[0] = offset + 0;
    indices[1] = offset + 1;
    indices[2] = offset + 2;

    /* Bottom / left triangle */
    indices[3] = offset + 0;
    indices[4] = offset + 2;
    indices[5] = offset + 3;
}

void draw_sprite_quad(struct renderer_canvas *canvas, struct draw_sprite_params params, struct quad quad)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .sprite = params.sprite });

    draw_sprite_quad_internal(canvas, params.clip, params.tint, quad);
}

void draw_sprite_rect(struct renderer_canvas *canvas, struct draw_sprite_params params, struct rect rect)
{
    struct quad quad = quad_from_rect(rect);
    draw_sprite_quad(canvas, params, quad);
}

/* ========================== *
 * Solid fill shapes
 * ========================== */

INTERNAL void draw_solid_poly_internal(struct renderer_canvas *canvas, struct v2_array array, u32 color)
{
    u32 num_tris = array.count - 2;
    u32 num_indices = num_tris * 3;

    struct texture_shader_vertex *vertices = NULL;
    vidx *indices = NULL;
    u32 idx_offset = renderer_canvas_push_vertices(canvas, (u8 **)&vertices, &indices, array.count, num_indices);

    /* Fill vertices */
    for (u32 i = 0; i < array.count; ++i) {
        vertices[i] = (struct texture_shader_vertex) {
            .pos = array.points[i],
            .color = color
        };
    }

    /* Fill indices */
    for (u32 i = 0; i < num_tris; ++i) {
        u32 tri_offset = i * 3;
        indices[tri_offset + 0] = idx_offset + 0;
        indices[tri_offset + 1] = idx_offset + (i + 1);
        indices[tri_offset + 2] = idx_offset + (i + 2);
    }
}

void draw_solid_poly(struct renderer_canvas *canvas, struct v2_array array, u32 color)
{
    if (array.count < 3) {
        return;
    }

    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    draw_solid_poly_internal(canvas, array, color);
}

void draw_solid_circle(struct renderer_canvas *canvas, struct v2 pos, f32 radius, u32 color, u32 detail)
{
    struct temp_arena scratch = scratch_begin_no_conflict();

    struct v2 *points = arena_push_array(scratch.arena, struct v2, detail);
    for(u32 i = 0; i < detail; ++i) {
        struct v2 p = V2(
            radius * math_cos(i * (PI * 2.f) / detail),
            radius * math_sin(i * (PI * 2.f) / detail)
        );
        points[i] = v2_add(pos, p);
    }

    struct v2_array a = {
        .points = points,
        .count = detail
    };
    draw_solid_poly(canvas, a, color);

    scratch_end(scratch);
}

void draw_solid_quad(struct renderer_canvas *canvas, struct quad quad, u32 color)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
}

void draw_solid_rect(struct renderer_canvas *canvas, struct rect rect, u32 color)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    struct quad quad = quad_from_rect(rect);
    draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
}

/* ========================== *
 * Solid line shapes
 * ========================== */

void draw_solid_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, u32 color)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    struct quad quad = quad_from_line(start, end, thickness);
    draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
}

void draw_solid_ray(struct renderer_canvas *canvas, struct v2 pos, struct v2 rel, f32 thickness, u32 color)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    struct quad quad = quad_from_ray(pos, rel, thickness);
    draw_sprite_quad_internal(canvas, CLIP_ALL, color, quad);
}

void draw_solid_poly_line(struct renderer_canvas *canvas, struct v2_array array, b32 loop, f32 thickness, u32 color)
{
    if (array.count < 2) {
        return;
    }

    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });
    for (u64 i = 1; i < array.count; ++i) {
        struct v2 p1 = array.points[i - 1];
        struct v2 p2 = array.points[i];
        struct quad q = quad_from_line(p1, p2, thickness);
        draw_sprite_quad_internal(canvas, CLIP_ALL, color, q);
    }
    if (loop && array.count > 2) {
        struct v2 p1 = array.points[array.count - 1];
        struct v2 p2 = array.points[0];
        struct quad q = quad_from_line(p1, p2, thickness);
        draw_sprite_quad_internal(canvas, CLIP_ALL, color, q);
    }
}

void draw_solid_quad_line(struct renderer_canvas *canvas, struct quad quad, f32 thickness, u32 color)
{
    struct v2 points[] = { quad.p1, quad.p2, quad.p3, quad.p4 };
    struct v2_array a = { .points = points, .count = ARRAY_COUNT(points) };
    draw_solid_poly_line(canvas, a, true, thickness, color);
}

void draw_solid_rect_line(struct renderer_canvas *canvas, struct rect rect, f32 thickness, u32 color)
{
    struct quad q = quad_from_rect(rect);
    draw_solid_quad_line(canvas, q, thickness, color);
}

void draw_solid_arrow_line(struct renderer_canvas *canvas, struct v2 start, struct v2 end, f32 thickness, f32 arrowhead_height, u32 color)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = G.solid_white });

    const f32 head_width_ratio = 0.5f;  /* Width of arrowhead relative to its length */

    const f32 max_height_to_line_ratio = 0.9f;  /* Maximum length of arrowhead relative to total line length */
    arrowhead_height = min_f32(arrowhead_height, v2_len(v2_sub(end, start)) * max_height_to_line_ratio);

    struct v2 head_start_dir = v2_sub(start, end);
    head_start_dir = v2_norm(head_start_dir);
    head_start_dir = v2_mul(head_start_dir, arrowhead_height);

    struct v2 head_start = v2_add(end, head_start_dir);

    struct v2 head_p1_dir = v2_mul(v2_perp(head_start_dir), head_width_ratio);
    struct v2 head_p2_dir = v2_neg(head_p1_dir);

    struct v2 head_p1 = v2_add(head_start, head_p1_dir);
    struct v2 head_p2 = v2_add(head_start, head_p2_dir);

    struct v2 head_points[] = { end, head_p1, head_p2 };
    struct v2_array head_points_v2_array = {
        .points = head_points,
        .count = ARRAY_COUNT(head_points)
    };
    draw_solid_poly_internal(canvas, head_points_v2_array, color);

    struct quad line_quad = quad_from_line(start, head_start, thickness);
    draw_sprite_quad_internal(canvas, CLIP_ALL, color, line_quad);
}

void draw_solid_arrow_ray(struct renderer_canvas *canvas, struct v2 pos, struct v2 rel, f32 thickness, f32 arrowhead_height, u32 color)
{
    struct v2 end = v2_add(pos, rel);
    draw_solid_arrow_line(canvas, pos, end, thickness, arrowhead_height, color);
}

/* ========================== *
 * Text
 * ========================== */

void draw_text(struct renderer_canvas *canvas, struct font *font, struct v2 pos, struct string str)
{
    draw_text_ex(canvas, font, pos, 1.0, str);
}

void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 pos, f32 scale, struct string str)
{
    renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture_handle = font->image_renderer_handle });

    struct v2 draw_pos = pos;
    draw_pos.y += font->point_size * scale;

    struct string_codepoint_iter iter = string_codepoint_iter_begin(str);
    while (string_codepoint_iter_next(&iter)) {
        u32 codepoint = iter.codepoint;

        /* TODO: Remove this (placeholder \n) */
        if (codepoint == '\n') {
            draw_pos.x = pos.x;
            draw_pos.y += (font->point_size * 1.5f) * scale;
            continue;
        }

        struct font_glyph *glyph = font_get_glyph(font, codepoint);
        f32 x = draw_pos.x + glyph->off_x * scale;
        f32 y = draw_pos.y + glyph->off_y * scale;
        f32 width = glyph->width * scale;
        f32 height = glyph->height * scale;

        struct clip_rect clip = {
            {
                glyph->atlas_rect.x / (f32)font->image_width,
                glyph->atlas_rect.y / (f32)font->image_height
            },

            {
                (glyph->atlas_rect.x + glyph->atlas_rect.width) / (f32)font->image_width,
                (glyph->atlas_rect.y + glyph->atlas_rect.height) / (f32)font->image_height
            }
        };

        struct quad quad = quad_from_rect(RECT(x, y, width, height));
        draw_sprite_quad_internal(canvas, clip, 0xFFFFFFFF, quad);

        draw_pos.x += glyph->advance * scale;
    }
    string_codepoint_iter_end(&iter);
}
