#include "twn_game_api.h"
#include "state.h"
#include "twn_vec.h"

#include <SDL.h>

/* planned features: */
/* grid-based, bounded space (65536 / POINTS_PER_METER meters), which allows for efficient storage */
/* 65534 point limit, 16 object limit, 2048 faces per object, 32 textures per object */
/* triangles and quads only */
/* support for billboards and flat two sided quads */
/* texture painting */
/* bones with mesh animations, snapping to grid, with no weights */
/* billboard render to specified angles */
/* 1 point light primitive lighting */
/* live edited textures are capped at 128x128 */

/* assumptions: */
/* up is always (0,1,0) */
/* preallocations everywhere */

static State state;
static bool init;


static uint8_t new_object(const char *name) {
    if (state.objects_sz >= OBJECT_LIMIT)
        return INVALID_OBJECT;
    state.objects_sz++;
    state.objects[state.objects_sz-1].name = SDL_strdup(name);
    return state.objects_sz-1;
}


static uint16_t new_point(int16_t x, int16_t y, int16_t z) {
    if (state.points_sz >= POINT_LIMIT)
        return INVALID_POINT;
    state.points_sz++;
    state.points[state.points_sz-1] = (Point){x, y, z};
    return state.points_sz-1;
}


static uint16_t push_face(uint8_t object,
                          uint16_t p0, uint16_t p1, uint16_t p2, uint16_t p3,
                          uint8_t texture, uint8_t tex_scale, int16_t tex_x, int16_t tex_y)
{
    Object *o = &state.objects[object];
    o->faces_sz++;
    o->faces[o->faces_sz-1] = (Face) {
        .p = {p0, p1, p2, p3},
        .texture = texture,
        .tex_scale = tex_scale,
        .tex_x = tex_x, .tex_y = tex_y,
    };
    return o->faces_sz-1;
}


static uint8_t push_texture(uint8_t object, char *texture) {
    /* TODO: search and combine if it already exists */
    Object *o = &state.objects[object];
    o->textures_sz++;
    o->textures[o->textures_sz-1] = SDL_strdup(texture);
    return o->textures_sz-1;
}


static void push_operation(Operation operation, bool active) {
    state.op_stack_ptr++;
    uint8_t op = state.op_stack_ptr % UNDO_STACK_SIZE;
    state.op_stack[op] = operation;
    state.op_active = active;
}


static inline Vec3 point_to_vec3(uint8_t object, uint16_t point) {
    Object *o = &state.objects[object];
    return (Vec3){ (o->position.x + state.points[point].x) / (float)POINTS_PER_METER,
                   (o->position.y + state.points[point].y) / (float)POINTS_PER_METER,
                   (o->position.z + state.points[point].z) / (float)POINTS_PER_METER };
}


static inline Vec2 project_texture_coordinate(Vec2 origin, Vec3 plane, Vec3 point, float scale) {
    Vec3 right = vec3_norm(vec3_cross(plane, (Vec3){0,1,0}));
    if (isnanf(right.x)) right = vec3_norm(vec3_cross(plane, (Vec3){1,0,0}));
    Vec3 up = vec3_norm(vec3_cross(plane, right));
    Vec3 proj_origin = vec3_add(vec3_scale(right, origin.x), vec3_scale(up, origin.y));
    Vec3 local = vec3_sub(proj_origin, point);
    return (Vec2){ vec3_dot(local, right) * scale, vec3_dot(local, up) * scale };
}


