#include <algorithm>
#include <stdio.h>
#include <stdint.h>
#include <bit>
#include <SDL3/SDL.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_impl_sdl3.h>
#include <tracy/Tracy.hpp>
#include <tracy/TracyC.h>
#include <webgpu/webgpu.h>
#include "smol-atlas.h"

#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "renderer.h"
#include "defer.h"
#include "stb_image.h"
#include "change_directory.h"

#include "../assets/maps/worlds.h"

#pragma clang diagnostic ignored "-Waddress-of-temporary"

using namespace glm;

#define ORG ("ammerhai")
#define APP ("mikemon")

#define ASSETS_PATH "./assets/"

#define NEAR_PLANE (0.01f)

#define TILE_SIZE            (32)
#define TILE_ATLAS_SIZE      (512)
#define PLACEABLE_ATLAS_SIZE (256)

static SDL_Window *window;
static R_Texture  framebuffer;

static R_Texture player_texture;
static R_Texture character_shadow_texture;
static R_Texture tile_textures_atlas;
static R_Texture tile_textures_atlas_imgui;
static R_Texture placeable_textures_atlas;
static R_Texture placeable_textures_atlas_imgui;

static R_Buffer view_projection_matrix_buffer;
static R_Buffer per_frame_buffer;
static R_Buffer tint_color_buffer;

static R_Buffer player_instance_buffer;
static R_Buffer placeable_instance_buffer;
static R_Buffer tile_uvs_buffer;
static R_Buffer placeable_uvs_buffer;
static R_Buffer placeable_sizes_and_offsets_buffer;

static i32vec2 window_size = { 1280, 720 };

static MIX_Mixer *mixer;
static MIX_Track *music_track;
static MIX_Track *sfx_track;
static MIX_Audio *music_setting_off_piano;

static float volume_master = 50.0f;
static float volume_music  = 50.0f;
static float volume_sfx    = 50.0f;

static bool Running = true;

static float camera_fovy_degrees = 31.0f;
static float camera_tilt         = 10.0f;
static float camera_distance     = 13.5f;

static mat4x4 view_matrix;
static mat4x4 inverse_view_matrix;

static mat4x4 projection_matrix;
static mat4x4 inverse_projection_matrix; 

static SDL_Time last_time;
static SDL_Time current_time;

static bool         enable_time_tints = true;
static bool         use_actual_time = true;
static SDL_DateTime calendar_time;

static vec2 mouse_pos;

static bool in_editor;
static bool show_grid = true;

static vec2  editor_camera_position;
static float editor_camera_distance = 30.0f;

static bool show_demo_window;
static bool show_tile_picker;
static bool show_placeable_editor;
static bool show_world_editor;
static bool show_settings;

static float character_speed = 4.0f;

#define log_error(...) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, __VA_ARGS__)

float remap(float in_a, float in_b, float out_a, float out_b, float v) {
    return mix(out_a, out_b, (v - in_a) / (in_b - in_a));
}

vec2 remap(vec2 in_a, vec2 in_b, vec2 out_a, vec2 out_b, vec2 v) {
    return mix(out_a, out_b, (v - in_a) / (in_b - in_a));
}

mat4x4 view(vec3 player_pos, float tilt, float camera_distance) {
    float s = sinf(tilt);
    float c = cosf(tilt);
    
    return {
        1,  0, 0,                      -player_pos.x,
        0,  c, s,                  c * -player_pos.y,
        0, -s, c, s * player_pos.y - camera_distance,
        0,  0, 0,                                  1,
    };
}

mat4x4 inverse_view(vec3 player_pos, float tilt, float camera_distance) {
    float s = sinf(tilt);
    float c = cosf(tilt);
    
    return {
        1, 0,  0,                        player_pos.x,
        0, c, -s, -s * camera_distance + player_pos.y,
        0, s,  c,                 c * camera_distance,
        0, 0,  0,                                   1,
    };
}

mat4x4 projection(float fovy, float aspect, float near) {
    float g = 1.0 / tanf(fovy * 0.5);

    return {
        g / aspect, 0,  0,    0,
                 0, g,  0,    0,
                 0, 0,  0, near,
                 0, 0, -1,    0,
    };
}

mat4x4 inverse_projection(float fovy, float aspect, float near) {
    float g = 1.0 / tanf(fovy * 0.5);

    return {
        aspect / g,     0,        0,  0,
                 0, 1 / g,        0,  0,
                 0,     0,        0, -1,
                 0,     0, 1 / near,  0,
    };
}

#define MAX_TINT_TIMES 32
static int num_used_tint_times = 4;
static int time_tints_times[MAX_TINT_TIMES][3] = {
    { 4,  0, 0 },
    { 9,  0, 0 },
    { 19, 0, 0 },
    { 21, 0, 0 },
};

static vec3 time_tints[MAX_TINT_TIMES] = {
    { 0.314f, 0.369f, 0.455f },
    { 1.0f,   0.891f, 0.868f },
    { 1.0f,   0.465f, 0.373f },
    { 0.314f, 0.369f, 0.455f },
};

enum Settings_Category {
    SETTINGS_UNKNOWN,
    SETTINGS_AUDIO,
};

struct Instance {
    vec2 pos;
    uint32_t sprite_selection;
};

static Instance player_instance = {{ 0.0f, 0.0f }};

struct Placeable {
    u32vec2 pos;
    Uint32  kind;
};

struct Map {
    i32vec2 size;
    Uint16 *tiles;

    Uint32     num_placeables;
    Placeable *placeables;

    char name[64];

    Uint8 *static_collision_grid;
    Uint8 *water_collision_grid;

    R_Texture texture;
    R_Buffer  placeables_buffer;
};

static Uint32 num_loaded_maps;
static Map *loaded_maps;
static Map *current_map;

struct alignas(16) PerFrame {
    i32vec2 drag_start;
    i32vec2 mouse;
    vec2    grid_offset;
    Uint32  grid_width;
    Uint32  map_width;
    Uint32  grid_height;
};

static PerFrame per_frame = {};

typedef enum : Uint8 {
    DIR_DOWN,
    DIR_UP,
    DIR_LEFT,
    DIR_RIGHT,
} Direction;

struct Player {
    i32vec2 position;
    i32vec2 target_position;

    Direction direction;

    bool is_moving;
    vec2 visual_position;
    Uint64 animation_time;
};

static Player player;

typedef enum : Uint8 {
    TILEKIND_ERROR = 0,
    TILEKIND_NONE  = 1,
    TILEKIND_GRASS = 2,
    TILEKIND_DIRT  = 3,
    TILEKIND_WATER = 4,
} TileKind;

#define TILE_CORNER_INFO(rotation, top_left, top_right, bottom_right, bottom_left) (rotation), std::rotl((Uint32)(((TILEKIND_##top_left) << 24) | ((TILEKIND_##top_right) << 16) | ((TILEKIND_##bottom_right) << 8) | (TILEKIND_##bottom_left)), (rotation) * 8)

typedef struct {
    Uint16      serialization_id;
    const char *asset_path;

    Uint8  rotation;
    Uint32 corner_info;
} TileInfo;

// TILE_CORNER_INFO ORDER:
//     0         1         2         3
// +---+---+ +---+---+ +---+---+ +---+---+
// | 1 | 2 | | 2 | 3 | | 3 | 4 | | 4 | 1 |
// +---+---+ +---+---+ +---+---+ +---+---+
// | 4 | 3 | | 1 | 4 | | 2 | 1 | | 3 | 2 |
// +---+---+ +---+---+ +---+---+ +---+---+

