#include "twn_engine_context_c.h"
#include "twn_draw_c.h"
#include "twn_draw.h"
#include "twn_util_c.h"

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


void draw_circle(Vec2 position, float radius, Color color) {
    CirclePrimitive circle = {
        .radius = radius,
        .color = color,
        .position = position,
    };

    Primitive2D primitive = {
        .type = PRIMITIVE_2D_CIRCLE,
        .circle = circle,
    };

    arrput(ctx.render_queue_2d, primitive);
}


void create_circle_geometry(Vec2 position,
                            float radius,
                            size_t num_vertices,
                            Vec2 vertices[])
{
    SDL_assert(num_vertices <= CIRCLE_VERTICES_MAX);

    /* the angle (in radians) to rotate by on each iteration */
    float seg_rotation_angle = (360.0f / (float)(num_vertices - 2)) * ((float)M_PI / 180);

    vertices[0].x = (float)position.x;
    vertices[0].y = (float)position.y;

    /* this point will rotate around the center */
    float start_x = 0.0f - radius;
    float start_y = 0.0f;

    for (size_t i = 1; i < num_vertices - 1; ++i) {
        float final_seg_rotation_angle = (float)i * seg_rotation_angle;

        float c, s;
        sincosf(final_seg_rotation_angle, &s, &c);

        vertices[i].x = c * start_x - s * start_y;
        vertices[i].y = c * start_y + s * start_x;

        vertices[i].x += position.x;
        vertices[i].y += position.y;
    }

    // place a redundant vertex to make proper circling over shared element buffer
    {
        float final_seg_rotation_angle = (float)1 * seg_rotation_angle;

        float c, s;
        sincosf(final_seg_rotation_angle, &s, &c);

        vertices[num_vertices - 1].x = c * start_x - s * start_y;
        vertices[num_vertices - 1].y = c * start_y + s * start_x;

        vertices[num_vertices - 1].x += position.x;
        vertices[num_vertices - 1].y += position.y;
    }
}


void render_circle(const CirclePrimitive *circle) {
    static Vec2 vertices[CIRCLE_VERTICES_MAX];
    static int prev_num_vertices = 0;
    static Vec2 prev_position = {0};

    int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX);

    if (prev_num_vertices != num_vertices) {
        create_circle_geometry(circle->position,
                               circle->radius,
                               num_vertices,
                               vertices);
        prev_num_vertices = num_vertices;
        prev_position = circle->position;
    } else {
        /* reuse the data, but offset it by difference with previously generated position */
        /* no evil cos sin ops this way, if radius is shared in sequential calls */
        Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y };
        for (int i = 0; i < num_vertices; ++i)
            vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y };
        prev_position = circle->position;
    }

    VertexBuffer buffer = get_scratch_vertex_array();
    specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices);

    DeferredCommandDraw command = {0};

    command.vertices = (AttributeArrayPointer) {
        .arity = 2,
        .type = TWN_FLOAT,
        .stride = sizeof (Vec2),
        .offset = 0,
        .buffer = buffer
    };

    command.constant_colored = true;
    command.color = circle->color;

    command.element_buffer = get_circle_element_buffer();
    command.element_count = (num_vertices - 2) * 3;
    command.range_end = (num_vertices - 2) * 3;

    command.texture_mode = circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY;
    command.pipeline = PIPELINE_2D;

    command.depth_range_high = depth_range_high;
    command.depth_range_low = depth_range_low;

    DeferredCommand final_command = {
        .type = DEFERRED_COMMAND_TYPE_DRAW,
        .draw = command
    };

    arrpush(deferred_commands, final_command);
}