static void render_object(uint8_t object) {
    Object *o = &state.objects[object];

    if (o->is_invisible)
        return;

    for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
        Face *f = &o->faces[fi];

        /* quads */
        if (f->p[3] != INVALID_POINT) {
            Vec3 p0 = point_to_vec3(object, f->p[0]);
            Vec3 p1 = point_to_vec3(object, f->p[1]);
            Vec3 p2 = point_to_vec3(object, f->p[2]);
            Vec3 p3 = point_to_vec3(object, f->p[3]);

            if (state.solid_display_mode) {
                Vec3 n = vec3_norm(vec3_cross(vec3_sub(p1, p0), vec3_sub(p2, p0)));
                Vec2 to = { f->tex_x / (float)POINTS_PER_METER, f->tex_y / (float)POINTS_PER_METER, };
                Vec2 tul = project_texture_coordinate(to, n, p0, (float)f->tex_scale);
                Vec2 tdr = project_texture_coordinate(to, n, p2, (float)f->tex_scale);

                draw_quad(state.current_texture,
                          p0, p1, p2, p3,
                          (Rect){tul.x, tdr.x, tdr.x - tul.x, tdr.y - tul.y},
                          (Color){255,255,255,255} );
            }
            else {
                draw_line_3d(p0, p1, 1, (Color){255,255,255,255});
                draw_line_3d(p1, p2, 1, (Color){255,255,255,255});
                draw_line_3d(p2, p3, 1, (Color){255,255,255,255});
                draw_line_3d(p3, p0, 1, (Color){255,255,255,255});
            }
        } else {
            Vec3 p0 = point_to_vec3(object, f->p[0]);
            Vec3 p1 = point_to_vec3(object, f->p[1]);
            Vec3 p2 = point_to_vec3(object, f->p[2]);

            // if (state.solid_display_mode)
            //     draw_triangle(state.current_texture,
            //                  p0, p1, p2, )
            // else {
            //     draw_line_3d(p0, p1, 1, (Color){255,255,255,255});
            //     draw_line_3d(p1, p2, 1, (Color){255,255,255,255});
            //     draw_line_3d(p2, p3, 1, (Color){255,255,255,255});
            //     draw_line_3d(p3, p0, 1, (Color){255,255,255,255});
            // }
        }
    }
}


static uint8_t new_cube(Point pos, Point size) {
    uint8_t object = new_object("cube");

    uint16_t p0 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2);
    uint16_t p1 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2);
    uint16_t p2 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z - size.z / 2);
    uint16_t p3 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z - size.z / 2);
    uint16_t p4 = new_point(pos.x - size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2);
    uint16_t p5 = new_point(pos.x - size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2);
    uint16_t p6 = new_point(pos.x + size.x / 2, pos.y + size.y / 2, pos.z + size.z / 2);
    uint16_t p7 = new_point(pos.x + size.x / 2, pos.y - size.y / 2, pos.z + size.z / 2);

    uint8_t tex = push_texture(object, "/data/placeholder.png");
    push_face(object, p2, p3, p0, p1, tex, 128, 0, 0);
    push_face(object, p5, p4, p7, p6, tex, 128, 0, 0);
    push_face(object, p1, p0, p4, p5, tex, 128, 0, 0);
    push_face(object, p6, p7, p3, p2, tex, 128, 0, 0);
    push_face(object, p2, p1, p5, p6, tex, 128, 0, 0);
    push_face(object, p0, p3, p7, p4, tex, 128, 0, 0);

    return object;
}


static void process_camera_rotation(void) {
    float horizontal_rotation = 0;
    float vertical_rotation = 0;

    if (input_action_pressed("camera_rotate_left"))
        horizontal_rotation -= CAMERA_ROTATION_SPEED;
    if (input_action_pressed("camera_rotate_right"))
        horizontal_rotation += CAMERA_ROTATION_SPEED;
    if (input_action_pressed("camera_rotate_up"))
        vertical_rotation -= CAMERA_ROTATION_SPEED;
    if (input_action_pressed("camera_rotate_down"))
        vertical_rotation += CAMERA_ROTATION_SPEED;

    Vec3 front = vec3_cross(state.camera_direction, (Vec3){0,1,0});
    Vec3 local_position = vec3_sub(state.active_center, state.camera_position);
    Vec3 new_local_position = vec3_rotate(local_position, horizontal_rotation, (Vec3){0,1,0});

    state.camera_direction = vec3_rotate(state.camera_direction, horizontal_rotation, (Vec3){0,1,0});
    Vec3 new_rot = vec3_rotate(state.camera_direction, vertical_rotation, front);

    /* only apply if it's in limits */
    float d = vec3_dot(new_rot, (Vec3){0,-1,0});
    if (fabsf(d) <= 0.999f) {
        new_local_position = vec3_rotate(new_local_position, vertical_rotation, front);
        state.camera_direction = new_rot;
    }

    state.camera_position = vec3_sub(state.active_center, new_local_position);
}