static TileInfo tile_infos[] = {
    { 1, "tiles/error.png", TILE_CORNER_INFO(0, ERROR, ERROR, ERROR, ERROR) },
    { 0, "tiles/empty.png", TILE_CORNER_INFO(0, NONE,  NONE,  NONE,  NONE ) },

    // GRASS
    { 2, "tiles/grass_3.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) },
    { 3, "tiles/grass_1.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) },
    { 4, "tiles/grass_2.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) },
    { 5, "tiles/grass_4.png", TILE_CORNER_INFO(0, GRASS, GRASS, GRASS, GRASS) },

    // DIRT
    { 6, "tiles/dirt_3.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) },
    { 7, "tiles/dirt_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) },
    { 8, "tiles/dirt_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, DIRT, DIRT ) },

    // WATER
    {  9, "tiles/water_1.png", TILE_CORNER_INFO(0, WATER, WATER, WATER, WATER) },
    { 10, "tiles/water_2.png", TILE_CORNER_INFO(0, WATER, WATER, WATER, WATER) },

    // GRASS / DIRT
    { 11, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) },
    { 12, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) },
    { 13, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) },
    { 14, "tiles/grass_dirt_1.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) },

    { 15, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) },
    { 16, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) },
    { 17, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) },
    { 18, "tiles/grass_dirt_2.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) },

    { 19, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, GRASS) },
    { 20, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, GRASS) },
    { 21, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, GRASS) },
    { 22, "tiles/grass_dirt_3.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, GRASS) },

    { 23, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, DIRT ) },
    { 24, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, DIRT ) },
    { 25, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, DIRT ) },
    { 26, "tiles/grass_dirt_outer_corner_1.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, DIRT ) },

    { 27, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, DIRT ) },
    { 28, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, DIRT ) },
    { 29, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, DIRT ) },
    { 30, "tiles/grass_dirt_outer_corner_2.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, DIRT ) },

    { 31, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) },
    { 32, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) },
    { 33, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) },
    { 34, "tiles/grass_dirt_inner_corner_1.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) },

    { 35, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) },
    { 36, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) },
    { 37, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) },
    { 38, "tiles/grass_dirt_inner_corner_2.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) },

    { 39, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, GRASS) },
    { 40, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, GRASS) },
    { 41, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(2, GRASS, DIRT, GRASS, GRASS) },
    { 42, "tiles/grass_dirt_inner_corner_3.png", TILE_CORNER_INFO(3, GRASS, DIRT, GRASS, GRASS) },

    { 43, "tiles/grass_dirt_two_corner.png", TILE_CORNER_INFO(0, GRASS, DIRT, GRASS, DIRT ) },
    { 44, "tiles/grass_dirt_two_corner.png", TILE_CORNER_INFO(1, GRASS, DIRT, GRASS, DIRT ) },

    // GRASS / WATER
    { 47, "tiles/water_grass.png", TILE_CORNER_INFO(0, WATER, WATER, GRASS, GRASS) },
    { 48, "tiles/water_grass.png", TILE_CORNER_INFO(1, WATER, WATER, GRASS, GRASS) },
    { 49, "tiles/water_grass.png", TILE_CORNER_INFO(2, WATER, WATER, GRASS, GRASS) },
    { 50, "tiles/water_grass.png", TILE_CORNER_INFO(3, WATER, WATER, GRASS, GRASS) },

    { 51, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(0, WATER, WATER, GRASS, WATER) },
    { 52, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(1, WATER, WATER, GRASS, WATER) },
    { 53, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(2, WATER, WATER, GRASS, WATER) },
    { 54, "tiles/water_water_water_grass.png", TILE_CORNER_INFO(3, WATER, WATER, GRASS, WATER) },

    { 55, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, GRASS) },
    { 56, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, GRASS) },
    { 57, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(2, GRASS, WATER, GRASS, GRASS) },
    { 58, "tiles/grass_water_grass_grass.png", TILE_CORNER_INFO(3, GRASS, WATER, GRASS, GRASS) },

    { 59, "tiles/grass_water_grass_water.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, WATER) },
    { 60, "tiles/grass_water_grass_water.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, WATER) },

    // DIRT / WATER
    { 61, "tiles/dirt_water.png", TILE_CORNER_INFO(0, WATER, WATER, DIRT, DIRT) },
    { 62, "tiles/dirt_water.png", TILE_CORNER_INFO(1, WATER, WATER, DIRT, DIRT) },
    { 63, "tiles/dirt_water.png", TILE_CORNER_INFO(2, WATER, WATER, DIRT, DIRT) },
    { 64, "tiles/dirt_water.png", TILE_CORNER_INFO(3, WATER, WATER, DIRT, DIRT) },

    { 65, "tiles/water_water_water_dirt.png", TILE_CORNER_INFO(0, WATER, WATER, DIRT, WATER) },
    { 66, "tiles/water_water_water_dirt.png", TILE_CORNER_INFO(1, WATER, WATER, DIRT, WATER) },
    { 67, "tiles/water_water_water_dirt.png", TILE_CORNER_INFO(2, WATER, WATER, DIRT, WATER) },
    { 68, "tiles/water_water_water_dirt.png", TILE_CORNER_INFO(3, WATER, WATER, DIRT, WATER) },

    { 69, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, DIRT) },
    { 70, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, DIRT) },
    { 71, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(2, DIRT, WATER, DIRT, DIRT) },
    { 72, "tiles/dirt_water_dirt_dirt.png", TILE_CORNER_INFO(3, DIRT, WATER, DIRT, DIRT) },

    { 73, "tiles/dirt_water_dirt_water.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, WATER) },
    { 74, "tiles/dirt_water_dirt_water.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, WATER) },

    // GRASS / DIRT / WATER
    { 75, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(0, DIRT, WATER, GRASS, WATER) },
    { 76, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(1, DIRT, WATER, GRASS, WATER) },
    { 77, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(2, DIRT, WATER, GRASS, WATER) },
    { 78, "tiles/dirt_water_water_grass.png", TILE_CORNER_INFO(3, DIRT, WATER, GRASS, WATER) },

    { 79, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(0, WATER, WATER, DIRT, GRASS) },
    { 80, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(1, WATER, WATER, DIRT, GRASS) },
    { 81, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(2, WATER, WATER, DIRT, GRASS) },
    { 82, "tiles/water_water_grass_dirt.png", TILE_CORNER_INFO(3, WATER, WATER, DIRT, GRASS) },

    { 83, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(0, WATER, WATER, GRASS, DIRT) },
    { 84, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(1, WATER, WATER, GRASS, DIRT) },
    { 85, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(2, WATER, WATER, GRASS, DIRT) },
    { 86, "tiles/water_water_dirt_grass.png", TILE_CORNER_INFO(3, WATER, WATER, GRASS, DIRT) },

    { 87, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(0, GRASS, GRASS, WATER, DIRT) },
    { 88, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(1, GRASS, GRASS, WATER, DIRT) },
    { 89, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(2, GRASS, GRASS, WATER, DIRT) },
    { 90, "tiles/grass_grass_dirt_water.png", TILE_CORNER_INFO(3, GRASS, GRASS, WATER, DIRT) },

    { 91, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(0, GRASS, GRASS, DIRT, WATER) },
    { 92, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(1, GRASS, GRASS, DIRT, WATER) },
    { 93, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(2, GRASS, GRASS, DIRT, WATER) },
    { 94, "tiles/grass_grass_water_dirt.png", TILE_CORNER_INFO(3, GRASS, GRASS, DIRT, WATER) },

    { 95, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(0, GRASS, WATER, GRASS, DIRT) },
    { 96, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(1, GRASS, WATER, GRASS, DIRT) },
    { 97, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(2, GRASS, WATER, GRASS, DIRT) },
    { 98, "tiles/grass_water_dirt_grass.png", TILE_CORNER_INFO(3, GRASS, WATER, GRASS, DIRT) },
    
    { 99,  "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(0, DIRT, WATER, DIRT, GRASS) },
    { 100, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(1, DIRT, WATER, DIRT, GRASS) },
    { 101, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(2, DIRT, WATER, DIRT, GRASS) },
    { 102, "tiles/dirt_water_grass_dirt.png", TILE_CORNER_INFO(3, DIRT, WATER, DIRT, GRASS) },

    { 103, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(0, DIRT, DIRT, WATER, GRASS) },
    { 104, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(1, DIRT, DIRT, WATER, GRASS) },
    { 105, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(2, DIRT, DIRT, WATER, GRASS) },
    { 106, "tiles/dirt_dirt_grass_water.png", TILE_CORNER_INFO(3, DIRT, DIRT, WATER, GRASS) },

    { 107, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(0, DIRT, DIRT, GRASS, WATER) },
    { 108, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(1, DIRT, DIRT, GRASS, WATER) },
    { 109, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(2, DIRT, DIRT, GRASS, WATER) },
    { 110, "tiles/dirt_dirt_water_grass.png", TILE_CORNER_INFO(3, DIRT, DIRT, GRASS, WATER) },
};

static vec4 tile_uvs[SDL_arraysize(tile_infos)];

struct PlaceableInfo {
    Uint16      serialization_id;
    const char *asset_path;
    vec2        visual_offset;
};

static PlaceableInfo placeable_infos[] = {
    { 0, "decorations/tree_standard_tile.png",       vec2(0, 0.25) },
    { 1, "decorations/Stone.png",                    vec2(0, 0) },
    { 2, "decorations/stone_grass.png",              vec2(0, 0) },
};

static vec4 placeable_uvs              [SDL_arraysize(placeable_infos)];
static vec4 placeable_sizes_and_offsets[SDL_arraysize(placeable_infos)];

static Sint32 selected_placeable = -1;

static Sint32 selected_tile_kind = -1;
static Sint32 selected_tile      = -1;

static bool dragging_tile_change;
static bool dragging_camera_change;
static vec2 drag_start_pos;

static bool      queued_movement;
static Direction queued_movement_direction;

static R_Texture create_texture(const char *path) {
    char path_to_load[256] = ASSETS_PATH;
    SDL_strlcat(path_to_load, path, SDL_arraysize(path_to_load));

    int width = 0, height = 0, channels = 0; 
    stbi_uc *data = stbi_load(path_to_load, &width, &height, &channels, 0);
    if (!data) {
        log_error("Failed to load texture (\"%s\").", path_to_load);
        return NULL;
    }

    R_Texture result = renderer_texture_create(channels == 4 ? R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB : R_TEXTURE_FORMAT_R8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, width, height, data, path);
    if (!result) {
        log_error("Failed to load texture (\"%s\").", path_to_load);
        stbi_image_free(data);
        return NULL;
    }
    stbi_image_free(data);

    return result;
}

static Uint32 get_corner_info(Map *world, Sint32 tile_pos_x, Sint32 tile_pos_y) {
    if (tile_pos_x < 0 || tile_pos_x >= world->size.x || tile_pos_y < 0 || tile_pos_y >= world->size.y)
        return 0;

    return tile_infos[world->tiles[tile_pos_y * world->size.x + tile_pos_x]].corner_info;
}

static Uint32 find_matching_tile(Uint32 corner_info) {
    for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) {
        if (corner_info == tile_infos[i].corner_info)
            return i;
    }

    return 0;
}

static void recalculate_static_collision_grid(Map *world) {
    if (world->static_collision_grid) SDL_free(world->static_collision_grid);
    world->static_collision_grid = (Uint8 *)SDL_calloc((world->size.x - 1) * (world->size.y - 1), sizeof(*world->static_collision_grid));

    for (int i = 0; i < world->num_placeables; i++) {
        u32vec2 pos = world->placeables[i].pos;
        world->static_collision_grid[pos.y * (world->size.x - 2) + pos.x] = 1;
    }
}

static void recalculate_water_collision_grid(Map *world) {
    if (world->water_collision_grid) SDL_free(world->water_collision_grid);
    world->water_collision_grid = (Uint8 *)SDL_calloc((world->size.x - 1) * (world->size.y - 1), sizeof(*world->water_collision_grid));

    for (Sint32 y = 0; y < world->size.y - 2; y++){
        for (Sint32 x = 0; x < world->size.x - 2; x++) {
            Uint32 grid_corner_info = 
                (get_corner_info(world, x + 0, y + 0) & (0xff << 16)) |
                (get_corner_info(world, x + 1, y + 0) & (0xff << 24)) |
                (get_corner_info(world, x + 0, y + 1) & (0xff <<  8)) |
                (get_corner_info(world, x + 1, y + 1) & (0xff <<  0));

            world->water_collision_grid[y * (world->size.x - 2) + x] = grid_corner_info == ((TILEKIND_WATER << 24) | (TILEKIND_WATER << 16) | (TILEKIND_WATER << 8) | (TILEKIND_WATER << 0));
        }
    }
}

static bool save_worlds_list() {
    const char *path = ASSETS_PATH "maps/worlds.h";

    SDL_IOStream *file = SDL_IOFromFile(path, "wb");
    if (!file) {
        log_error("Failed to open world list file for writing.");
        return false;
    }
    defer(SDL_CloseIO(file));

    SDL_IOprintf(file, "#ifndef WORLDS_H\n#define WORLDS_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif // __cplusplus\n\nstatic const char *world_names[] = {\n");

    for (int i = 0; i < num_loaded_maps; i++) {
        SDL_IOprintf(file, "    \"%.*s\",\n", (int)SDL_arraysize(loaded_maps[i].name), loaded_maps[i].name);
    }

    SDL_IOprintf(file, "};\n\n#ifdef __cplusplus\n}\n#endif // __cplusplus\n\n#endif // WORLDS_H\n");

    if(!SDL_FlushIO(file)) {
        log_error("Failed to flush data to world list file.");
        return false;   
    };

    return true;
}

#define MAP_FILE_VERSION (3u)

static bool save_map(Map *map) {
    char path[256] = ASSETS_PATH "maps/";
    SDL_strlcat(path, map->name, SDL_arraysize(path));
    SDL_strlcat(path, ".sv", SDL_arraysize(path));

    SDL_IOStream *file = SDL_IOFromFile(path, "wb");
    if (!file) {
        log_error("Failed to open map file for writing.");
        return false;
    }
    defer(SDL_CloseIO(file));

    if (!SDL_WriteU32LE(file, MAP_FILE_VERSION)) {
        log_error("Failed to write version to map file.");
        return false;
    }

    if (!SDL_WriteS32LE(file, map->size.x)) {
        log_error("Failed to write width to map file.");
        return false;
    }

    if (!SDL_WriteS32LE(file, map->size.y)) {
        log_error("Failed to write height to map file.");
        return false;
    }

    for (int i = 0; i < map->size.x * map->size.y; i++) {
        Uint16 id = tile_infos[map->tiles[i]].serialization_id;

        if (!SDL_WriteU16LE(file, id)) {
            log_error("Failed to write tile to map file.");
            return false;
        }
    }

    if (!SDL_WriteU32LE(file, map->num_placeables)) {
        log_error("Failed to write num_placeable to map file.");
        return false;
    }

    for (int i = 0; i < map->num_placeables; i++) {
        Uint16 id = placeable_infos[map->placeables[i].kind].serialization_id;

        if (!SDL_WriteU16LE(file, id)) {
            log_error("Failed to write placeables kind to map file.");
            return false;
        }

        if (!SDL_WriteU32LE(file, map->placeables[i].pos.x)) {
            log_error("Failed to write placeables pos.x to map file.");
            return false;
        }

        if (!SDL_WriteU32LE(file, map->placeables[i].pos.y)) {
            log_error("Failed to write placeables pos.y to map file.");
            return false;
        }
    }

    if(!SDL_FlushIO(file)) {
        log_error("Failed to flush data to map file.");
        return false;   
    };

    SDL_Log("Saved map file.");
    return true;
}

static bool load_map(const char *name, Map *result) {
    char path[256] = ASSETS_PATH "maps/";
    SDL_strlcat(path, name, SDL_arraysize(path));
    SDL_strlcat(path, ".sv", SDL_arraysize(path));

    SDL_IOStream *file = SDL_IOFromFile(path, "rb");
    if (!file) {
        log_error("Failed to open map file for reading.");
        return false;
    }
    defer(SDL_CloseIO(file));
    SDL_memcpy(result->name, name, SDL_min(strlen(name), SDL_arraysize(result->name) - 1));

    Uint32 version = 0;
    if (!SDL_ReadU32LE(file, &version)) {
        log_error("Failed read version from map file.");
        return false;
    }

    if (!version) {
        log_error("Map file version for map '%s' was 0 which is not valid.", name);
        return false;
    }

    if (version > 3) {
        log_error("Map file version (%u) is higher than the highest supported.", version);
        return false;
    }

    if (!SDL_ReadS32LE(file, &result->size.x)) {
        log_error("Failed read width from map file.");
        return false;
    }

    if (!SDL_ReadS32LE(file, &result->size.y)) {
        log_error("Failed read height from map file.");
        return false;
    }

    result->tiles = (Uint16 *)SDL_malloc(result->size.x * result->size.y * sizeof(Uint16));

    for (int i = 0; i < result->size.x * result->size.y; i++) {
        if (version >= 2) {
            Uint16 serialization_id = 0;
            if (!SDL_ReadU16LE(file, &serialization_id)) {
                free(result->tiles);
                return false;
            }

            Uint16 info_index = 0;
            for (int i = 0; i < SDL_arraysize(tile_infos); i++) {
                if (tile_infos[i].serialization_id == serialization_id) {
                    info_index = i;
                    break;
                }
            }

            result->tiles[i] = info_index;
        } else {
            assert(false && "Tried to load an unsupported map version.");
            log_error("Tried to load an unsupported map version. Aborting.");
            free(result->tiles);
            return false;
        }
    }

    if (version >= 3) {
        if (!SDL_ReadU32LE(file, &result->num_placeables)) {
            log_error("Failed to read num_placeables from map file.");
            free(result->tiles);
            return false;
        }

        result->placeables = (Placeable *)SDL_malloc(result->num_placeables * sizeof(Placeable));

        for (int i = 0; i < result->num_placeables; i++) {
            Uint16 serialization_id = 0;
            if (!SDL_ReadU16LE(file, &serialization_id)) {
                free(result->tiles);
                free(result->placeables);
                return false;
            }

            Uint16 info_index = 0;
            for (int i = 0; i < SDL_arraysize(placeable_infos); i++) {
                if (placeable_infos[i].serialization_id == serialization_id) {
                    info_index = i;
                    break;
                }
            }

            result->placeables[i].kind = info_index;

            if (!SDL_ReadU32LE(file, &result->placeables[i].pos.x)) {
                free(result->tiles);
                free(result->placeables);
                log_error("Failed to read placeable pos.x from map file.");
                return false;
            }

            if (!SDL_ReadU32LE(file, &result->placeables[i].pos.y)) {
                free(result->tiles);
                free(result->placeables);
                log_error("Failed to read placeable pos.y from map file.");
                return false;
            }
        }
    }

    char buffer_name[256] = "Map ";
    SDL_strlcat(buffer_name, result->name, SDL_arraysize(buffer_name));

    result->texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_STORAGE, false, result->size.x, result->size.y, result->tiles, "map_texture");

    if (result->num_placeables) {
        result->placeables_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, result->num_placeables * sizeof(*result->placeables), result->placeables, "placeables_buffer");
    }

    recalculate_static_collision_grid(result);
    recalculate_water_collision_grid(result);

    SDL_Log("Loaded map file.");
    return true;
}

