#include "game.h"
#include "sys.h"
#include "util.h"
#include "world.h"
#include "sprite.h"
#include "sound.h"
#include "mixer.h"
#include "math.h"
#include "scratch.h"
#include "atomic.h"
#include "app.h"
#include "log.h"

GLOBAL struct {
    struct atomic_i32 game_thread_shutdown;
    struct sys_thread game_thread;

    struct world world;

    /* Game thread input */
    struct sys_mutex game_cmds_mutex;
    struct arena game_cmds_arena;

    /* Game thread output */
    struct sys_mutex published_tick_mutex;
    struct world published_tick;
    struct atomic_u64 published_tick_id;
} G = { 0 }, DEBUG_ALIAS(G, G_game);

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

INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown);
INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg);

struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
                                         struct sprite_startup_receipt *sheet_sr,
                                         struct sound_startup_receipt *sound_sr)
{
    (UNUSED)mixer_sr;
    (UNUSED)sheet_sr;
    (UNUSED)sound_sr;

    /* Initialize game cmd storage */
    G.game_cmds_mutex = sys_mutex_alloc();
    G.game_cmds_arena = arena_alloc(GIGABYTE(64));

    /* Initialize world */
    world_alloc(&G.world);

    /* Initialize tick transmission */
    world_alloc(&G.published_tick);
    G.published_tick_mutex = sys_mutex_alloc();

    G.world.timescale = GAME_TIMESCALE;
    G.game_thread = sys_thread_alloc(&game_thread_entry_point, NULL, STR("[P2] Game thread"));
    app_register_exit_callback(&game_shutdown);

    return (struct game_startup_receipt) { 0 };
}

INTERNAL APP_EXIT_CALLBACK_FUNC_DEF(game_shutdown)
{
    __prof;
    atomic_i32_eval_exchange(&G.game_thread_shutdown, true);
    sys_thread_wait_release(&G.game_thread);
}

/* ========================== *
 * Game cmd
 * ========================== */

INTERNAL void push_cmds(struct game_cmd_array cmd_array)
{
    sys_mutex_lock(&G.game_cmds_mutex);
    {
        struct game_cmd *cmds = arena_push_array(&G.game_cmds_arena, struct game_cmd, cmd_array.count);
        MEMCPY(cmds, cmd_array.cmds, cmd_array.count * sizeof(*cmds));
    }
    sys_mutex_unlock(&G.game_cmds_mutex);
}

INTERNAL struct game_cmd_array pop_cmds(struct arena *arena)
{
    struct game_cmd_array array = { 0 };
    if (G.game_cmds_arena.pos > 0) {
        sys_mutex_lock(&G.game_cmds_mutex);
        {
            struct buffer game_cmds_buff = arena_to_buffer(&G.game_cmds_arena);
            arena_align(arena, alignof(struct game_cmd));
            array.cmds = (struct game_cmd *)arena_push_array(arena, u8, game_cmds_buff.size);
            array.count = game_cmds_buff.size / sizeof(struct game_cmd);
            MEMCPY(array.cmds, game_cmds_buff.data, game_cmds_buff.size);
            arena_reset(&G.game_cmds_arena);
        }
        sys_mutex_unlock(&G.game_cmds_mutex);
    }
    return array;
}

/* ========================== *
 * Update
 * ========================== */

INTERNAL void publish_game_tick(void)
{
    __prof;
    sys_mutex_lock(&G.published_tick_mutex);
    {
        world_copy_replace(&G.published_tick, &G.world);
        atomic_u64_eval_exchange(&G.published_tick_id, G.published_tick.tick_id);
    }
    sys_mutex_unlock(&G.published_tick_mutex);
}