static void process_camera_translation(void) {
    Vec3 right = vec3_norm(vec3_cross(state.camera_direction, (Vec3){0,1,0}));
    Vec3 up = vec3_norm(vec3_cross(state.camera_direction, right));
    Vec3 was = state.camera_position;

    if (input_action_pressed("camera_rotate_left"))
        state.camera_position = vec3_sub(state.camera_position, vec3_scale(right, CAMERA_TRANSLATION_SPEED));
    if (input_action_pressed("camera_rotate_right"))
        state.camera_position = vec3_add(state.camera_position, vec3_scale(right, CAMERA_TRANSLATION_SPEED));
    if (input_action_pressed("camera_rotate_up"))
        state.camera_position = vec3_sub(state.camera_position, vec3_scale(up, CAMERA_TRANSLATION_SPEED));
    if (input_action_pressed("camera_rotate_down"))
        state.camera_position = vec3_add(state.camera_position, vec3_scale(up, CAMERA_TRANSLATION_SPEED));

    state.active_center = vec3_add(state.active_center, vec3_sub(state.camera_position, was));

    draw_billboard("/data/camera.png",
                    vec3_add(state.camera_position, vec3_scale(state.camera_direction, vec3_length(state.camera_position))),
                    (Vec2){0.2f,0.2f},
                    (Rect){0,0,16,16},
                    (Color){255,255,255,255},
                    false);

    /* show relation to origin */
    draw_billboard("/data/center.png",
                    (Vec3){0},
                    (Vec2){0.1f,0.1f},
                    (Rect){0,0,16,16},
                    (Color){255,255,255,255},
                    false);

    draw_line_3d((Vec3){0}, state.active_center, 1, (Color){255,255,255,255});
}


static void process_camera_movement(void) {
    input_action("camera_rotate_left", "A");
    input_action("camera_rotate_right", "D");
    input_action("camera_rotate_up", "W");
    input_action("camera_rotate_down", "S");
    input_action("camera_lock_rotation", "SPACE");

    if (input_action_pressed("camera_lock_rotation")) {
        process_camera_translation();
    } else {
        process_camera_rotation();
    }
}


static inline DrawCameraUnprojectResult unproject_point(Vec2 point) {
    return draw_camera_unproject(
        point,
        state.camera_position,
        state.camera_direction,
        (Vec3){0, 1, 0},
        state.camera_is_orthographic ? 0 : CAMERA_FOV,
        state.camera_zoom,
        100 );
}


static bool find_closest_point(uint8_t* object_result, uint16_t *point_result) {
    DrawCameraUnprojectResult pos_and_ray = unproject_point(ctx.mouse_position);

    /* step over every selectable object and find points closest to the view ray */
    /* by constructing triangles and finding their height, from perpendicular */
    uint16_t closest_point = INVALID_POINT;
    uint8_t closest_obj = INVALID_OBJECT;
    float closest_distance = INFINITY;
    for (uint8_t obj = 0; obj < state.objects_sz; ++obj) {
        Object *o = &state.objects[obj];

        if (o->is_invisible)
            continue;

        /* TODO: is it possible to skip repeated points? does it matter? */
        /*       as we limit the point could we could actually have bool array preallocated for this */
        for (uint16_t fi = 0; fi < o->faces_sz; ++fi) {
            Face *f = &o->faces[fi];
            for (uint16_t pi = 0; pi < 4; ++pi) {
                Vec3 p = point_to_vec3(obj, f->p[pi]);
                Vec3 d = vec3_sub(pos_and_ray.position, p);
                Vec3 b = vec3_cross(d, pos_and_ray.direction);
                float ray_dist = vec3_length(b);
                if (ray_dist > ((float)SELECTION_SPHERE_RADIUS / POINTS_PER_METER))
                    continue;
                float dist = vec3_length(vec3_add(p, b));
                if (dist < closest_distance) {
                    closest_distance = dist;
                    closest_obj = obj;
                    closest_point = f->p[pi];
                }
            }
        }
    }

    if (closest_point == INVALID_POINT)
        return false;

    if (object_result)
        *object_result = closest_obj;
    if (point_result)
        *point_result = closest_point;

    return true;
}