static void unload_map(Map *map) {
    map->size = i32vec2(0, 0);
    free(map->tiles);
    SDL_free(map->name);
    renderer_texture_destroy(map->texture);
}

static void change_map_size(Map *map, char direction, int amount) {
    renderer_texture_destroy(map->texture);

    Uint16*old_map        = map->tiles;
    Sint32 old_map_width  = map->size.x;
    Sint32 old_map_height = map->size.y;

    Sint32 new_x_offset = 0;
    Sint32 new_y_offset = 0;
    Sint32 old_x_offset = 0;
    Sint32 old_y_offset = 0;

    Sint32 to_fill_width  = map->size.x;
    Sint32 to_fill_height = map->size.y;
    Sint32 to_fill_x_offset = 0;
    Sint32 to_fill_y_offset = 0;

    if (direction == 'W') {
        player.position.x = player.position.x + amount;
        map->size.x += amount;
        to_fill_width = amount;
        
        if (amount < 0) 
            old_x_offset = -amount;
        else 
            new_x_offset = amount;
    }

    if (direction == 'N') {
        player.position.y = player.position.y + amount;
        map->size.y += amount;
        to_fill_height = amount;

        if (amount < 0)
            old_y_offset = -amount;
        else 
            new_y_offset = amount;
    }

    if (direction == 'E') {
        map->size.x += amount;
        to_fill_width = amount;
        to_fill_x_offset = old_map_width;
    }

    if (direction == 'S') {
        map->size.y += amount;
        to_fill_height = amount;
        to_fill_y_offset = old_map_height;
    }

    map->tiles = (Uint16 *)malloc(map->size.x * map->size.y * sizeof(Uint16));

    for (int y = 0; y < min(old_map_height, map->size.y); y++) {
        for (int x = 0; x < min(old_map_width, map->size.x); x++) {
            map->tiles[(y + new_y_offset) * map->size.x + (x + new_x_offset)] = old_map[(y + old_y_offset) * old_map_width + (x + old_x_offset)];
        }
    }
    free(old_map);

    for (int y = 0; y < to_fill_height; y++) {
        for (int x = 0; x < to_fill_width; x++) {
            map->tiles[(y + to_fill_y_offset) * map->size.x + (x + to_fill_x_offset)] = 1;
        }
    }

    player.position = clamp(player.position, i32vec2(0, 0), map->size - 2);

    map->texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_SAMPLED, false, map->size.x, map->size.y, map->tiles, "map_texture");
    recalculate_static_collision_grid(map);
    recalculate_water_collision_grid(map);
}

static void blit(char *dst, Sint32 dst_pitch, Sint32 dst_x, Sint32 dst_y, char *src, Sint32 src_pitch, Sint32 width, Sint32 height, int components = 4) {
    for (Sint32 y = 0; y < height; y++)
        memmove(&dst[((dst_y + y) * dst_pitch + dst_x) * components], &src[y * src_pitch * components], width * components);
}

static bool SelectableTile(const char *label, bool selected, Uint32 tile_index, const ImVec2& image_size) {
    const ImGuiContext *context = ImGui::GetCurrentContext();
    const ImVec2 padding = context->Style.FramePadding;

    bool pressed = ImGui::Selectable(label, selected, 0, image_size + padding * 2.0f);

    ImVec2 min = ImGui::GetItemRectMin();
    ImVec2 max = ImGui::GetItemRectMax();

    vec4 uv = tile_uvs[tile_index] / (float)TILE_ATLAS_SIZE;
    context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)ImGui_ImplRenderer_GetTextureID(tile_textures_atlas_imgui), min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv.x, uv.y), ImVec2(uv.z, uv.y), ImVec2(uv.z, uv.w), ImVec2(uv.x, uv.w));

    return pressed;
}

static bool SelectableImage(const char *label, R_Texture texture, ImVec2 image_size, vec2 uv_min, vec2 uv_max, bool selected) {
    const ImGuiContext *context = ImGui::GetCurrentContext();
    const ImVec2 padding = context->Style.FramePadding;

    bool pressed = ImGui::Selectable(label, selected, 0, ImVec2(image_size.x, image_size.y) + padding * 2.0f);

    ImVec2 min = ImGui::GetItemRectMin();
    ImVec2 max = ImGui::GetItemRectMax();

    context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)ImGui_ImplRenderer_GetTextureID(texture), min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv_min.x, uv_min.y), ImVec2(uv_max.x, uv_min.y), ImVec2(uv_max.x, uv_max.y), ImVec2(uv_min.x, uv_max.y));

    return pressed;
}

static ImVec4 linear_to_sRGB(ImVec4 linear) {
    float red   = linear.x <= 0.0031308f ? 12.92f * linear.x : 1.055f * powf(linear.x, 1.0f / 2.4f) - 0.055;
    float green = linear.y <= 0.0031308f ? 12.92f * linear.y : 1.055f * powf(linear.y, 1.0f / 2.4f) - 0.055;
    float blue  = linear.z <= 0.0031308f ? 12.92f * linear.z : 1.055f * powf(linear.z, 1.0f / 2.4f) - 0.055;

    return ImVec4(red, green, blue, linear.w);
}

static ImVec4 sRGB_to_linear(ImVec4 linear) {
    float red   = linear.x <= 0.0031308f ? linear.x / 12.92f : powf((linear.x + 0.055) / 1.055, 2.4f);
    float green = linear.y <= 0.0031308f ? linear.y / 12.92f : powf((linear.y + 0.055) / 1.055, 2.4f);
    float blue  = linear.z <= 0.0031308f ? linear.z / 12.92f : powf((linear.z + 0.055) / 1.055, 2.4f);

    return ImVec4(red, green, blue, linear.w);
}

static vec3 Unproject(vec3 screen_pos) {
    vec4 result = vec4(screen_pos, 1.0f) * inverse_projection_matrix * inverse_view_matrix;
    result.x /= result.w;
    result.y /= result.w;
    result.z /= result.w;

    return result.xyz();
}

static vec2 get_floor_intersection_of_mouse(vec2 mouse_pos) {
    vec2 mouse = remap(vec2(0, 0), window_size, vec2(-1, 1), vec2(1, -1), mouse_pos);
    vec3 camera_position = (vec4(0, 0, 0, 1) * inverse_view_matrix).xyz();

    vec3 probe   = Unproject(vec3(mouse, .5));
    vec3 ray_dir = normalize(probe - camera_position);

    float t = -camera_position.z / ray_dir.z;
    vec3 floor_intersection = camera_position + (t * ray_dir);

    return floor_intersection.xy();
}

#ifdef TRACY_ENABLE
static SDL_malloc_func  sdl_malloc  = NULL;
static SDL_calloc_func  sdl_calloc  = NULL;
static SDL_realloc_func sdl_realloc = NULL;
static SDL_free_func    sdl_free    = NULL;

static void setup_memory_functions() {
    SDL_GetMemoryFunctions(&sdl_malloc, &sdl_calloc, &sdl_realloc, &sdl_free);
    SDL_SetMemoryFunctions(
        [](size_t size) -> void * {
            void *result = sdl_malloc(size);
            TracyAllocN(result, size, "SDL");
            return result;
        },
        [](size_t nmemb, size_t size) -> void * {
            void *result = sdl_calloc(nmemb, size);
            TracyAllocN(result, nmemb * size, "SDL");
            return result;
        },
        [](void *mem, size_t size) -> void * {
            void *result = sdl_realloc(mem, size);
            TracyFreeN(mem, "SDL");
            TracyAllocN(result, size, "SDL");
            return result;
        },
        [](void *mem) {
            TracyFreeN(mem, "SDL");
            sdl_free(mem);
        }
    );
}
#else
static void setup_memory_functions() {}
#endif

