#include "colla/colla.c"

#if COLLA_WIN || COLLA_LINUX
#define SOKOL_D3D11
#elif COLLA_LINUX
#define SOKOL_VULKAN
#else
#error "unsupported os"
#endif

#include "libs/sokol_app.h"
#include "libs/sokol_gfx.h"
#include "libs/sokol_time.h"
#include "libs/sokol_glue.h"
#include "libs/handmademath.h"

#include "vecmath.h"

#include "gen/obj.h"

#include "utils.c"
#include "obj.c"
#include "camera.c"

////////////////////////////////

arena_t frame_arena = {0};
sg_pipeline pip = {0};
// sg_bindings binds = {0};
sg_pass_action pass_action = {0};
scene_t scene = {0};
camera_t camera = {0};
u64 time_last = 0;

vs_params_t vs_params = {0};

////////////////////////////////

void sokol_log_cb(
    const char *tag,
    u32 log_level,
    u32 log_item_id,
    const char *message_or_null,
    u32 line_nr,
    const char *filename_or_null,
    void *user_data
) {
    os_log_level_e levels[] = {
        LOG_FATAL,
        LOG_ERR,
        LOG_WARN,
        LOG_INFO,
    };
    os_log_print(
        filename_or_null, 
        line_nr, 
        levels[log_level], 
        "(%s): %s", 
        tag, 
        message_or_null
    );
}

void app_init(void) {
    sg_setup(&(sg_desc){
        .environment = sglue_environment(),
        .logger.func = sokol_log_cb,
        .d3d11.shader_debugging = true,
    });
    stm_setup();

    colla_init(COLLA_OS);

    sapp_lock_mouse(true);

    frame_arena = arena_make(ARENA_VIRTUAL, GB(1));

    // scene = obj_load_gltf(frame_arena, strv("data/metro.glb"));
    scene = obj_load_gltf(frame_arena, strv("data/scene.glb"));
    camera = cam_init();

    sg_shader shd = sg_make_shader(obj_shader_desc(sg_query_backend()));

    pip = sg_make_pipeline(&(sg_pipeline_desc){
        .shader = shd,
        .index_type = SG_INDEXTYPE_UINT16,
        // .cull_mode = SG_CULLMODE_BACK,
        .depth = {
            .compare = SG_COMPAREFUNC_LESS_EQUAL,
            .write_enabled = true,
        },
        .layout = {
            .attrs = {
                [0].format = SG_VERTEXFORMAT_FLOAT3,
                [1].format = SG_VERTEXFORMAT_FLOAT3,
                [2].format = SG_VERTEXFORMAT_FLOAT2,
                [3].format = SG_VERTEXFORMAT_FLOAT2,
            },
        },
    });

    pass_action = (sg_pass_action){
        .colors[0] = {
            .load_action = SG_LOADACTION_CLEAR,
            .clear_value = { 0.1f, 0.1f, 0.1f, 1.f },
        },
    };

    time_last = stm_now();
}

void app_frame(void) {
    u64 dt_ticks = stm_laptime(&time_last);
    float dt = (float)stm_sec(dt_ticks);

    utils_update();
    cam_update(&camera, dt);

    float aspect_ratio = sapp_widthf() / sapp_heightf();

    HMM_Mat4 view = cam_view(&camera);
    HMM_Mat4 proj = cam_proj(&camera);
    HMM_Mat4 model = HMM_Translate(HMM_V3(0, 0, -10));

    // HMM_Mat4 vp = HMM_Mul(HMM_Mul(proj, view), model);
    // HMM_Mat4 vp = HMM_Mul(proj, view);

    vs_params.mvp = HMM_Mul(proj, view);
    vs_params.cam_pos = camera.pos;

    arena_rewind(&frame_arena, 0);

    sg_begin_pass(&(sg_pass){ .action = pass_action, .swapchain = sglue_swapchain() });
    sg_apply_pipeline(pip);
    sg_apply_uniforms(0, SG_RANGE_REF(vs_params));
        for (int i = 0; i < scene.object_count; ++i) {
            sg_apply_bindings(&scene.objects[i].bindings);
            sg_draw(0, scene.objects[i].draw_count, 1);
        }
    sg_end_pass();
    sg_commit();
}

void app_event(const sapp_event *e) {
    switch (e->type) {
        case SAPP_EVENTTYPE_KEY_UP:
        case SAPP_EVENTTYPE_KEY_DOWN:
            if (e->key_code == SAPP_KEYCODE_ESCAPE) {
                sapp_quit();
                return;
            }
            set_key_state(e->key_code, e->type == SAPP_EVENTTYPE_KEY_DOWN);
            break;
        case SAPP_EVENTTYPE_MOUSE_MOVE:
            set_mouse_pos_and_delta(e->mouse_x, e->mouse_y, e->mouse_dx, e->mouse_dy);
            break;
    }
}

void app_cleanup(void) {
}

sapp_desc sokol_main(int argc, char* argv[]) {
    return (sapp_desc) {
        .width = 640,
        .height = 480,
        .window_title = "engine",
        .logger.func = sokol_log_cb,
        .init_cb = app_init,
        .frame_cb = app_frame,
        .cleanup_cb = app_cleanup,
        .event_cb = app_event,
        .win32 = {
            .console_attach = true,
        },
    };
}

#define SOKOL_IMPL
#define STB_IMAGE_IMPLEMENTATION

#include "libs/sokol_app.h"
#include "libs/sokol_gfx.h"
#include "libs/sokol_time.h"
#include "libs/sokol_glue.h"
#include "libs/stb_image.h"