INTERNAL void recalculate_world_xform_recurse(struct entity *parent)
{
    struct temp_arena scratch = scratch_begin_no_conflict();

    struct stack_node {
        struct entity *entity;
        struct xform parent_xform;
    };

    /* Depth first iteration */
    *arena_push(scratch.arena, struct stack_node) = (struct stack_node) { .entity = parent, .parent_xform = XFORM_IDENT };
    u64 stack_count = 1;
    while (stack_count > 0) {
        /* Pull from top of stack */
        struct stack_node node;
        arena_pop(scratch.arena, struct stack_node, &node);
        --stack_count;

        /* Calculate child world xform */
        struct entity *child = node.entity;
        struct xform world_xform = xform_mul(node.parent_xform, child->rel_xform);
        child->world_xform = world_xform;

        /* Append sub-children to stack */
        struct entity *subchild = entity_from_handle(&G.world.entity_store, child->last);
        while (subchild->valid) {
            *arena_push(scratch.arena, struct stack_node) = (struct stack_node) {
                .entity = subchild,
                .parent_xform = world_xform
            };
            ++stack_count;
            subchild = entity_from_handle(&G.world.entity_store, subchild->prev);
        }
    }

    scratch_end(scratch);
}

#if 0
INTERNAL struct v2 sprite_sheet_size_meters(struct sprite_tag s)
{
    struct v2 size = { 0 };
    struct sprite_scope *scope = sprite_scope_begin();
    {
        struct sheet *sheet = sprite_sheet_from_tag_await(scope, s);
        size.x = sheet->frame_size.x / (f32)PIXELS_PER_UNIT;
        size.y = sheet->frame_size.y / (f32)PIXELS_PER_UNIT;
    }
    sprite_scope_end(scope);
    return size;
}
#endif