static bool recreate_tile_textures() {
    tile_textures_atlas = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB, R_TEXTURE_USAGE_SAMPLED, false, TILE_ATLAS_SIZE, TILE_ATLAS_SIZE, NULL, "tile_atlas_texture");
    if (!tile_textures_atlas) {
        log_error("Failed to create texture.");
        return false;
    }

    tile_textures_atlas_imgui = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, TILE_ATLAS_SIZE, TILE_ATLAS_SIZE, NULL, "tile_atlas_texture imgui");
    if (!tile_textures_atlas) {
        log_error("Failed to create texture.");
        return false;
    }

    for (Uint32 i = 0; i < SDL_arraysize(tile_infos); i++) {
        char path[256] = ASSETS_PATH;
        SDL_strlcat(path, tile_infos[i].asset_path, SDL_arraysize(path));

        int width = 0, height = 0;
        stbi_uc *data = stbi_load(path, &width, &height, NULL, 4);
        if (!data) {
            log_error("Failed to load texture (\"%s\"). Exiting.", path);
            renderer_texture_destroy(tile_textures_atlas);
            tile_textures_atlas = NULL;
            renderer_texture_destroy(tile_textures_atlas_imgui);
            tile_textures_atlas_imgui = NULL;
            return false;
        }

        SDL_assert_always(width  == TILE_SIZE);
        SDL_assert_always(height == TILE_SIZE);

        Uint32 x = (i * TILE_SIZE) % TILE_ATLAS_SIZE;
        Uint32 y = ((i * TILE_SIZE) / TILE_ATLAS_SIZE) * TILE_SIZE;

        tile_uvs[i].x = x;
        tile_uvs[i].y = y;
        tile_uvs[i].z = x + TILE_SIZE;
        tile_uvs[i].w = y + TILE_SIZE;

        if (tile_infos[i].rotation) {
            Uint32 copy[TILE_SIZE * TILE_SIZE]; memcpy(copy, data, TILE_SIZE * TILE_SIZE * 4);

            for (int y = 0; y < TILE_SIZE; y++) {
                for (int x = 0; x < TILE_SIZE; x++) {
                    switch(tile_infos[i].rotation) {
                        case 1: ((Uint32 *)data)[(TILE_SIZE - x - 1) * TILE_SIZE + (y)]                 = copy[y * TILE_SIZE + x]; break;
                        case 2: ((Uint32 *)data)[(TILE_SIZE - y - 1) * TILE_SIZE + (TILE_SIZE - x - 1)] = copy[y * TILE_SIZE + x]; break;
                        case 3: ((Uint32 *)data)[(x)                 * TILE_SIZE + (TILE_SIZE - y - 1)] = copy[y * TILE_SIZE + x]; break;
                        default: assert(false); break;
                    };
                }
            }
        }

        renderer_texture_update(tile_textures_atlas,       x, y, TILE_SIZE, TILE_SIZE, data, TILE_SIZE * 4);
        renderer_texture_update(tile_textures_atlas_imgui, x, y, TILE_SIZE, TILE_SIZE, data, TILE_SIZE * 4);
        stbi_image_free(data);
    }

    tile_uvs_buffer = renderer_buffer_create(R_BUFFER_USAGE_STORAGE, sizeof(tile_uvs), tile_uvs, "tile_uvs_buffer");

    return true;
}

static bool recreate_placeable_textures() {
    smol_atlas_t *placeable_atlas = sma_atlas_create(PLACEABLE_ATLAS_SIZE, PLACEABLE_ATLAS_SIZE);
    smol_atlas_item_t **placeable_atlas_items = (smol_atlas_item_t **)SDL_calloc(SDL_arraysize(placeable_infos), sizeof(smol_atlas_item_t *));

    defer(sma_atlas_destroy(placeable_atlas));
    defer(SDL_free(placeable_atlas_items));

    for (Uint32 i = 0; i < SDL_arraysize(placeable_infos); i++) {
        char path[256] = ASSETS_PATH;
        SDL_strlcat(path, placeable_infos[i].asset_path, SDL_arraysize(path));

        int width = 0, height = 0;
        if (!stbi_info(path, &width, &height, NULL))
            return false;

        placeable_atlas_items[i] = sma_item_add(placeable_atlas, width, height);
        if (!placeable_atlas_items[i])
            return false;
    }

    placeable_textures_atlas = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM_SRGB, R_TEXTURE_USAGE_SAMPLED, false, PLACEABLE_ATLAS_SIZE, PLACEABLE_ATLAS_SIZE, NULL, "placeable_atlas_texture");
    if (!placeable_textures_atlas) {
        log_error("Failed to create texture.");
        return false;
    }

    placeable_textures_atlas_imgui = renderer_texture_create(R_TEXTURE_FORMAT_RGBA8_UNORM, R_TEXTURE_USAGE_SAMPLED, false, PLACEABLE_ATLAS_SIZE, PLACEABLE_ATLAS_SIZE, NULL, "placeable_atlas_texture imgui");
    if (!placeable_textures_atlas_imgui) {
        log_error("Failed to create texture.");
        return false;
    }

    for (Uint32 i = 0; i < SDL_arraysize(placeable_infos); i++) {
        char path[256] = ASSETS_PATH;
        SDL_strlcat(path, placeable_infos[i].asset_path, SDL_arraysize(path));

        int width = 0, height = 0;
        stbi_uc *data = stbi_load(path, &width, &height, NULL, 4);
        if (!data) {
            log_error("Failed to load texture (\"%s\"). Exiting.", path);
            renderer_texture_destroy(placeable_textures_atlas);
            placeable_textures_atlas = NULL;
            renderer_texture_destroy(placeable_textures_atlas_imgui);
            placeable_textures_atlas_imgui = NULL;
            return false;
        }

        placeable_uvs[i].x = sma_item_x(placeable_atlas_items[i]);
        placeable_uvs[i].y = sma_item_y(placeable_atlas_items[i]);
        placeable_uvs[i].z = sma_item_x(placeable_atlas_items[i]) + sma_item_width (placeable_atlas_items[i]);
        placeable_uvs[i].w = sma_item_y(placeable_atlas_items[i]) + sma_item_height(placeable_atlas_items[i]);

        placeable_sizes_and_offsets[i].x = (float)width  / 32.0f;
        placeable_sizes_and_offsets[i].y = (float)height / 32.0f;
        placeable_sizes_and_offsets[i].z = placeable_infos[i].visual_offset.x;
        placeable_sizes_and_offsets[i].w = placeable_infos[i].visual_offset.y;

        renderer_texture_update(placeable_textures_atlas,       sma_item_x(placeable_atlas_items[i]), sma_item_y(placeable_atlas_items[i]), sma_item_width(placeable_atlas_items[i]), sma_item_height(placeable_atlas_items[i]), data, sma_item_width(placeable_atlas_items[i]) * 4);
        renderer_texture_update(placeable_textures_atlas_imgui, sma_item_x(placeable_atlas_items[i]), sma_item_y(placeable_atlas_items[i]), sma_item_width(placeable_atlas_items[i]), sma_item_height(placeable_atlas_items[i]), data, sma_item_width(placeable_atlas_items[i]) * 4);
        stbi_image_free(data);
    }

    placeable_uvs_buffer               = renderer_buffer_create(R_BUFFER_USAGE_STORAGE, sizeof(placeable_uvs),               placeable_uvs,               "placeable_uvs_buffer");
    placeable_sizes_and_offsets_buffer = renderer_buffer_create(R_BUFFER_USAGE_STORAGE, sizeof(placeable_sizes_and_offsets), placeable_sizes_and_offsets, "placeable_sizes_and_offsets_buffer");

    return true;
}

static int real_mod(int a, int b) {
    int result = a % b;
    return result >= 0 ? result : result + b;
}

static bool imgui_time_picker(const char *label, int time[3]) {
    bool result = ImGui::DragScalarN(label, ImGuiDataType_S32, time, 3);

    time[1] += time[2] >= 0 ? time[2] / 60 : -1;
    time[0] += time[1] >= 0 ? time[1] / 60 : -1;

    time[2] = real_mod(time[2], 60);
    time[1] = real_mod(time[1], 60);
    time[0] = real_mod(time[0], 24);

    return result;
}

static void change_map_tile(Sint32 pos_x, Sint32 pos_y, TileKind kind) {
    const Uint32 INFO_NONE     = ((TILEKIND_NONE  << 24) | (TILEKIND_NONE  << 16) | (TILEKIND_NONE  << 8) | TILEKIND_NONE);
    const Uint32 INFO_ERROR    = ((TILEKIND_ERROR << 24) | (TILEKIND_ERROR << 16) | (TILEKIND_ERROR << 8) | TILEKIND_ERROR);
    const Uint32 INFO_MASKS[4] = { 0x0000ff00, 0x000000ff, 0xff000000, 0x00ff0000 };

    Uint32 corner_infos[4] = { get_corner_info(current_map, pos_x + 0, pos_y + 1), get_corner_info(current_map, pos_x + 1, pos_y + 1), get_corner_info(current_map, pos_x + 1, pos_y + 0), get_corner_info(current_map, pos_x + 0, pos_y + 0) };

    for (int i = 0; i < 4; i++) {
        Uint32 replace_mask = INFO_MASKS[i];
        if ((corner_infos[i] == INFO_NONE) | (corner_infos[i] == INFO_ERROR))
            replace_mask = 0xffffffff;
        
        corner_infos[i] = corner_infos[i] ^ ((corner_infos[i] ^ ((kind << 24) | (kind << 16) | (kind << 8) | kind)) & replace_mask);
    }
    
    if (0 <= pos_x + 0 && pos_x + 0 < current_map->size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map->size.y)
        current_map->tiles[(pos_y + 1) * current_map->size.x + pos_x + 0] = find_matching_tile(corner_infos[0]);

    if (0 <= pos_x + 1 && pos_x + 1 < current_map->size.x && 0 <= pos_y + 1 && pos_y + 1 < current_map->size.y)
        current_map->tiles[(pos_y + 1) * current_map->size.x + pos_x + 1] = find_matching_tile(corner_infos[1]);
    
    if (0 <= pos_x + 1 && pos_x + 1 < current_map->size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map->size.y)
        current_map->tiles[(pos_y + 0) * current_map->size.x + pos_x + 1] = find_matching_tile(corner_infos[2]);
    
    if (0 <= pos_x + 0 && pos_x + 0 < current_map->size.x && 0 <= pos_y + 0 && pos_y + 0 < current_map->size.y)
        current_map->tiles[(pos_y + 0) * current_map->size.x + pos_x + 0] = find_matching_tile(corner_infos[3]);

    renderer_texture_update(current_map->texture, 0, 0, current_map->size.x, current_map->size.y, current_map->tiles, current_map->size.x * sizeof(Uint16));
    recalculate_water_collision_grid(current_map);
}

static void SameLineOrWrap(const ImVec2& size) {
    ImGuiWindow *window = ImGui::GetCurrentWindow();
    ImVec2 pos = ImVec2(window->DC.CursorPosPrevLine.x + ImGui::GetStyle().ItemSpacing.x, window->DC.CursorPosPrevLine.y);
    if (window->WorkRect.Contains(ImRect(pos, pos + size)))
        ImGui::SameLine();
}

static i32vec2 grid_tile_pos_from_floor_intersection(vec2 floor_intersection) {
    return {
        (Sint32)SDL_floorf(floor_intersection.x + (selected_tile == -1 ? 0.5f : 1.0f)),
        (Sint32)SDL_floorf(floor_intersection.y + (selected_tile == -1 ? 0.5f : 1.0f)),
    };
}

static i32vec2 grid_entity_pos_from_floor_intersection(vec2 floor_intersection) {
    return {
        (Sint32)SDL_floorf(floor_intersection.x + 0.5f),
        (Sint32)SDL_floorf(floor_intersection.y + 0.5f),
    };
}