/* o = vector origin */
/* v = vector direction, normalized */
/* p = any point on plane */
/* n = normal of a plane */
static bool vector_plane_intersection(Vec3 o, Vec3 v, Vec3 p, Vec3 n, Vec3 *out) {
    float dot = vec3_dot(n, v);
    if (fabsf(dot) > FLT_EPSILON) {
        Vec3 w = vec3_sub(o, p);
        float fac = -vec3_dot(n, w) / dot;
        *out = vec3_add(o, vec3_scale(v, fac));
        return true;
    }
    /* vector and plane are perpendicular, assume that it lies exactly on it */
    return false;
}


static void show_snap_lines(Vec3 p) {
    float step = 1.0f / ((float)POINTS_PER_METER / state.grid_snap_granularity);
    int const lines_per_side = (SNAP_LINES_SHOW - 1) / 2;
    for (int l = -lines_per_side; l <= lines_per_side; ++l) {
        if (!state.axis_mask[0]) {
            Vec3 c = vec3_add(p, vec3_scale((Vec3){1,0,0}, step * (float)l));
            draw_line_3d(
                vec3_add(c, vec3_scale((Vec3){0,0,1},  SNAP_LINES_WIDTH)),
                vec3_add(c, vec3_scale((Vec3){0,0,1}, -SNAP_LINES_WIDTH)),
                1,
                SNAP_LINES_COLOR);
        }
        if (!state.axis_mask[1]) {
            Vec3 axis = fabsf(vec3_dot(state.camera_direction, (Vec3){0,0,1})) >= 0.5f ? (Vec3){1,0,0} : (Vec3){0,0,1};
            Vec3 c = vec3_add(p, vec3_scale((Vec3){0,1,0}, step * (float)l));
            draw_line_3d(
                vec3_add(c, vec3_scale(axis,  SNAP_LINES_WIDTH)),
                vec3_add(c, vec3_scale(axis, -SNAP_LINES_WIDTH)),
                1,
                SNAP_LINES_COLOR);
        }
        if (!state.axis_mask[2]) {
            Vec3 c = vec3_add(p, vec3_scale((Vec3){0,0,1}, step * (float)l));
            draw_line_3d(
                vec3_add(c, vec3_scale((Vec3){1,0,0},  SNAP_LINES_WIDTH)),
                vec3_add(c, vec3_scale((Vec3){1,0,0}, -SNAP_LINES_WIDTH)),
                1,
                SNAP_LINES_COLOR);
        }
    }
}


static void process_operation_move_point(Operation *op) {
    /* finish dragging around */
    /* TODO: dont keep empty ops on stack? */
    if (input_action_just_released("select")) {
        state.op_active = false;
        return;
    }

    float size = sinf(ctx.frame_number / 10) * 0.05f + 0.05f;
    draw_billboard("/data/grab.png",
                   point_to_vec3(op->data.move_point.object, op->data.move_point.point),
                   (Vec2){size, size},
                   (Rect){0,0,16,16},
                   (Color){255,255,255,255},
                   false);

    DrawCameraUnprojectResult cam = unproject_point(ctx.mouse_position);
    Vec3 p = point_to_vec3(op->data.move_point.object, op->data.move_point.point);
    bool point_moved = false;

    /* figure out which planes are angled acutely against the viewing direction */
    bool y_closer_than_horizon = fabsf(vec3_dot(state.camera_direction, (Vec3){0,1,0})) >= 0.5f;

    Vec3 plane_normal_x = y_closer_than_horizon ? (Vec3){0,1,0} : (Vec3){0,0,1};
    Vec3 plane_normal_z = y_closer_than_horizon ? (Vec3){0,1,0} : (Vec3){1,0,0};

    /* show snapping in lines */
    show_snap_lines(p);

    Vec3 s;
    if (!state.axis_mask[0]) {
        if (vector_plane_intersection(cam.position, cam.direction, p, plane_normal_x, &s)) {
            int16_t xch = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){1,0,0}) * (float)POINTS_PER_METER));
            xch -= xch % state.grid_snap_granularity;
            state.points[op->data.move_point.point].x += xch;
            op->data.move_point.delta_x += xch;
            if (xch != 0) point_moved = true;
        }
    }

    if (!state.axis_mask[1]) {
        if (vector_plane_intersection(cam.position, cam.direction, p, (Vec3){0,0,1}, &s)) {
            int16_t ych = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){0,1,0}) * (float)POINTS_PER_METER));
            ych -= ych % state.grid_snap_granularity;
            state.points[op->data.move_point.point].y += ych;
            op->data.move_point.delta_y += ych;
            if (ych != 0) point_moved = true;
        }
    }

    if (!state.axis_mask[2]) {
        if (vector_plane_intersection(cam.position, cam.direction, p, plane_normal_z, &s)) {
            int16_t zch = (int16_t)(roundf(vec3_dot(vec3_sub(s, p), (Vec3){0,0,1}) * (float)POINTS_PER_METER));
            zch -= zch % state.grid_snap_granularity;
            state.points[op->data.move_point.point].z += zch;
            op->data.move_point.delta_z += zch;
            if (zch != 0) point_moved = true;
        }
    }

    if (point_moved)
        audio_play("/data/bong.ogg", NULL, false, 0.12f, 0.0f);
}