INTERNAL void game_update(void)
{
    __prof;

    struct temp_arena scratch = scratch_begin_no_conflict();

    /* ========================== *
     * Begin frame cache scopes
     * ========================== */

    struct sprite_scope *sprite_frame_scope = sprite_scope_begin();

    /* TODO: remove this (testing) */
    /* Initialize entities */
    static b32 run = 0;
    if (!run) {
        run = 1;

        /* Player ent */
        struct entity *player_ent;
        {
            struct v2 pos = V2(1, 1);
            struct v2 size = V2(1, 1);
            f32 r = 0;

            struct entity *e = entity_alloc(&G.world.entity_store);
            e->valid = true;
            e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);

            struct v2 sprite_pos = V2(0, 0);
            f32 sprite_rot = 0;
            struct v2 sprite_size = V2(0.5f, 0.5f);
            // struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);

            struct v2 sprite_pivot;
            {
                struct v2 sprite_pivot_norm = V2(0, 0.5);  /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */
                struct v2 half_size = v2_mul(sprite_size, 0.5f);
                sprite_pivot = v2_mul_v2(sprite_pivot_norm, half_size);
            }

            struct xform sprite_xf = XFORM_POS(sprite_pos);
            sprite_xf = xform_rotate(sprite_xf, sprite_rot);
            sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot));
            sprite_xf = xform_scale(sprite_xf, sprite_size);
            e->sprite_quad_xform = sprite_xf;

            e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));;
            e->sprite_span_name = STR("UNARMED");
            e->sprite_tint = COLOR_WHITE;

            entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
            e->player_max_speed = 4.f;
            e->player_acceleration = 20.0f;
            e->player_aim = V2(0, -1);

            entity_enable_prop(e, ENTITY_PROP_ANIMATING);

            player_ent = e;

            //entity_enable_prop(e, ENTITY_PROP_TEST);
            //entity_enable_prop(e, ENTITY_PROP_TEST_FOLLOW_MOUSE);
        }

        /* Child ent */
        struct entity *parent = player_ent;
        {
            struct v2 pos = V2(0, 0.25);
            struct v2 size = V2(1, 1);
            f32 r = 0;

            struct entity *e = entity_alloc(&G.world.entity_store);
            e->valid = true;
            e->rel_xform = XFORM_TRS(.t = pos, .r = r, .s = size);

            struct v2 sprite_pos = V2(0, 0);
            f32 sprite_rot = 0;
            struct v2 sprite_size = V2(0.5f, 0.5f);
            // struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);

            struct v2 sprite_pivot;
            {
                struct v2 sprite_pivot_norm = V2(0, 0.5);  /* Pivot x & y are each normalized about sprite dimensions. <0, 0> is center, <1, 1> is bottom right corner, etc. */
                struct v2 half_size = v2_mul(sprite_size, 0.5f);
                sprite_pivot = v2_mul_v2(sprite_pivot_norm, half_size);
            }

            struct xform sprite_xf = XFORM_POS(sprite_pos);
            sprite_xf = xform_rotate(sprite_xf, sprite_rot);
            sprite_xf = xform_translate(sprite_xf, v2_neg(sprite_pivot));
            sprite_xf = xform_scale(sprite_xf, sprite_size);
            e->sprite_quad_xform = sprite_xf;

            e->sprite = sprite_tag_from_path(STR("res/graphics/tim.ase"));;
            e->sprite_span_name = STR("UNARMED");
            e->sprite_tint = RGBA_32_F(0.5, 0.5, 0, 1);

            //entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
            //e->player_max_speed = 5.f;
            //e->player_acceleration = 15.0f;

            entity_enable_prop(e, ENTITY_PROP_ANIMATING);

            //entity_enable_prop(e, ENTITY_PROP_TEST);

            entity_link(&G.world.entity_store, parent, e);

            if (sys_rand_u32() % 2 == 0) {
                u64 parent_idx = sys_rand_u32() % entity_store_as_array(&G.world.entity_store).count;
                struct entity *rand_ent = entity_from_handle(&G.world.entity_store, (struct entity_handle) { .idx = parent_idx, .gen = 1 });
                if (rand_ent->valid) {
                    parent = rand_ent;
                } else {
                    parent = e;
                }
            }
        }

        /* Camera ent */
        {
            struct entity *e = entity_alloc(&G.world.entity_store);
            e->valid = true;
            e->rel_xform = XFORM_IDENT;

            entity_enable_prop(e, ENTITY_PROP_CAMERA);
            entity_enable_prop(e, ENTITY_PROP_CAMERA_ACTIVE);
            e->camera_follow = player_ent->handle;

            f32 width = (f32)DEFAULT_CAMERA_WIDTH;
            f32 height = (f32)DEFAULT_CAMERA_HEIGHT;
            e->camera_quad_xform = XFORM_TRS(.s = V2(width, height));
        }

        /* Sound ent */
        {
            struct entity *e = entity_alloc(&G.world.entity_store);
            e->valid = true;
            e->rel_xform = XFORM_POS(V2(-3, -3));

            struct v2 sprite_size = V2(0.25f, 0.25f);
            // struct v2 sprite_size = sprite_sheet_size_meters(sprite_sheet_tag);
            e->sprite_quad_xform = xform_with_scale(XFORM_IDENT, sprite_size);

            e->sprite = sprite_tag_from_path(STR("res/graphics/sound.ase"));;
            e->sprite_tint = RGBA_32_F(1, 1, 0, 1);

            entity_enable_prop(e, ENTITY_PROP_TEST_SOUND_EMITTER);
            e->sound_name = STR("res/sounds/test.mp3");
            e->sound_desc = MIXER_DESC(.flags = MIXER_FLAG_SPATIALIZE);

            entity_enable_prop(e, ENTITY_PROP_TEST);
        }
    }

    ++G.world.tick_id;
    G.world.tick_ts = sys_timestamp();
    G.world.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.world.timescale);
    G.world.time += G.world.dt;
    struct entity_array entities_array =  entity_store_as_array(&G.world.entity_store);

    /* ========================== *
     * Process game cmds
     * ========================== */

    struct game_cmd_array game_cmds = pop_cmds(scratch.arena);
    for (u64 cmd_index = 0; cmd_index < game_cmds.count; ++cmd_index) {
        struct game_cmd cmd = game_cmds.cmds[cmd_index];

        switch (cmd.kind) {
            /* Movement */
            case GAME_CMD_KIND_PLAYER_MOVE: {
                G.world.player_move_dir = cmd.move_dir;
                G.world.player_aim = cmd.aim;
            } break;

            /* Clear level */
            case GAME_CMD_KIND_CLEAR_ALL: {
                logf_info("Clearing level");
                for (u64 i = 0; i < entities_array.count; ++i) {
                    struct entity *ent = &entities_array.entities[i];
                    if (ent->valid) {
                        entity_release(&G.world.entity_store, ent);
                    }
                }
            } break;

            default: break;
        };
    }

    if (v2_len(G.world.player_move_dir) > 1.f) {
        /* Clamp movement magnitude */
        G.world.player_move_dir = v2_norm(G.world.player_move_dir);
    }

    /* ---------------------------------------------------------------------- */
    /* ---------------------------------------------------------------------- */

    /* ========================== *
     * Update entities pre-physics
     * ========================== */

    for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
        struct entity *ent = &entities_array.entities[entity_index];
        if (!ent->valid) continue;

        /* ========================== *
         * Initialize
         * ========================== */

        /* ENTITY_PROP_TEST */
        if (entity_has_prop(ent, ENTITY_PROP_TEST) && !ent->test_initialized) {
            ent->test_initialized = true;
            ent->test_start_rel_xform = ent->rel_xform;
            ent->test_start_sprite_xform = ent->sprite_quad_xform;
        }

        /* ========================== *
         * Update animation
         * ========================== */

        if (entity_has_prop(ent, ENTITY_PROP_ANIMATING)) {
            f64 time_in_frame = ent->animation_time_in_frame + G.world.dt;
            u64 span_frame_offset = ent->animation_frame;

            struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite);
            struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name);
            u64 frame_index = span.start + span_frame_offset;

            struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index);
            while (time_in_frame > frame.duration) {
                time_in_frame -= frame.duration;
                ++frame_index;
                if (frame_index > span.end) {
                    /* Loop animation */
                    frame_index = span.start;
                }
                frame = sprite_sheet_get_frame(sheet, frame_index);
            }
            span_frame_offset = frame_index - span.start;

            ent->animation_time_in_frame = time_in_frame;
            ent->animation_frame = span_frame_offset;
        }

        /* ========================== *
         * Test
         * ========================== */

        /* ENTITY_PROP_TEST */
        if (entity_has_prop(ent, ENTITY_PROP_TEST)) {
            f32 t = ((f32)G.world.time);
            struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3);
            f32 r = t + PI / 2;
            struct v2 s = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1);
            (UNUSED)og;
            (UNUSED)r;
            (UNUSED)s;

            og = v2_add(og, ent->test_start_rel_xform.og);
            r += xform_get_rotation(ent->test_start_rel_xform);
            s = v2_add(s, xform_get_scale(ent->test_start_rel_xform));


            ent->rel_xform.og = og;
            ent->rel_xform = xform_with_rotation(ent->rel_xform, r);
            ent->rel_xform = xform_with_scale(ent->rel_xform, s);
        }
    }

    /* ========================== *
     * Update entity physics
     * ========================== */

    for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
        struct entity *ent = &entities_array.entities[entity_index];
        if (!ent->valid) continue;
        if (ent->parent.gen) continue;  /* Only update parent entities */

        /* ========================== *
         * Player movement
         * ========================== */

        if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
            f32 max_speed = ent->player_max_speed;
            f32 acceleration_rate = ent->player_acceleration;
            acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS);  /* Can't integrate acceleration rate higher than FPS */
            struct v2 target_velocity = v2_mul(G.world.player_move_dir, max_speed);
            struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity);
            ent->acceleration = v2_mul(target_acceleration, acceleration_rate);
        }

        /* ========================== *
         * Integrate acceleration & velocity
         * ========================== */

        {
            f32 dt = (f32)G.world.dt;

            /* Apply acceleration to velocity */
            struct v2 a = v2_mul(ent->acceleration, dt);
            ent->velocity = v2_add(ent->velocity, a);

            /* Apply velocity to position */
            ent->rel_xform.og = v2_add(ent->rel_xform.og, v2_mul(ent->velocity, dt));
        }

        /* ========================== *
         * Player aim
         * ========================== */

        if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
            /* Update aim */
            ent->player_aim = G.world.player_aim;

            /* Update view angle */
            struct v2 ent_pos = ent->rel_xform.og;
            struct v2 look_pos = v2_add(ent_pos, ent->player_aim);
            f32 r = v2_angle_to_point(ent_pos, look_pos) + PI / 2;
            ent->rel_xform = xform_with_rotation(ent->rel_xform, r);
        }

        /* ========================== *
         * Calculate xforms
         * ========================== */

        recalculate_world_xform_recurse(ent);
    }

    /* ========================== *
     * Update entities post-physics
     * ========================== */

    for (u64 entity_index = 0; entity_index < entities_array.count; ++entity_index) {
        struct entity *ent = &entities_array.entities[entity_index];
        if (!ent->valid) continue;

        /* ========================== *
         * Update camera position
         * ========================== */

        /* Camera follow */
        if (entity_has_prop(ent, ENTITY_PROP_CAMERA)) {
            struct entity *follow = entity_from_handle(&G.world.entity_store, ent->camera_follow);

            if (entity_has_prop(follow, ENTITY_PROP_PLAYER_CONTROLLED)) {
#if 0
                /* Regular style camera */
                f32 target_dist_x = 0.5;
                //f32 target_dist_y = target_dist_x * (16.0f / 9.0f);
                f32 target_dist_y = target_dist_x;
                struct v2 target_dir = v2_mul_v2(v2_norm(follow->player_aim), V2(target_dist_x, target_dist_y));
                struct v2 target_pos = v2_add(ent->camera_rel_xform_target.og, target_dir);
                ent->camera_rel_xform_target = ent->rel_xform;
                ent->camera_rel_xform_target.og = target_pos;
#else
                /* "Look" style camera */
                f32 aspect_ratio = 1.0;
                {
                    struct xform quad_xf = xform_mul(ent->world_xform, ent->camera_quad_xform);
                    struct v2 camera_size = xform_get_scale(quad_xf);
                    if (!v2_eq(camera_size, V2(0, 0))) {
                        aspect_ratio = camera_size.x / camera_size.y;
                    }
                }
                f32 ratio_y = 0.33f;
                f32 ratio_x = ratio_y / aspect_ratio;
                struct v2 camera_focus_dir = v2_mul_v2(follow->player_aim, V2(ratio_x, ratio_y));
                struct v2 camera_focus_pos = v2_add(follow->world_xform.og, camera_focus_dir);
                ent->camera_rel_xform_target = ent->rel_xform;
                ent->camera_rel_xform_target.og = camera_focus_pos;
#endif
            }

            /* Lerp camera */
            if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) {
                f32 t = 1 - math_pow(2.f, -20.f * (f32)G.world.dt);
                ent->rel_xform = xform_lerp(ent->rel_xform, ent->camera_rel_xform_target, t);
            } else {
                /* Skip lerp */
                ent->rel_xform = ent->camera_rel_xform_target;
            }
            ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1;

            /* Recalculate xform tree */
            recalculate_world_xform_recurse(ent);
        }

        /* ========================== *
         *  Update sound emitters
         * ========================== */

        if (entity_has_prop(ent, ENTITY_PROP_TEST_SOUND_EMITTER)) {
            struct mixer_desc desc = ent->sound_desc;
            desc.speed = G.world.timescale;

            desc.pos = ent->world_xform.og;
            struct sound *sound = sound_load_async(ent->sound_name, 0);
            b32 played = ent->sound_handle.gen != 0;
            if (sound) {
                if (!played) {
                    ent->sound_handle = mixer_play_ex(sound, desc);
                } else {
                    mixer_track_set(ent->sound_handle, desc);
                }
            }
        }
    }

    /* ---------------------------------------------------------------------- */
    /* ---------------------------------------------------------------------- */

    /* Publish tick */
    publish_game_tick();
    __profframe("Game");

    /* ========================== *
     * End frame cache scopes
     * ========================== */

    sprite_scope_end(sprite_frame_scope);

    scratch_end(scratch);
}

/* ========================== *
 * Game thread
 * ========================== */

INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(game_thread_entry_point, arg)
{
    (UNUSED)arg;
    sys_timestamp_t last_frame_ts = 0;
    f64 target_dt = GAME_FPS > (0) ? (1.0 / GAME_FPS) : 0;
    while (!atomic_i32_eval(&G.game_thread_shutdown)) {
        __profscope(game_update_w_sleep);
        sleep_frame(last_frame_ts, target_dt);
        last_frame_ts = sys_timestamp();
        game_update();
    }
}

/* ========================== *
 * Interface
 * ========================== */

void game_get_latest_tick(struct world *dest)
{
    sys_mutex_lock(&G.published_tick_mutex);
    {
        world_copy_replace(dest, &G.published_tick);
    }
    sys_mutex_unlock(&G.published_tick_mutex);
}

u64 game_get_latest_tick_id(void)
{
    return atomic_u64_eval(&G.published_tick_id);
}

void game_push_cmds(struct game_cmd_array cmd_array)
{
    push_cmds(cmd_array);
}