static bool init_resources() {
    view_projection_matrix_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(mat4x4), NULL, "view_projection_matrix_buffer");
    if (!view_projection_matrix_buffer) {
        log_error("Failed to create buffer.");
        return false;
    }

    per_frame_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(per_frame), NULL, "per_frame_buffer");
    if (!per_frame_buffer) {
        log_error("Failed to create buffer.");
        return false;
    }

    tint_color_buffer = renderer_buffer_create(R_BUFFER_USAGE_UNIFORM, sizeof(vec3), NULL, "tint_color_buffer");
    if (!tint_color_buffer) {
        log_error("Failed to create buffer.");
        return false;
    }

    player_instance_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, sizeof(player_instance), &player_instance, "player_instance_buffer");
    if (!player_instance_buffer) {
        log_error("Failed to create buffer.");
        return false;
    }

    player_texture = create_texture("characters/main_character_female_sprite_sheet.png");
    if (!player_texture) {
        log_error("Failed to create shader texture.");
        return false;
    }

    character_shadow_texture = create_texture("characters/shadow.png");
    if (!character_shadow_texture) {
        log_error("Failed to create shader texture.");
        return false;
    }

    if (!recreate_tile_textures()) {
        log_error("Failed to create tile textures.");
        return false;
    }

    if (!recreate_placeable_textures()) {
        log_error("Failed to create placeable textures.");
        return false;
    }

    return true;
}

static void setup_working_directory() {
    if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;

    const char *current_directory = SDL_GetCurrentDirectory();

    change_directory(SDL_GetBasePath());
    if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;

    change_directory("..");
    if (SDL_GetPathInfo(ASSETS_PATH, NULL)) return;

    change_directory(current_directory);
}

static void process_event_editor(SDL_Event event) {
    ZoneScopedN("process_event_editor");

    ImGuiIO &io = ImGui::GetIO();
    
    ImGui_ImplSDL3_ProcessEvent(&event);
    
    switch (event.type) {
        case SDL_EVENT_KEY_DOWN: {
            if (event.key.key == SDLK_F9) {
                in_editor = !in_editor;
            }

            if (io.WantCaptureKeyboard)
                return;
        } break;

        case SDL_EVENT_MOUSE_WHEEL: {
            if (io.WantCaptureMouse)
                return;

            vec2 floor_pos_before = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y));
            editor_camera_distance = SDL_max(0.2, editor_camera_distance - event.wheel.y * SDL_sqrt(editor_camera_distance));

            view_matrix         = view        (vec3(editor_camera_position, 0), 0, editor_camera_distance);
            inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);

            vec2 floor_pos_after = get_floor_intersection_of_mouse(vec2(event.wheel.mouse_x, event.wheel.mouse_y));

            editor_camera_position += floor_pos_before - floor_pos_after;

            view_matrix         = view        (vec3(editor_camera_position, 0), 0, editor_camera_distance);
            inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);
        } break;

        case SDL_EVENT_MOUSE_BUTTON_DOWN: {
            if (io.WantCaptureMouse)
                return;

            vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y));
            i32vec2 tile_pos   = grid_tile_pos_from_floor_intersection(floor_intersection);
            i32vec2 entity_pos = grid_entity_pos_from_floor_intersection(floor_intersection);

            drag_start_pos = floor_intersection;

            if (event.button.button == SDL_BUTTON_RIGHT) {
                dragging_camera_change = true;
            }

            if (event.button.button == SDL_BUTTON_LEFT) {
                if (selected_placeable != -1 && 0 <= tile_pos.x && tile_pos.x < current_map->size.x -1 && 0 <= tile_pos.y && tile_pos.y < current_map->size.y - 1) {
                    Sint32 found_placeable = -1;
                    for (Sint32 i = 0; i < current_map->num_placeables; i++) {
                        if ((current_map->placeables[i].pos.x == entity_pos.x) && (current_map->placeables[i].pos.y == entity_pos.y)) {
                            found_placeable = i;
                            break;
                        }
                    }
                    
                    if (found_placeable != -1) {
                        Placeable tmp = current_map->placeables[found_placeable];
                        SDL_memmove(&current_map->placeables[found_placeable], &current_map->placeables[found_placeable + 1], (current_map->num_placeables - (found_placeable + 1)) * sizeof(*current_map->placeables));
                        current_map->placeables[current_map->num_placeables - 1] = tmp;
                    } else {
                        current_map->placeables = (Placeable *)SDL_realloc(current_map->placeables, (current_map->num_placeables + 1) * sizeof(*current_map->placeables));
                        current_map->placeables[current_map->num_placeables] = {
                            .pos = entity_pos,
                            .kind = (Uint32)selected_placeable,
                        };

                        current_map->num_placeables++;
                    }

                    std::stable_sort(&current_map->placeables[0], &current_map->placeables[current_map->num_placeables], [](const Placeable &a, const Placeable &b){ return a.pos.y > b.pos.y; });

                    if (current_map->placeables_buffer) renderer_buffer_destroy(current_map->placeables_buffer);
                    current_map->placeables_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, current_map->num_placeables * sizeof(*current_map->placeables), current_map->placeables, "placeables_buffer");
                    recalculate_static_collision_grid(current_map);
                }

                if (show_placeable_editor && selected_placeable == -1) {
                    for (Sint32 i = 0; i < current_map->num_placeables; i++) {
                        if ((current_map->placeables[i].pos.x == entity_pos.x) && (current_map->placeables[i].pos.y == entity_pos.y)) {
                            SDL_memmove(&current_map->placeables[i], &current_map->placeables[i + 1], (current_map->num_placeables - (i + 1)) * sizeof(*current_map->placeables));
                            current_map->num_placeables--;
                            break;
                        }
                    }

                    if (current_map->placeables_buffer) renderer_buffer_destroy(current_map->placeables_buffer);
                    if (current_map->num_placeables)    current_map->placeables_buffer = renderer_buffer_create(R_BUFFER_USAGE_VERTEX, current_map->num_placeables * sizeof(*current_map->placeables), current_map->placeables, "placeables_buffer");
                    recalculate_static_collision_grid(current_map);
                }

                if (selected_tile_kind != -1) {
                    change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind);

                    if (-1 <= tile_pos.x && tile_pos.x < current_map->size.x && -1 <= tile_pos.y && tile_pos.y < current_map->size.y) {
                        dragging_tile_change = true;
                    }
                }

                if (0 <= tile_pos.x && tile_pos.x < current_map->size.x && 0 <= tile_pos.y && tile_pos.y < current_map->size.y) {
                    dragging_tile_change = true;
                }
                
                SDL_Keymod modifiers = SDL_GetModState();
                if (modifiers & SDL_KMOD_SHIFT && tile_pos.x <= -1) {
                    if(modifiers & SDL_KMOD_CTRL)
                        change_map_size(current_map, 'W', -1);
                    else
                        change_map_size(current_map, 'W', 1);
                }

                if (modifiers & SDL_KMOD_SHIFT && tile_pos.x == current_map->size.x) {
                    if (modifiers & SDL_KMOD_CTRL)
                        change_map_size(current_map, 'E', -1);
                    else
                        change_map_size(current_map, 'E', 1);
                }

                if (modifiers & SDL_KMOD_SHIFT && tile_pos.y <= -1) {
                    if (modifiers & SDL_KMOD_CTRL)
                        change_map_size(current_map, 'N', -1);
                    else
                        change_map_size(current_map, 'N', 1);
                }

                if (modifiers & SDL_KMOD_SHIFT && tile_pos.y == current_map->size.y) {
                    if (modifiers & SDL_KMOD_CTRL)
                        change_map_size(current_map, 'S', -1);
                    else
                        change_map_size(current_map, 'S', 1);
                }
            }
        } break;

        case SDL_EVENT_MOUSE_BUTTON_UP: {
            if (io.WantCaptureMouse)
                return;

            if (event.button.button == SDL_BUTTON_RIGHT) {
                dragging_camera_change = false;
            }

            if (event.button.button == SDL_BUTTON_LEFT) {
                if (selected_tile != -1 && dragging_tile_change) {
                    vec2 floor_intersection = get_floor_intersection_of_mouse(vec2(event.button.x, event.button.y));
                    i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);

                    Sint32 tile_x = clamp(0, tile_pos.x, current_map->size.x - 1);
                    Sint32 tile_y = clamp(0, tile_pos.y, current_map->size.y - 1);

                    i32vec2 drag_start = grid_tile_pos_from_floor_intersection(drag_start_pos);

                    Sint32 start_x = min(tile_x, drag_start.x);
                    Sint32 start_y = min(tile_y, drag_start.y);

                    Sint32 end_x = max(tile_x, drag_start.x);
                    Sint32 end_y = max(tile_y, drag_start.y);

                    for (Sint32 y = start_y; y <= end_y; y++) {
                        for (Sint32 x = start_x; x <= end_x; x++) {
                            current_map->tiles[x + current_map->size.x * y] = selected_tile;
                        }
                    }

                    renderer_texture_update(current_map->texture, 0, 0, current_map->size.x, current_map->size.y, current_map->tiles, current_map->size.x * sizeof(Uint16));
                    recalculate_water_collision_grid(current_map);
                }

                dragging_tile_change = false;
            }
        } break;

        case SDL_EVENT_MOUSE_MOTION: {
            mouse_pos = vec2(event.motion.x, event.motion.y);
            vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);

            if (dragging_camera_change) {
                editor_camera_position -= (floor_intersection - drag_start_pos);
                
                view_matrix         = view        (vec3(editor_camera_position, 0), 0, editor_camera_distance);
                inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);
            }

            if (selected_tile_kind != -1 && dragging_tile_change) {
                vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);
                i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection); 
                change_map_tile(tile_pos.x, tile_pos.y, (TileKind)selected_tile_kind);
            }
        } break;
    }
}