static void reverse_operation_move_point(Operation *op) {
    SDL_assert(op->kind == OPERATION_MOVE_POINT);
    state.points[op->data.move_point.point].x -= op->data.move_point.delta_x;
    state.points[op->data.move_point.point].y -= op->data.move_point.delta_y;
    state.points[op->data.move_point.point].z -= op->data.move_point.delta_z;
    audio_play("/data/drop.ogg", NULL, false, 0.4f, 0.0f);
}


static void process_operations(void) {
    if (input_action_just_pressed("undo")) {
        /* TODO: checks and defined limit */
        Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE];
        state.op_stack_ptr--;

        switch (op->kind) {
        case OPERATION_MOVE_POINT: {
            reverse_operation_move_point(op);

            break;
        }

        default:
            (void)0;
        }
    }

    if (!state.op_active) {
        uint16_t point_select; uint8_t obj_select;
        if (find_closest_point(&obj_select, &point_select)) {
            draw_billboard("/data/point.png",
                           point_to_vec3(obj_select, point_select),
                           (Vec2){0.05f, 0.05f},
                           (Rect){0,0,16,16},
                           (Color){255,255,255,255},
                           false);

            if (input_action_just_pressed("select"))
                push_operation((Operation){
                    .kind = OPERATION_MOVE_POINT,
                    .data = { .move_point = { .point = point_select } },
                }, true );
        }
    }

    if (state.op_active) {
        Operation *op = &state.op_stack[state.op_stack_ptr % UNDO_STACK_SIZE];
        switch (op->kind) {
        case OPERATION_MOVE_POINT: {
            process_operation_move_point(op);

            break;
        }

        default:
            (void)0;
        }
    }
}


static void draw_axes(void) {
    /* axis helpers */
    /* idea: double selection of axes for diagonal edits */
    draw_line_3d(state.active_center, (Vec3){256,0,0}, 1, state.axis_mask[0] ? (Color){0,0,0,75} : (Color){255,0,0,125});
    draw_billboard("/data/x.png",
                   vec3_add(state.active_center, (Vec3){2,0,0}),
                   (Vec2){0.1f, 0.1f},
                   (Rect){0},
                   state.axis_mask[0] ? (Color){0,0,0,255} : (Color){255,0,0,255},
                   false);

    draw_line_3d(state.active_center, (Vec3){0,256,0}, 1, state.axis_mask[1] ? (Color){0,0,0,75} : (Color){75,125,25,125});
    draw_billboard("/data/y.png",
                   vec3_add(state.active_center, (Vec3){0,1.5f,0}),
                   (Vec2){0.1f, 0.1f},
                   (Rect){0},
                   state.axis_mask[1] ? (Color){0,0,0,255} : (Color){75,125,25,255},
                   false);

    draw_line_3d(state.active_center, (Vec3){0,0,256}, 1, state.axis_mask[2] ? (Color){0,0,0,75} : (Color){0,0,255,125});
    draw_billboard("/data/z.png",
                   vec3_add(state.active_center, (Vec3){0,0,2}),
                   (Vec2){0.1f, 0.1f},
                   (Rect){0},
                   state.axis_mask[2] ? (Color){0,0,0,255} : (Color){0,0,255,255},
                   false);
}