static void update_state_editor() {
    ZoneScopedN("update_state_editor");

    ImGui_ImplRenderer_NewFrame();
    ImGui_ImplSDL3_NewFrame();
    ImGui::NewFrame();

    const ImGuiViewport *viewport = ImGui::GetMainViewport();
    ImGuiStyle &style = ImGui::GetStyle();

    if (ImGui::BeginMainMenuBar()) {
        if (ImGui::BeginMenu("File")) {
            if (ImGui::MenuItem("Reload")) {
                recreate_tile_textures();
                recreate_placeable_textures();
            }

            if (ImGui::MenuItem("Save")) {
                save_map(current_map);
            }
            ImGui::Separator();

            ImGui::MenuItem("Settings", NULL, &show_settings);
            ImGui::MenuItem("Demo Window", NULL, &show_demo_window);
            ImGui::Separator();
            
            if (ImGui::MenuItem("Exit")) {
                Running = false;
            }

            ImGui::EndMenu();
        }

        if (ImGui::BeginMenu("Edit")) {
            ImGui::MenuItem("Tile Picker", NULL, &show_tile_picker);
            ImGui::MenuItem("Placeables",  NULL, &show_placeable_editor);
            ImGui::MenuItem("Worlds",      NULL, &show_world_editor);
            ImGui::EndMenu();
        }

        ImGui::EndMainMenuBar();
    }

    ImGuiID main_viewport_dock = ImGui::GetID("main_viewport_dock");
    if (!ImGui::DockBuilderGetNode(main_viewport_dock)) {
        ImGui::DockBuilderAddNode    (main_viewport_dock, (ImGuiDockNodeFlags)ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode);
        ImGui::DockBuilderSetNodePos (main_viewport_dock, ImGui::GetMainViewport()->WorkPos);
        ImGui::DockBuilderSetNodeSize(main_viewport_dock, ImGui::GetMainViewport()->WorkSize);

        ImGuiID left_dock = ImGui::DockBuilderSplitNode(main_viewport_dock, ImGuiDir_Left, 0.2f, NULL, NULL);
        ImGui::DockBuilderDockWindow("Tile Picker", left_dock);
        ImGui::DockBuilderDockWindow("Placeables", left_dock);
        ImGui::DockBuilderDockWindow("Worlds", left_dock);
        ImGui::DockBuilderFinish(main_viewport_dock);
    }
    ImGui::DockSpaceOverViewport(main_viewport_dock, ImGui::GetMainViewport(), ImGuiDockNodeFlags_AutoHideTabBar | ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode);

    if (show_settings) {
        ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_FirstUseEver);
        if (ImGui::Begin("Settings", &show_settings)) {
            if (ImGui::DragFloat("Master", &volume_master, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) {
                MIX_SetMixerGain(mixer, volume_master / 100.0f);
            };
            if (ImGui::DragFloat("Music", &volume_music, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) {
                MIX_SetTrackGain(music_track, volume_music / 100.0f);
            }
            if (ImGui::DragFloat("SFX", &volume_sfx, 1.0f, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) {
                MIX_SetTrackGain(sfx_track, volume_sfx / 100.0f);
            }
            ImGui::NewLine();

            ImGui::DragFloat("fovy",            &camera_fovy_degrees);
            ImGui::DragFloat("camera_distance", &camera_distance, 0.25f, 1.0f, INFINITY);
            ImGui::DragFloat("camera_tilt",     &camera_tilt,     0.25f, 0.0f, 89.0f);

            ImGui::NewLine();
            ImGui::BeginDisabled(use_actual_time);
            ImGui::DragScalarN("Time", ImGuiDataType_S32, &calendar_time.hour, 3);
            ImGui::EndDisabled();
            ImGui::Checkbox("use actual time", &use_actual_time);
            ImGui::Checkbox("enable time based tinting", &enable_time_tints);

            ImGui::NewLine();
            for (int i = 0; i < num_used_tint_times; i++) {
                ImGui::PushID(i);
                imgui_time_picker("##time", time_tints_times[i]);
                ImGui::PopID();
            }

            if (ImGui::Button("Add"))    num_used_tint_times = clamp(1, num_used_tint_times + 1, MAX_TINT_TIMES);
            ImGui::SameLine();
            if (ImGui::Button("Remove")) num_used_tint_times = clamp(1, num_used_tint_times - 1, MAX_TINT_TIMES);

            ImGui::NewLine();
            for (int i = 0; i < num_used_tint_times; i++) {
                ImGui::PushID(i);
                ImGui::ColorEdit3("##color", glm::value_ptr(time_tints[i]));
                ImGui::PopID();
            }

            if (!ImGui::IsAnyItemActive()) {
                for (int j = 0; j < num_used_tint_times; j++) {
                    for (int i = 0; i < num_used_tint_times - 1; i++) {
                        if (time_tints_times[i][0] >  time_tints_times[i + 1][0] ||
                            time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] >  time_tints_times[i + 1][1] ||
                            time_tints_times[i][0] == time_tints_times[i + 1][0] && time_tints_times[i][1] == time_tints_times[i + 1][1] && time_tints_times[i][2] > time_tints_times[i + 1][2]) {
                            
                            int temp_time[3] = { time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2] };
                            vec3 temp_color = time_tints[i];
    
                            time_tints_times[i][0] = time_tints_times[i + 1][0];
                            time_tints_times[i][1] = time_tints_times[i + 1][1];
                            time_tints_times[i][2] = time_tints_times[i + 1][2];
    
                            time_tints[i] = time_tints[i + 1];
    
                            time_tints_times[i + 1][0] = temp_time[0];
                            time_tints_times[i + 1][1] = temp_time[1];
                            time_tints_times[i + 1][2] = temp_time[2];
    
                            time_tints[i + 1] = temp_color;
                        }
                    }
                }
            }
        }
        ImGui::End();
    }

    if (show_tile_picker) {
        if (ImGui::Begin("Tile Picker", &show_tile_picker, ImGuiWindowFlags_NoFocusOnAppearing)) {
            if (ImGui::Selectable("None", selected_tile_kind == -1 && selected_tile == -1)) {
                selected_tile_kind = -1;
                selected_tile = -1;
            }

            if (ImGui::Selectable("Grass", selected_tile_kind == TILEKIND_GRASS)) {
                selected_tile_kind = TILEKIND_GRASS;
                selected_tile = -1;
            }

            if (ImGui::Selectable("Dirt", selected_tile_kind == TILEKIND_DIRT)) {
                selected_tile_kind = TILEKIND_DIRT;
                selected_tile = -1;
            }

            if (ImGui::Selectable("Water", selected_tile_kind == TILEKIND_WATER)) {
                selected_tile_kind = TILEKIND_WATER;
                selected_tile = -1;
            }

            for (int i = 0; i < SDL_arraysize(tile_infos); i++) {
                ImGui::PushID(i);

                if (i != 0)
                    SameLineOrWrap(ImVec2(32, 32));

                if (SelectableTile("##tile", selected_tile == i, i, ImVec2(32, 32))) {
                    selected_tile_kind = -1;
                    selected_tile = i;
                }

                ImGui::PopID();
            }
        }
        ImGui::End();
    } else {
        selected_tile      = -1;
        selected_tile_kind = -1;
    }

    if (show_placeable_editor) {
        if (ImGui::Begin("Placeables", &show_placeable_editor, ImGuiWindowFlags_NoFocusOnAppearing)) {
            if (SelectableImage("##placeable", placeable_textures_atlas_imgui, ImVec2(64, 64), vec2(0, 0), vec2(0, 0), selected_placeable == -1))
                selected_placeable = -1;

            for (int i = 0; i < SDL_arraysize(placeable_infos); i++) {
                ImGui::PushID(i);

                vec2 size = placeable_sizes_and_offsets[i].xy() * 32.0f;

                if (i != 0)
                    SameLineOrWrap(ImVec2(size.x, 1));

                if (SelectableImage("##placeable", placeable_textures_atlas_imgui, ImVec2(size.x, size.y), placeable_uvs[i].xy() / (float)PLACEABLE_ATLAS_SIZE, placeable_uvs[i].zw() / (float)PLACEABLE_ATLAS_SIZE, selected_placeable == i))
                    selected_placeable = i;

                ImGui::PopID();
            }
        }
        ImGui::End();
    } else {
        selected_placeable = -1;
    }

    auto add_world_modal = ImGui::GetID("Add World");
    ImGui::SetNextWindowSize(ImVec2(200, 0), ImGuiCond_Appearing);
    if (ImGui::BeginPopupModal("Add World")) {
        static Map new_map = {};
        ImGui::SetNextItemWidth(-1);
        ImGui::InputTextWithHint("##Name", "Name", new_map.name, SDL_arraysize(new_map.name));

        float button_width = (ImGui::GetContentRegionAvail().x - style.ItemSpacing.x) * 0.5f;

        if (ImGui::Button("Cancel", ImVec2(button_width, 0))) ImGui::CloseCurrentPopup();
        ImGui::SameLine();
        if (ImGui::Button("Add", ImVec2(button_width, 0))) {
            new_map.size  = vec2(2, 2);
            new_map.tiles = (Uint16 *)SDL_calloc(new_map.size.x * new_map.size.y, sizeof(*new_map.tiles));

            new_map.texture = renderer_texture_create(R_TEXTURE_FORMAT_R16_UINT, R_TEXTURE_USAGE_STORAGE, false, new_map.size.x, new_map.size.y, new_map.tiles, "map_texture");

            size_t current_map_index = current_map - loaded_maps;

            loaded_maps = (Map *)SDL_realloc(loaded_maps, (num_loaded_maps + 1) * sizeof(new_map));
            loaded_maps[num_loaded_maps++] = new_map;

            current_map = &loaded_maps[current_map_index];
            
            save_map(&new_map);
            save_worlds_list();
            new_map = {};

            ImGui::CloseCurrentPopup();
        }
        
        ImGui::EndPopup();
    }

    if (show_world_editor) {
        if (ImGui::Begin("Worlds", &show_world_editor, ImGuiWindowFlags_NoFocusOnAppearing)) {
            if(ImGui::BeginListBox("##Worlds", ImVec2(-1, 0))) {
                for (int i = 0; i < num_loaded_maps; i++) {
                    if (ImGui::Selectable(loaded_maps[i].name, current_map == &loaded_maps[i])) {
                        current_map = &loaded_maps[i];
                    }
                }
                ImGui::EndListBox();

                if (ImGui::Button("Add World", ImVec2(-1, 0))) {
                    ImGui::OpenPopup(add_world_modal);
                }
            }
        }
        ImGui::End();
    }

    ImGui::SetNextWindowPos({ viewport->WorkPos.x + viewport->WorkSize.x - 10.0f, viewport->WorkPos.y + 10.0f }, ImGuiCond_Always, { 1.0f, 0.0f });
    if (ImGui::Begin("Overlay", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) {
        ImGui::Checkbox("Grid", &show_grid);
    }
    ImGui::End();

    if (show_demo_window)
        ImGui::ShowDemoWindow(&show_demo_window);
}

static void render_editor() {
    ZoneScopedN("render_editor");

    {
        ZoneScopedN("update buffers");
        {
            ZoneScopedN("player_instance_buffer");
            player_instance.pos = player.position;

            renderer_buffer_update(player_instance_buffer, 0, sizeof(player_instance), &player_instance);
        }

        {
            ZoneScopedN("per_frame");

            float aspect_ratio = ((float)window_size.x / (float)window_size.y);

            view_matrix         = view        (vec3(editor_camera_position, 0), 0, editor_camera_distance);
            inverse_view_matrix = inverse_view(vec3(editor_camera_position, 0), 0, editor_camera_distance);

            projection_matrix         = projection        (radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE);
            inverse_projection_matrix = inverse_projection(radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE);

            mat4x4 view_projection_matrix = view_matrix * projection_matrix;
            renderer_buffer_update(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix);
            
            vec2 floor_intersection = get_floor_intersection_of_mouse(mouse_pos);
            i32vec2 tile_pos = grid_tile_pos_from_floor_intersection(floor_intersection);

            per_frame.map_width   = current_map->size.x;
            per_frame.grid_width  = selected_tile != -1 ? current_map->size.x : current_map->size.x + 1;
            per_frame.grid_height = selected_tile != -1 ? current_map->size.y : current_map->size.y + 1;
            per_frame.grid_offset = selected_tile != -1 ? vec2(-0.5f, -0.5f) : vec2(-1.0f, -1.0f);
            per_frame.mouse       = tile_pos;

            if (dragging_tile_change && selected_tile != -1) {
                i32vec2 grid_tile_pos = grid_tile_pos_from_floor_intersection(drag_start_pos);
                per_frame.drag_start = grid_tile_pos;
            } else {
                per_frame.drag_start = tile_pos;
            }

            renderer_buffer_update(per_frame_buffer, 0, sizeof(per_frame), &per_frame);
        }

        {
            ZoneScopedN("tint color");
            Sint64 tint_times_ns[MAX_TINT_TIMES];
            for (int i = 0; i < num_used_tint_times; i++)
                tint_times_ns[i] = (time_tints_times[i][0] * 60 * 60 + time_tints_times[i][1] * 60 + time_tints_times[i][2]) * SDL_NS_PER_SECOND;
            tint_times_ns[num_used_tint_times] = (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND + tint_times_ns[0];

            Sint64 calendar_time_ns = (calendar_time.hour * 60 * 60 + calendar_time.minute * 60 + calendar_time.second) * SDL_NS_PER_SECOND + (Sint64)calendar_time.nanosecond;

            int last_time_index = num_used_tint_times - 1;
            for (int i = 0; i < num_used_tint_times; i++) {
                if (calendar_time_ns > tint_times_ns[i])
                    last_time_index = i;
            }
            if (calendar_time_ns <= tint_times_ns[0]) calendar_time_ns += (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND;

            Sint64 v = calendar_time_ns - tint_times_ns[last_time_index];
            Sint64 time_between = tint_times_ns[last_time_index + 1] - tint_times_ns[last_time_index];

            double t = v / (double)time_between;

            vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t);
            if (!enable_time_tints) tint_color = vec3(1, 1, 1);
            renderer_buffer_update(tint_color_buffer, 0, sizeof(tint_color), &tint_color);
        }
    }

    renderer_frame_set_target(framebuffer, R_surface, R_LOAD_OP_CLEAR, R_STORE_OP_STORE, { 0.01f, 0.01f, 0.01f, 0.01f });

    {
        ZoneScopedN("Draw Map");
        renderer_frame_set_shader(R_SHADER_WORLD);
        renderer_frame_draw(NULL, NULL, NULL, 6, current_map->size.y * current_map->size.x, &(R_Draw_Resources){
            .vertex_storage_textures = (R_Texture[]){ current_map->texture },
            .vertex_uniform_buffers  = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer },

            .num_vertex_storage_textures = 1,
            .num_vertex_uniform_buffers  = 2,

            .fragment_sampled_textures = (R_Texture[]){ tile_textures_atlas },
            .fragment_storage_buffers  = (R_Buffer[]){ tile_uvs_buffer },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_storage_buffers  = 1,
            .num_fragment_uniform_buffers  = 1,
        });
    }

    if (current_map->num_placeables) {
        ZoneScopedN("Draw Placeables");
        renderer_frame_set_shader(R_SHADER_PLACEABLE);
        renderer_frame_draw(NULL, NULL, current_map->placeables_buffer, 6, current_map->num_placeables, &(R_Draw_Resources){
            .vertex_storage_buffers = (R_Buffer[]){ placeable_sizes_and_offsets_buffer },
            .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer },

            .num_vertex_storage_buffers = 1,
            .num_vertex_uniform_buffers = 2,

            .fragment_sampled_textures = (R_Texture[]){ placeable_textures_atlas },
            .fragment_storage_buffers  = (R_Buffer[]){ placeable_uvs_buffer },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_storage_buffers  = 1,
            .num_fragment_uniform_buffers  = 1,
        });
    }

    if (show_grid) {
        ZoneScopedN("Draw Grid");

        renderer_frame_set_shader(R_SHADER_GRID);
        renderer_frame_draw(NULL, NULL, NULL, per_frame.grid_width * 2 + per_frame.grid_height * 2 + 4, 1, &(R_Draw_Resources){
            .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer },
            .num_vertex_uniform_buffers = 2,
        });
    }

    {
        ZoneScopedN("ImGui Render");
        ImGui::Render();
        ImDrawData *draw_data = ImGui::GetDrawData();
        ImGui_ImplRenderer_RenderDrawData(draw_data);
    }
}

static bool try_queued_move() {
    queued_movement = false;
    player.direction = queued_movement_direction;

    i32vec2 target_position = player.position;
    switch (queued_movement_direction) {
        case DIR_UP:    {  target_position = clamp(ivec2(0, 0), player.position + ivec2( 0,  1), current_map->size - ivec2(2, 2)); } break;
        case DIR_LEFT:  {  target_position = clamp(ivec2(0, 0), player.position + ivec2(-1,  0), current_map->size - ivec2(2, 2)); } break;
        case DIR_DOWN:  {  target_position = clamp(ivec2(0, 0), player.position + ivec2( 0, -1), current_map->size - ivec2(2, 2)); } break;
        case DIR_RIGHT: {  target_position = clamp(ivec2(0, 0), player.position + ivec2( 1,  0), current_map->size - ivec2(2, 2)); } break;
    }

    if (target_position != player.position) {
        if (current_map->static_collision_grid[target_position.y * (current_map->size.x - 2) + target_position.x] == 1)
            return false;

        if (current_map->water_collision_grid[target_position.y * (current_map->size.x - 2) + target_position.x] == 1)
            return false;

        player.target_position = target_position;
        player.is_moving = true;
        return true;
    }

    return false;
}

static void process_event_game(SDL_Event event) {
    ZoneScopedN("process_event");

    switch (event.type) {
        case SDL_EVENT_KEY_DOWN: {
            if (event.key.repeat) return;

            if (event.key.key == SDLK_UP || event.key.key == SDLK_W) {
                queued_movement = true;
                queued_movement_direction = DIR_UP;
            }

            if (event.key.key == SDLK_LEFT || event.key.key == SDLK_A) {
                queued_movement = true;
                queued_movement_direction = DIR_LEFT;
            }

            if (event.key.key == SDLK_DOWN || event.key.key == SDLK_S) {
                queued_movement = true;
                queued_movement_direction = DIR_DOWN;
            }

            if (event.key.key == SDLK_RIGHT || event.key.key == SDLK_D) {
                queued_movement = true;
                queued_movement_direction = DIR_RIGHT;
            }

            if (event.key.key == SDLK_F9) {
                in_editor = !in_editor;
            }
        } break;

        case SDL_EVENT_GAMEPAD_BUTTON_DOWN: {
            if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP) {
                queued_movement = true;
                queued_movement_direction = DIR_UP;
            }

            if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT) {
                queued_movement = true;
                queued_movement_direction = DIR_LEFT;
            }

            if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN) {
                queued_movement = true;
                queued_movement_direction = DIR_DOWN;
            }

            if (event.gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT) {
                queued_movement = true;
                queued_movement_direction = DIR_RIGHT;
            }
        } break;
    }
}

static void update_state_game() {
    ZoneScopedN("update_state_game");

    SDL_Time delta_time = current_time - last_time;
    float    delta_t    = delta_time / (float)SDL_NS_PER_SECOND;

    const bool *keyboard_state = SDL_GetKeyboardState(NULL);

    if (player.is_moving) {
        player.animation_time += delta_time * (keyboard_state[SDL_SCANCODE_LSHIFT] ? 2 : 1);

        vec2 leftover_movement = vec2(player.target_position) - player.visual_position;
        vec2 movement = clamp(min(vec2(0, 0), leftover_movement), normalize(leftover_movement) * (delta_t * character_speed * (keyboard_state[SDL_SCANCODE_LSHIFT] ? 2.0f : 1.0f)), max(vec2(0, 0), leftover_movement));
        player.visual_position += movement;

        if (movement == leftover_movement) {
            player.is_moving = false;
            player.position = player.target_position;
        }
    }

    bool is_movement_key_pressed = keyboard_state[SDL_SCANCODE_W] || keyboard_state[SDL_SCANCODE_A] || keyboard_state[SDL_SCANCODE_S] || keyboard_state[SDL_SCANCODE_D] || keyboard_state[SDL_SCANCODE_UP] || keyboard_state[SDL_SCANCODE_LEFT] || keyboard_state[SDL_SCANCODE_DOWN] || keyboard_state[SDL_SCANCODE_RIGHT];
    if (!player.is_moving && (queued_movement || is_movement_key_pressed)) {
        try_queued_move();
    }

    if (!player.is_moving)
        player.animation_time = 0;

    if (!MIX_TrackPlaying(music_track)) {
        MIX_SetTrackAudio(music_track, music_setting_off_piano);
        MIX_PlayTrack(music_track, 0);
    }
}

static void render_game() {
    ZoneScopedN("render_game");

    {
        ZoneScopedN("update buffers");
        {
            ZoneScopedN("player_instance_buffer");

            Uint64 ns_per_variation = (1.0f / (character_speed * 2)) * SDL_NS_PER_SECOND; 
            Uint64 variation_index = (player.animation_time / (ns_per_variation)) % 4;

            player_instance.pos              = player.visual_position;
            player_instance.sprite_selection = variation_index << 16 | player.direction;

            renderer_buffer_update(player_instance_buffer, 0, sizeof(player_instance), &player_instance);
        }

        {
            ZoneScopedN("per_frame");

            float aspect_ratio = ((float)window_size.x / (float)window_size.y);

            view_matrix         = view        (vec3(player.visual_position, 0), radians(camera_tilt), camera_distance);
            inverse_view_matrix = inverse_view(vec3(player.visual_position, 0), radians(camera_tilt), camera_distance);

            projection_matrix         = projection        (radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE);
            inverse_projection_matrix = inverse_projection(radians(camera_fovy_degrees), aspect_ratio, NEAR_PLANE);

            mat4x4 view_projection_matrix = view_matrix * projection_matrix;
            renderer_buffer_update(view_projection_matrix_buffer, 0, sizeof(view_projection_matrix), &view_projection_matrix);

            per_frame.map_width = current_map->size.x;
            renderer_buffer_update(per_frame_buffer, 0, sizeof(per_frame), &per_frame);
        }

        {
            ZoneScopedN("tint color");
            Sint64 tint_times_ns[MAX_TINT_TIMES];
            for (int i = 0; i < num_used_tint_times; i++)
                tint_times_ns[i] = (time_tints_times[i][0] * 60 * 60 + time_tints_times[i][1] * 60 + time_tints_times[i][2]) * SDL_NS_PER_SECOND;
            tint_times_ns[num_used_tint_times] = (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND + tint_times_ns[0];

            Sint64 calendar_time_ns = (calendar_time.hour * 60 * 60 + calendar_time.minute * 60 + calendar_time.second) * SDL_NS_PER_SECOND + (Sint64)calendar_time.nanosecond;

            int last_time_index = num_used_tint_times - 1;
            for (int i = 0; i < num_used_tint_times; i++) {
                if (calendar_time_ns > tint_times_ns[i])
                    last_time_index = i;
            }
            if (calendar_time_ns <= tint_times_ns[0]) calendar_time_ns += (24 * 60 * 60 + 60 * 60 + 60) * SDL_NS_PER_SECOND;

            Sint64 v = calendar_time_ns - tint_times_ns[last_time_index];
            Sint64 time_between = tint_times_ns[last_time_index + 1] - tint_times_ns[last_time_index];

            double t = v / (double)time_between;

            vec3 tint_color = mix(time_tints[last_time_index], time_tints[(last_time_index + 1) % num_used_tint_times], t);
            if (!enable_time_tints) tint_color = vec3(1, 1, 1);
            renderer_buffer_update(tint_color_buffer, 0, sizeof(tint_color), &tint_color);
        }
    }

    renderer_frame_set_target(framebuffer, R_surface, R_LOAD_OP_CLEAR, R_STORE_OP_STORE, { 0.01f, 0.01f, 0.01f, 0.01f });

    {
        ZoneScopedN("Draw Map");
        renderer_frame_set_shader(R_SHADER_WORLD);
        renderer_frame_draw(NULL, NULL, NULL, 6, current_map->size.y * current_map->size.x, &(R_Draw_Resources){
            .vertex_storage_textures = (R_Texture[]){ current_map->texture },
            .vertex_uniform_buffers  = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer },

            .num_vertex_storage_textures = 1,
            .num_vertex_uniform_buffers  = 2,

            .fragment_sampled_textures = (R_Texture[]){ tile_textures_atlas },
            .fragment_storage_buffers  = (R_Buffer[]){ tile_uvs_buffer },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_storage_buffers  = 1,
            .num_fragment_uniform_buffers  = 1,
        });
    }

    if (current_map->num_placeables) {
        ZoneScopedN("Draw Placeables");
        renderer_frame_set_shader(R_SHADER_PLACEABLE);
        renderer_frame_draw(NULL, NULL, current_map->placeables_buffer, 6, current_map->num_placeables, &(R_Draw_Resources){
            .vertex_storage_buffers = (R_Buffer[]){ placeable_sizes_and_offsets_buffer },
            .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer, per_frame_buffer },

            .num_vertex_storage_buffers = 1,
            .num_vertex_uniform_buffers = 2,

            .fragment_sampled_textures = (R_Texture[]){ placeable_textures_atlas },
            .fragment_storage_buffers  = (R_Buffer[]){ placeable_uvs_buffer },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_storage_buffers  = 1,
            .num_fragment_uniform_buffers  = 1,
        });
    }

    {
        ZoneScopedN("Draw Player");
        renderer_frame_set_shader(R_SHADER_CHARACTER_SHADOW);
        renderer_frame_draw(NULL, NULL, player_instance_buffer, 6, 1, &(R_Draw_Resources){
            .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer },

            .num_vertex_uniform_buffers = 1,

            .fragment_sampled_textures = (R_Texture[]){ character_shadow_texture },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_uniform_buffers  = 1,
        });

        renderer_frame_set_shader(R_SHADER_CHARACTER);
        renderer_frame_draw(NULL, NULL, player_instance_buffer, 6, 1, &(R_Draw_Resources){
            .vertex_uniform_buffers = (R_Buffer[]){ view_projection_matrix_buffer },

            .num_vertex_uniform_buffers = 1,

            .fragment_sampled_textures = (R_Texture[]){ player_texture },
            .fragment_uniform_buffers  = (R_Buffer[]){ tint_color_buffer },

            .num_fragment_sampled_textures = 1,
            .num_fragment_uniform_buffers  = 1,
        });
    }
}