static void display_textures(void) {
    String list = file_read("/data/assets/", ":images");
    if (!list.data)
        return;

    draw_rectangle((Rect){0, 480 - 50, 640, 480}, (Color){0,0,0,126});

    char *selected = NULL;
    char *saveptr = NULL;
    int count = 0;
    char const *part = SDL_strtokr(list.data, "\n", &saveptr);
    do {
        Rect box = (Rect){(float)count * 50, 480 - 48, 48, 48};
        draw_sprite(part,
                    box,
                    (Rect){0,0,64,64},
                    (Color){255,255,255,255},
                    0, false, false, true);
        count++;

        if (rect_intersects(box, (Rect){ctx.mouse_position.x, ctx.mouse_position.y, 1, 1}))
            selected = SDL_strdup(part);

    } while ((part = SDL_strtokr(NULL, "\n", &saveptr)));

    if (selected) {
        draw_text(selected, (Vec2){0, 480 - 48 - 24}, 24, (Color){255,255,255,255}, NULL);
        if (input_action_just_pressed("select")) {
            if (state.current_texture) SDL_free(state.current_texture);
            state.current_texture = selected;
        } else
            SDL_free(selected);
    }
}


void game_tick(void) {
    if (!init) {
        /* default state */
        new_cube((Point){0,0,0}, (Point){POINTS_PER_METER,POINTS_PER_METER,POINTS_PER_METER});
        state.camera_position = (Vec3){2,1,2};
        state.camera_direction = vec3_norm(((Vec3){-2,-1,-2}));
        state.camera_zoom = 0.5f;
        state.grid_snap_granularity = 16;
        state.axis_mask[1] = 1; state.axis_mask[2] = 1;
        init = true;
    }

    input_action("toggle_display_mode", "Q");
    input_action("toggle_projection", "E");

    input_action("toggle_x_axis", "Z");
    input_action("toggle_y_axis", "X");
    input_action("toggle_z_axis", "C");

    input_action("select", "LCLICK");
    input_action("undo", "F");

    if (input_action_just_pressed("toggle_display_mode")) {
        audio_play("/data/click.wav", NULL, false, 0.7f, 0.0f);
        state.solid_display_mode = !state.solid_display_mode;
    }

    if (input_action_just_pressed("toggle_projection")) {
        audio_play("/data/pop.wav", NULL, false, 0.8f, 0.0f);
        state.camera_is_orthographic = !state.camera_is_orthographic;
    }

    if (input_action_just_pressed("toggle_x_axis")) {
        state.axis_mask[0] = 0; state.axis_mask[1] = 1; state.axis_mask[2] = 1;
    }

    if (input_action_just_pressed("toggle_y_axis")) {
        state.axis_mask[0] = 1; state.axis_mask[1] = 0; state.axis_mask[2] = 1;
    }

    if (input_action_just_pressed("toggle_z_axis")) {
        state.axis_mask[0] = 1; state.axis_mask[1] = 1; state.axis_mask[2] = 0;
    }

    process_camera_movement();
    process_operations();

    draw_axes();

    for (uint8_t obj = 0; obj < state.objects_sz; ++obj)
        render_object(obj);

    if (state.solid_display_mode)
        display_textures();

    draw_text("twndel\x03", (Vec2){0, 2}, 32, (Color){255,255,255,200}, NULL);
    draw_camera(
        state.camera_position,
        state.camera_direction,
        (Vec3){0, 1, 0},
        state.camera_is_orthographic ? 0 : CAMERA_FOV,
        state.camera_zoom,
        100
    );
}


void game_end(void) {
    for (uint8_t obj = 0; obj < state.objects_sz; ++obj) {
        Object *o = &state.objects[obj];
        for (uint8_t t = 0; t < o->textures_sz; ++t)
            SDL_free(o->textures[t]);
        SDL_free(o->name);
    }
    if (state.current_texture)
        SDL_free(state.current_texture);
}