static void process_events() {
    ZoneScopedN("process_events");

    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        switch (event.type) {
            case SDL_EVENT_QUIT: {
                Running = false;
            } break;

            case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
                window_size.x = event.window.data1;
                window_size.y = event.window.data2;
            } break;
        }

        if (in_editor) {
            process_event_editor(event);
        } else {
            process_event_game(event);
        }
    }
}

static void update_state() {
    ZoneScopedN("update_state");

    last_time = current_time;
    SDL_GetCurrentTime(&current_time);

    if (use_actual_time)
        SDL_TimeToDateTime(current_time, &calendar_time, true);

    calendar_time.minute += calendar_time.second >= 0 ? calendar_time.second / 60 : calendar_time.second / 60 - 1;
    calendar_time.hour   += calendar_time.minute >= 0 ? calendar_time.minute / 60 : calendar_time.minute / 60 - 1;

    calendar_time.second = real_mod(calendar_time.second, 60);
    calendar_time.minute = real_mod(calendar_time.minute, 60);
    calendar_time.hour   = real_mod(calendar_time.hour,   24);

    if (in_editor) {
        update_state_editor();
    } else {
        update_state_game();
    }
}

static void render() {
    ZoneScopedN("render");

    renderer_frame_begin();

    if (!framebuffer || renderer_texture_get_width(framebuffer) != renderer_texture_get_width(R_surface) || renderer_texture_get_height(framebuffer) != renderer_texture_get_height(R_surface)) {
        if (framebuffer) {
            renderer_texture_destroy(framebuffer);
            framebuffer = NULL;
        }

        framebuffer = renderer_texture_create(renderer_texture_get_format(R_surface), R_TEXTURE_USAGE_TARGET, true, renderer_texture_get_width(R_surface), renderer_texture_get_height(R_surface), NULL, "framebuffer");
    }

    if (in_editor) {
        render_editor();
    } else {
        render_game();
    }

    renderer_frame_end();
}

void save_settings() {
    SDL_Storage *user_storage = SDL_OpenUserStorage(ORG, APP, 0);
    if (!user_storage) return;
    defer(SDL_CloseStorage(user_storage));

    SDL_IOStream *io = SDL_IOFromDynamicMem();
    SDL_IOprintf(io, "[Audio]\n");
    SDL_IOprintf(io, "Master=%.0f\n", volume_master);
    SDL_IOprintf(io, "Music=%.0f\n", volume_music);
    SDL_IOprintf(io, "SFX=%.0f\n", volume_sfx);

    SDL_PropertiesID properties = SDL_GetIOProperties(io);
    void *data = SDL_GetPointerProperty(properties, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL);

    Sint64 size = SDL_GetIOSize(io);
    SDL_WriteStorageFile(user_storage, "settings.ini", data, size);

    SDL_CloseIO(io);
}

void load_settings() {
    SDL_Storage *user_storage = SDL_OpenUserStorage(ORG, APP, 0);
    if (!user_storage) return;
    defer(SDL_CloseStorage(user_storage));

    Uint64 file_size = 0;
    if (SDL_GetStorageFileSize(user_storage, "settings.ini", &file_size)) {
        char *file_data = (char *)SDL_calloc(1, file_size + 1);
        defer(SDL_free(file_data));

        if (!SDL_ReadStorageFile(user_storage, "settings.ini", file_data, file_size))
            return;

        Settings_Category current_category = SETTINGS_UNKNOWN;

        char *line_end = NULL;
        for (char *line = file_data; line < file_data + file_size; line = line_end + 1) {
            while (line[0] == '\n' || line[0] == '\r') line++;
            line_end = line;
            while(line_end < file_data + file_size && line_end[0] != '\n' && line_end[0] != '\r') line_end++;
            line_end[0] = 0;

            if (line[0] == '[' && line_end > line && line_end[-1] == ']') {
                line_end[-1] = 0;

                const char *category_name = line + 1;
                if (SDL_strcasecmp(category_name, "AUDIO") == 0) {
                    current_category = SETTINGS_AUDIO;
                } else {
                    SDL_Log("Unknown category '%s' in settings.ini.", category_name);
                }
            } else {
                switch (current_category) {
                    case SETTINGS_UNKNOWN: {
                        SDL_Log("Unknown value '%s' outside of any category in settings.ini.", line);
                    } break;

                    case SETTINGS_AUDIO: {
                        if (SDL_strncasecmp(line, "Master", sizeof("Master") - 1) == 0) {
                            line = line + sizeof("Master") - 1;
                            SDL_sscanf(line, "=%f", &volume_master);
                        } else if (SDL_strncasecmp(line, "Music", sizeof("Music") - 1) == 0) {
                            line = line + sizeof("Music") - 1;
                            SDL_sscanf(line, "=%f", &volume_music);
                        } else if (SDL_strncasecmp(line, "SFX", sizeof("SFX") - 1) == 0) {
                            line = line + sizeof("SFX") - 1;
                            SDL_sscanf(line, "=%f", &volume_sfx);
                        } else {
                            SDL_Log("Unknown value '%s' in category 'AUDIO' in settings.ini.", line);
                        }
                    } break;
                }
            }
        }

        MIX_SetMixerGain(mixer,       volume_master / 100.0f);
        MIX_SetTrackGain(music_track, volume_music  / 100.0f);
        MIX_SetTrackGain(sfx_track,   volume_sfx    / 100.0f);
    } else {
        SDL_Log("No settings found. Using default values.");
    }
}

int main(int argc, char **argv) {
    setup_memory_functions();
    setup_working_directory();

    #ifdef SDL_PLATFORM_LINUX
    if (getenv("ENABLE_VULKAN_RENDERDOC_CAPTURE"))
        SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "x11,wayland");
    #endif

    if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | SDL_INIT_GAMEPAD)) {
        log_error("Failed to initialize SDL (%s). Exiting.", SDL_GetError());
        return 1;
    }

    if (!MIX_Init()) {
        log_error("Failed to init SDL_mixer. Exiting.");
        return 1;
    }

    window = SDL_CreateWindow("Mikemon", window_size.x, window_size.y, SDL_WINDOW_RESIZABLE);
    if (!window) {
        log_error("Failed to create window (%s). Exiting.", SDL_GetError());
        return 1;
    }

    if (!renderer_init(window)) {
        log_error("Failed to initialize renderer. Exiting.");
        return 1;
    }

    if (!init_resources()) {
        log_error("Failed to init resources. Exiting.");
        return 1;
    }

    loaded_maps = (Map *)SDL_calloc(SDL_arraysize(world_names), sizeof(*loaded_maps));
    for (int i = 0; i < SDL_arraysize(world_names); i++) {
        if (!load_map(world_names[i], &loaded_maps[i])) {
            log_error("Failed to load map '%s'. Exiting.", world_names[i]);
            return 1;
        }
    }
    num_loaded_maps = SDL_arraysize(world_names);
    current_map = &loaded_maps[0];

    mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
    if (!mixer) {
        log_error("Failed to open default audio device. Ignoring.");
    }

    music_track = MIX_CreateTrack(mixer);
    if (!music_track) {
        log_error("Failed to create music track. Ignoring.");
    }

    sfx_track = MIX_CreateTrack(mixer);
    if (!sfx_track) {
        log_error("Failed to create sfx track. Ignoring.");
    }

    music_setting_off_piano = MIX_LoadAudio(mixer, ASSETS_PATH "music/setting_off_piano.opus", false);
    if (!music_setting_off_piano) {
        log_error("Failed to load music setting_off_piano.opus. Ignoring.");
    }
    
    load_settings();
    save_settings();

    IMGUI_CHECKVERSION();
    ImGuiContext *imgui_context = ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;

    char *storage_path = SDL_GetPrefPath(ORG, APP);

    size_t imgui_ini_path_len = SDL_strlen(storage_path) + SDL_strlen("imgui.ini") + 1;
    char *imgui_ini_path = (char *)SDL_calloc(imgui_ini_path_len, 1);

    SDL_strlcat(imgui_ini_path, storage_path, imgui_ini_path_len);
    SDL_strlcat(imgui_ini_path, "imgui.ini",  imgui_ini_path_len);

    SDL_free(storage_path);

    io.IniFilename = imgui_ini_path;

    io.Fonts->AddFontDefaultVector();

    ImGui::StyleColorsDark();

    ImGui_ImplSDL3_InitForOther(window);
    ImGui_ImplRenderer_Init();

    ImGuiSettingsHandler time_tints_settings_handler = {};
    time_tints_settings_handler.TypeName = "TimeTints";
    time_tints_settings_handler.TypeHash = ImHashStr("TimeTints");
    time_tints_settings_handler.ReadOpenFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, const char *name) -> void * {
        if (strcmp(name, "Settings") == 0)
            return (void *)-1;
        int num = atoi(name) + 1;
        return (void *)(Sint64)num;
    };
    time_tints_settings_handler.ReadLineFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, void *entry, const char *line) {
        if (entry == (void *)-1) {
            if (strncmp(line, "num", 3) == 0) {
                SDL_sscanf(line, "num=%d", &num_used_tint_times);
            } else if (strncmp(line, "enable", 6) == 0) {
                Uint32 enable = 0;
                SDL_sscanf(line, "enable=%d", &enable);
                enable_time_tints = !!enable;
            }
            return;
        }

        if (strncmp(line, "time", 4) == 0) {
            SDL_sscanf(line, "time=%d %d %d", &time_tints_times[(size_t)entry - 1][0], &time_tints_times[(size_t)entry - 1][1], &time_tints_times[(size_t)entry - 1][2]);
        } else if(strncmp(line, "color", 5) == 0) { 
            SDL_sscanf(line, "color=%g %g %g", &time_tints[(size_t)entry - 1][0], &time_tints[(size_t)entry - 1][1], &time_tints[(size_t)entry - 1][2]);
        }
    };
    time_tints_settings_handler.WriteAllFn = [](ImGuiContext *context, ImGuiSettingsHandler *handler, ImGuiTextBuffer *buffer) {
        buffer->append("[TimeTints][Settings]\n");
        buffer->appendf("enable=%u\n", enable_time_tints ? 1 : 0);
        buffer->appendf("num=%d\n\n", num_used_tint_times);

        for (int i = 0; i < num_used_tint_times; i++) {
            buffer->appendf("[TimeTints][%d]\n", i);
            buffer->appendf("time=%d %d %d\n", time_tints_times[i][0], time_tints_times[i][1], time_tints_times[i][2]);
            buffer->appendf("color=%g %g %g\n\n", time_tints[i][0], time_tints[i][1], time_tints[i][2]);
        }
    };
    ImGui::AddSettingsHandler(&time_tints_settings_handler);

    SDL_GetWindowSizeInPixels(window, &window_size.x, &window_size.y);

    while (Running) {
        ZoneScopedN("main_loop");

        process_events();
        update_state();
        render();

        FrameMark;
    }

    save_settings();

    ImGui_ImplRenderer_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();

    return 0;
}
