#include <stdio.h>
#include <stdint.h>
#include <SDL3/SDL.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <imgui_impl_sdl3.h>
#include <imgui_impl_sdlgpu3.h>
#include <tracy/Tracy.hpp>
#include <tracy/TracyC.h>

#include "smol-atlas.h"
#include "defer.h"
#include "log.h"
#include "m_string.h"
#include "math_graphics.h"
#include "load_entire_file.h"
#include "stb_image.h"

#include "../assets/shader/basic.h"
#include "../assets/shader/world.h"

using namespace M;

#define NEAR_PLANE (0.01f)

SDL_GPUDevice *device;
SDL_Window    *window;

SDL_GPUGraphicsPipeline *basic_graphics_pipeline;
SDL_GPUGraphicsPipeline *world_graphics_pipeline;
SDL_GPUSampler 			*point_sampler;

SDL_GPUBuffer *vertex_buffer;
SDL_GPUBuffer *index_buffer;
SDL_GPUBuffer *player_instance_buffer;
SDL_GPUBuffer *world_buffer;
SDL_GPUBuffer *tile_infos_buffer;

Sint32 window_width;
Sint32 window_height;

bool Running = true;

M4x4 view_matrix         = view        (V3_(0.0f, 0.0f, 0.0f), radians(45.0f), 10.0f);
M4x4 inverse_view_matrix = inverse_view(V3_(0.0f, 0.0f, 0.0f), radians(45.0f), 10.0f);

M4x4 projection_matrix         = projection        (radians(45.0f), 16.0f / 9.0f, NEAR_PLANE);
M4x4 inverse_projection_matrix = inverse_projection(radians(45.0f), 16.0f / 9.0f, NEAR_PLANE);

struct Vertex {
    V3 pos;
};

Vertex vertices[] = {
    {{ -0.5f,  0.5f, 0 }},
    {{  0.5f,  0.5f, 0 }},
    {{  0.5f, -0.5f, 0 }},
    {{ -0.5f, -0.5f, 0 }},
};

Uint16 indices[] = {
    0, 1, 2,
    0, 2, 3,
};

struct Instance {
    V2 pos;
    V4 uv0uv1;
    V4 uv2uv3;
};

Instance player_instance = { { 0.0f, 0.0f }, { 0, 0, 1, 0 }, { 1, 1, 0, 1 }};

Sint32 map_width;
Sint32 map_height;

Uint32* map_tiles;

struct Player {
    Sint32 pos_x;
    Sint32 pos_y;
};

Player player;

struct PerFrame {
    float  aspect_ratio;
    float  fovy_degrees;
    float  camera_x;
    float  camera_y;
    float  camera_distance;
    float  camera_tilt;
    Uint32 map_width;
};

PerFrame per_frame = { 
    .aspect_ratio    = 16.0f / 9.0f,
    .fovy_degrees    = 31.0f,
    .camera_x        = 0.0f,
    .camera_y        = 0.0f,
    .camera_distance = 13.5f,
    .camera_tilt     = 25.5f,
};

typedef struct {
    Uint16      type;
    const char *asset_path;

    V2 uv_min;
    V2 uv_max;
} TileInfo;

TileInfo tile_infos[] = {
    { 0x0001, "../assets/tiles/error.png" },
    { 0x0000, "../assets/tiles/empty.png" },
    { 0x0100, "../assets/tiles/grass_1.png" },
    { 0x0101, "../assets/tiles/grass_2.png" },
    { 0x0102, "../assets/tiles/grass_3.png" },
    { 0x0200, "../assets/tiles/ground_1.png" },
    { 0x0300, "../assets/tiles/water_1.png" },
    { 0x0301, "../assets/tiles/water_2.png" },
    { 0x0400, "../assets/tiles/grass_ground_1.png" },
};

int tile_atlas_size = 256;
smol_atlas_t *tile_atlas;

V4 cpu_tile_infos_buffer[SDL_arraysize(tile_infos)];

Sint32 selected_tile     = -1;
Sint32 selected_rotation = 0;

bool dragging_tile_change = false;
Sint32 drag_start_pos[2];

void save_map() {
    log("Save file is under construction.");
    FILE* file = fopen("../assets/map/map.sv", "wb");

    if (!file) {
        log_error("Save file creation has failed.");
        return;
    }
    defer(fclose(file));

    if (fwrite(&map_width, sizeof(Uint32), 1, file) != 1) {
        log_error("fwrite for map_width has failed.");
        return;
    }	

    if (fwrite(&map_height, sizeof(Uint32), 1, file) != 1) {
        log_error("fwrite for map_height has failed.");
        return;
    }

    for (int i = 0; i < map_width * map_height; i++) {
        Uint32 type        = tile_infos[map_tiles[i] & 0xffff].type;
        Uint32 orientation = (map_tiles[i] & 0x30000) >> 16;

        Uint32 to_write = (orientation << 16) | type;
        if (fwrite(&to_write, sizeof(Uint32), 1, file) != 1) {
            log_error("fwrite for tile_type at tile number %d has failed.", i);
            return;
        }
    }

    log("Saving map was successful.");
}

void load_map() {
    log("Load save file.");
    String file = load_entire_file("../assets/map/map.sv");
    if (!file.length) {
        log_error("Loading save file has failed.");
        return;
    }

    map_width = read<Uint32>(file);
    map_height = read<Uint32>(file);
    if (file.length != map_width * map_height * sizeof(Uint32)) {
        log_error("Incorrect file.length.");
        return;
    }

    if (map_tiles)
        free(map_tiles);

    map_tiles = (Uint32*)malloc(map_width * map_height * sizeof(Uint32));

    for (int i = 0; i < map_width * map_height; i++) {
        Uint32 to_read = read<Uint32>(file);
        Uint32 type        = to_read & 0xffff;
        Uint32 orientation = (to_read & 0x30000) >> 16;

        Uint32 kind = 0;
        for (int i = 0; i < SDL_arraysize(tile_infos); i++) {
            if (tile_infos[i].type == type) {
                kind = i;
                break;
            }
        }

        map_tiles[i] = (orientation << 16) | kind;
    }

    log("Loading map was successful.");
}

bool update_buffer(SDL_GPUBuffer *buffer, Uint32 offset, Uint32 num_bytes, void *data) {
    SDL_GPUTransferBufferCreateInfo	transfer_buffer_info = {
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size  = num_bytes,
    };

    SDL_GPUTransferBuffer *transfer_buffer = SDL_CreateGPUTransferBuffer(device, &transfer_buffer_info);
    if (!transfer_buffer) {
        log_error("Failed to create gpu transfer buffer (%s).", SDL_GetError());
        return false;
    }

    void *transfer_data = SDL_MapGPUTransferBuffer(device, transfer_buffer, true);
    if (!transfer_data) {
        log_error("Failed to map gpu transfer buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        return false;
    }
    memcpy(transfer_data, data, num_bytes);
    SDL_UnmapGPUTransferBuffer(device, transfer_buffer);

    SDL_GPUCommandBuffer *command_buffer = SDL_AcquireGPUCommandBuffer(device);
    if (!command_buffer) {
        log_error("Failed to acquire gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        return false;
    }

    SDL_GPUTransferBufferLocation transfer_info = {
        .transfer_buffer = transfer_buffer,
        .offset  		 = 0,
    };

    SDL_GPUBufferRegion buffer_region = {
        .buffer = buffer,
        .offset = offset,
        .size   = num_bytes,
    };

    SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(command_buffer);
    SDL_UploadToGPUBuffer(copy_pass, &transfer_info, &buffer_region, true);
    SDL_EndGPUCopyPass(copy_pass);

    if (!SDL_SubmitGPUCommandBuffer(command_buffer)) {
        log_error("Failed to submit gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_CancelGPUCommandBuffer(command_buffer);
        return false;
    }

    SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
    return true;
}

SDL_GPUBuffer *create_buffer(SDL_GPUBufferUsageFlags usage, Uint32 num_bytes, void *data = NULL, const char *name = NULL) {
    SDL_PropertiesID properties = 0;
    if (name) {
        properties = SDL_CreateProperties();
        if (properties) SDL_SetStringProperty(properties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, name);
    }
    
    SDL_GPUBufferCreateInfo buffer_info = {
        .usage = usage,
        .size  = num_bytes,

        .props = properties,
    };

    SDL_GPUBuffer *buffer = SDL_CreateGPUBuffer(device, &buffer_info);
    if (properties) SDL_DestroyProperties(properties);

    if (data) {
        if (!update_buffer(buffer, 0, num_bytes, data)) {
            log_error("Failed to update buffer (%s).", SDL_GetError());
            SDL_ReleaseGPUBuffer(device, buffer);
            return NULL;
        }
    }

    return buffer;
}

void change_map_size(char direction, int amount) {
    SDL_GPUBuffer *old_world_buffer = world_buffer;
    Uint32* old_map = map_tiles;
    auto old_map_width = map_width;
    auto old_map_height = map_height;

    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_width;
    Sint32 to_fill_height = map_height;
    Sint32 to_fill_x_offset = 0;
    Sint32 to_fill_y_offset = 0;

    if (direction == 'W') {
        player.pos_x = player.pos_x + amount;
        map_width += amount;
        to_fill_width = amount;
        
        if (amount < 0) 
            old_x_offset = -amount;
        else 
            new_x_offset = amount;
    }

    if (direction == 'N') {
        player.pos_y = player.pos_y + amount;
        map_height += amount;
        to_fill_height = amount;

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

    if (direction == 'E') {
        map_width += amount;
        to_fill_width = amount;
        to_fill_x_offset = old_map_width;
    }

    if (direction == 'S') {
        map_height += amount;
        to_fill_height = amount;
        to_fill_y_offset = old_map_height;
    }

    map_tiles = (Uint32*)malloc(map_width * map_height * sizeof(Uint32));

    for (int y = 0; y < min(old_map_height, map_height); y++) {
        for (int x = 0; x < min(old_map_width, map_width); x++) {
            map_tiles[(y + new_y_offset) * map_width + (x + new_x_offset)] = old_map[(y + old_y_offset) * old_map_width + (x + old_x_offset)];
        }
    }

    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_width + (x + to_fill_x_offset)] = 1;
        }
    }

    player.pos_x = clamp(0, player.pos_x, map_width - 1);
    player.pos_y = clamp(0, player.pos_y, map_height - 1);

    world_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, map_width * map_height * 4, map_tiles, "world_buffer");
    if (!world_buffer) {
        log_error("Failed to create buffer. Exiting.");
        exit(1);
    }

    free(old_map);
    SDL_ReleaseGPUBuffer(device, old_world_buffer);
}

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

    SDL_GPUTextureFormat format = SDL_GPU_TEXTUREFORMAT_INVALID;
    if (channels == 1) format = SDL_GPU_TEXTUREFORMAT_A8_UNORM;
    if (channels == 4) format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB;

    if (format == SDL_GPU_TEXTUREFORMAT_INVALID) {
        log_error("Failed to find texture format for texture (\"%s\").", path);
        stbi_image_free(data);
        return NULL;
    }

    SDL_PropertiesID properties = SDL_CreateProperties();
    if (properties) SDL_SetStringProperty(properties, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, path);

    SDL_GPUTextureCreateInfo texture_info = {
        .type   = SDL_GPU_TEXTURETYPE_2D,
        .format = format,
        .usage  = SDL_GPU_TEXTUREUSAGE_SAMPLER,
        .width  = (Uint32)width,
        .height = (Uint32)height,

        .layer_count_or_depth = 1,
        .num_levels = 1,

        .props = properties,
    };

    SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &texture_info);
    if (!texture) {
        log_error("Failed to create gpu texture (%s).", SDL_GetError());
        stbi_image_free(data);
        return NULL;
    }
    if (properties) SDL_DestroyProperties(properties);

    Uint32 upload_size = width * height * channels;
    SDL_GPUTransferBufferCreateInfo	transfer_buffer_info = {
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size  = upload_size,
    };

    SDL_GPUTransferBuffer *transfer_buffer = SDL_CreateGPUTransferBuffer(device, &transfer_buffer_info);
    if (!transfer_buffer) {
        log_error("Failed to create gpu transfer buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTexture(device, texture);
        stbi_image_free(data);
        return NULL;
    }

    void *transfer_data = SDL_MapGPUTransferBuffer(device, transfer_buffer, false);
    if (!transfer_data) {
        log_error("Failed to map gpu transfer buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        stbi_image_free(data);
        return NULL;
    }
    memcpy(transfer_data, data, upload_size);
    SDL_UnmapGPUTransferBuffer(device, transfer_buffer);
    stbi_image_free(data);

    SDL_GPUTextureTransferInfo transfer_info = {
        .transfer_buffer = transfer_buffer,
        .pixels_per_row  = (Uint32)width,
    };

    SDL_GPUTextureRegion tetxure_region = {
        .texture = texture,
        .w = (Uint32)width,
        .h = (Uint32)height,
        .d = 1,
    };

    SDL_GPUCommandBuffer *command_buffer = SDL_AcquireGPUCommandBuffer(device);
    if (!command_buffer) {
        log_error("Failed to acquire gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        return NULL;
    }

    SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(command_buffer);
    SDL_UploadToGPUTexture(copy_pass, &transfer_info, &tetxure_region, false);
    SDL_EndGPUCopyPass(copy_pass);

    if (!SDL_SubmitGPUCommandBuffer(command_buffer)) {
        log_error("Failed to submit gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        SDL_CancelGPUCommandBuffer(command_buffer);
        return NULL;
    }

    SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
    return texture;
}

SDL_GPUTexture *create_shader_texture(const char *name, const char *data, int width, int height, int channels) {
    SDL_GPUTextureFormat format = SDL_GPU_TEXTUREFORMAT_INVALID;
    if (channels == 1) format = SDL_GPU_TEXTUREFORMAT_A8_UNORM;
    if (channels == 4) format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB;

    if (format == SDL_GPU_TEXTUREFORMAT_INVALID) {
        log_error("Failed to find texture format for texture.");
        return NULL;
    }

    SDL_PropertiesID properties = SDL_CreateProperties();
    if (properties) SDL_SetStringProperty(properties, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, name);

    SDL_GPUTextureCreateInfo texture_info = {
        .type   = SDL_GPU_TEXTURETYPE_2D,
        .format = format,
        .usage  = SDL_GPU_TEXTUREUSAGE_SAMPLER,
        .width  = (Uint32)width,
        .height = (Uint32)height,

        .layer_count_or_depth = 1,
        .num_levels = 1,

        .props = properties,
    };

    SDL_GPUTexture *texture = SDL_CreateGPUTexture(device, &texture_info);
    if (!texture) {
        log_error("Failed to create gpu texture (%s).", SDL_GetError());
        return NULL;
    }
    if (properties) SDL_DestroyProperties(properties);

    Uint32 upload_size = width * height * channels;
    SDL_GPUTransferBufferCreateInfo	transfer_buffer_info = {
        .usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
        .size  = upload_size,
    };

    SDL_GPUTransferBuffer *transfer_buffer = SDL_CreateGPUTransferBuffer(device, &transfer_buffer_info);
    if (!transfer_buffer) {
        log_error("Failed to create gpu transfer buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTexture(device, texture);
        return NULL;
    }

    void *transfer_data = SDL_MapGPUTransferBuffer(device, transfer_buffer, false);
    if (!transfer_data) {
        log_error("Failed to map gpu transfer buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        return NULL;
    }
    memcpy(transfer_data, data, upload_size);
    SDL_UnmapGPUTransferBuffer(device, transfer_buffer);

    SDL_GPUTextureTransferInfo transfer_info = {
        .transfer_buffer = transfer_buffer,
        .pixels_per_row  = (Uint32)width,
    };

    SDL_GPUTextureRegion tetxure_region = {
        .texture = texture,
        .w = (Uint32)width,
        .h = (Uint32)height,
        .d = 1,
    };

    SDL_GPUCommandBuffer *command_buffer = SDL_AcquireGPUCommandBuffer(device);
    if (!command_buffer) {
        log_error("Failed to acquire gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        return NULL;
    }

    SDL_GPUCopyPass *copy_pass = SDL_BeginGPUCopyPass(command_buffer);
    SDL_UploadToGPUTexture(copy_pass, &transfer_info, &tetxure_region, false);
    SDL_EndGPUCopyPass(copy_pass);

    if (!SDL_SubmitGPUCommandBuffer(command_buffer)) {
        log_error("Failed to submit gpu command buffer (%s).", SDL_GetError());
        SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
        SDL_ReleaseGPUTexture(device, texture);
        SDL_CancelGPUCommandBuffer(command_buffer);
        return NULL;
    }

    SDL_ReleaseGPUTransferBuffer(device, transfer_buffer);
    return texture;
}

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);
}

bool SelectableImage(const char *label, bool selected, SDL_GPUTextureSamplerBinding *image, const ImVec2& image_size, const ImVec2& uv0 = ImVec2(0, 0), const ImVec2& uv1 = ImVec2(1, 1), Uint8 orientation = 0) {
    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();
    
    if (image) {
        switch (orientation) {
            case 0: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)image, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y)); break;
            case 1: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)image, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y)); break;
            case 2: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)image, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv1.x, uv1.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y)); break;
            case 3: context->CurrentWindow->DrawList->AddImageQuad((ImTextureID)image, min + padding, ImVec2(max.x - padding.x, min.y + padding.y), max - padding, ImVec2(min.x + padding.x, max.y - padding.y), ImVec2(uv0.x, uv1.y), ImVec2(uv0.x, uv0.y), ImVec2(uv1.x, uv0.y), ImVec2(uv1.x, uv1.y)); break;
            default: SDL_assert_always(false); break;
        }
    }

    return pressed;
}

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);
}

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);
}

V3 Unproject(V3 screen_pos) {
    V4 result = inverse_view_matrix * inverse_projection_matrix * V4_(screen_pos, 1.0f);
    result.xyz /= result.w;

    return result.xyz;
}

V2 get_floor_intersection_of_mouse(V2 mouse_pos) {
    V2 mouse = remap(V2{ 0, 0 }, V2{ (float)window_width, (float)window_height }, V2{ -1, 1 }, V2{ 1, -1 }, V2{ mouse_pos.x, mouse_pos.y });
    V3 camera_position = (inverse_view_matrix * V4_(0, 0, 0, 1)).xyz;

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

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

    floor_intersection.xy += V2_(0.5f, 0.5f);

    return floor_intersection.xy;
}

int main(int argc, char **argv) {
    load_map();

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

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

    device = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, true, NULL);
    if (!device) {
        log_error("Failed to create gpu device (%s). Exiting.", SDL_GetError());
        return 1;
    }
    SDL_SetGPUAllowedFramesInFlight(device, 1);
    
    window = SDL_CreateWindow("Mikemon", 1280, 720, SDL_WINDOW_RESIZABLE);
    if (!window) {
        log_error("Failed to create window (%s). Exiting.", SDL_GetError());
        return 1;
    }

    if (!SDL_ClaimWindowForGPUDevice(device, window)) {
        log_error("Failed to claim window for gpu device (%s). Exiting.", SDL_GetError());
        return 1;
    }
    SDL_SetGPUSwapchainParameters(device, window, SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR, SDL_GPU_PRESENTMODE_VSYNC);

    SDL_GPUShaderCreateInfo basic_vertex_shader_info = {
        .code_size  = SDL_arraysize(SPIRV_basic),
        .code       = SPIRV_basic,
        .entrypoint = "main_vertex",
        .format     = SDL_GPU_SHADERFORMAT_SPIRV,
        .stage      = SDL_GPU_SHADERSTAGE_VERTEX,

        .num_uniform_buffers = 1,
    };

    SDL_GPUShader *basic_vertex_shader = SDL_CreateGPUShader(device, &basic_vertex_shader_info);
    if (!basic_vertex_shader) {
        log_error("Failed to create basic vertex shader. Exiting.");
        return 1;
    }

    SDL_GPUShaderCreateInfo basic_pixel_shader_info = {
        .code_size  = SDL_arraysize(SPIRV_basic),
        .code       = SPIRV_basic,
        .entrypoint = "main_fragment",
        .format     = SDL_GPU_SHADERFORMAT_SPIRV,
        .stage      = SDL_GPU_SHADERSTAGE_FRAGMENT,

        .num_samplers = 1,
    };

    SDL_GPUShader *basic_pixel_shader = SDL_CreateGPUShader(device, &basic_pixel_shader_info);
    if (!basic_pixel_shader) {
        log_error("Failed to create basic pixel shader. Exiting.");
        return 1;
    }

    SDL_GPUShaderCreateInfo world_vertex_shader_info = {
        .code_size  = SDL_arraysize(SPIRV_world),
        .code       = SPIRV_world,
        .entrypoint = "main_vertex",
        .format     = SDL_GPU_SHADERFORMAT_SPIRV,
        .stage      = SDL_GPU_SHADERSTAGE_VERTEX,

        .num_storage_buffers = 1,
        .num_uniform_buffers = 1,
    };

    SDL_GPUShader *world_vertex_shader = SDL_CreateGPUShader(device, &world_vertex_shader_info);
    if (!world_vertex_shader) {
        log_error("Failed to create world vertex shader. Exiting.");
        return 1;
    }

    SDL_GPUShaderCreateInfo world_pixel_shader_info = {
        .code_size  = SDL_arraysize(SPIRV_world),
        .code       = SPIRV_world,
        .entrypoint = "main_fragment",
        .format     = SDL_GPU_SHADERFORMAT_SPIRV,
        .stage      = SDL_GPU_SHADERSTAGE_FRAGMENT,

        .num_samplers = 1,
    };

    SDL_GPUShader *world_pixel_shader = SDL_CreateGPUShader(device, &world_pixel_shader_info);
    if (!world_pixel_shader) {
        log_error("Failed to create world pixel shader. Exiting.");
        return 1;
    }

    SDL_GPUColorTargetDescription color_target_descriptions[] = {
        {
            .format      = SDL_GetGPUSwapchainTextureFormat(device, window),
            .blend_state = {
                .src_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
                .dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                .color_blend_op        = SDL_GPU_BLENDOP_ADD,

                .src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
                .dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                .alpha_blend_op        = SDL_GPU_BLENDOP_ADD,
                
                .enable_blend     = true,
            },
        },
    };

    {
        SDL_GPUVertexBufferDescription vertex_buffer_descriptions[] = {
            {
                .slot       = 0,
                .pitch      = sizeof(Vertex),
                .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
            },
            {
                .slot               = 1,
                .pitch              = sizeof(Instance),
                .input_rate         = SDL_GPU_VERTEXINPUTRATE_INSTANCE,
                .instance_step_rate = 1,
            },
        };

        SDL_GPUVertexAttribute vertex_attributes[] = {
            {
                .location    = 0,
                .buffer_slot = 0,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
                .offset      = offsetof(Vertex, pos),
            },
            {
                .location    = 1,
                .buffer_slot = 1,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
                .offset      = offsetof(Instance, pos),
            },
            {
                .location    = 2,
                .buffer_slot = 1,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
                .offset      = offsetof(Instance, uv0uv1),
            },
            {
                .location    = 3,
                .buffer_slot = 1,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
                .offset      = offsetof(Instance, uv2uv3),
            },
        };

        SDL_GPUGraphicsPipelineCreateInfo basic_graphics_pipeline_info = {
            .vertex_shader      = basic_vertex_shader,
            .fragment_shader    = basic_pixel_shader,
            .vertex_input_state = {
                .vertex_buffer_descriptions = vertex_buffer_descriptions,
                .num_vertex_buffers         = SDL_arraysize(vertex_buffer_descriptions),
                .vertex_attributes          = vertex_attributes,
                .num_vertex_attributes      = SDL_arraysize(vertex_attributes),
            },
            .primitive_type   = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_CLOCKWISE,
                .enable_depth_clip = true,
            },
            .target_info = {
                .color_target_descriptions = color_target_descriptions,
                .num_color_targets         = SDL_arraysize(color_target_descriptions),
            },
        };

        basic_graphics_pipeline = SDL_CreateGPUGraphicsPipeline(device, &basic_graphics_pipeline_info);
        if (!basic_graphics_pipeline) {
            log_error("Failed to create basic graphics pipeline. Exiting.");
            return 1;
        }

        SDL_ReleaseGPUShader(device, basic_vertex_shader);
        SDL_ReleaseGPUShader(device, basic_pixel_shader);
    }

    {
        SDL_GPUVertexBufferDescription vertex_buffer_descriptions[] = {
            {
                .slot       = 0,
                .pitch      = sizeof(Vertex),
                .input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
            },
            {
                .slot               = 1,
                .pitch              = sizeof(Uint32),
                .input_rate         = SDL_GPU_VERTEXINPUTRATE_INSTANCE,
                .instance_step_rate = 1,
            },
        };

        SDL_GPUVertexAttribute vertex_attributes[] = {
            {
                .location    = 0,
                .buffer_slot = 0,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
                .offset      = offsetof(Vertex, pos),
            },
            {
                .location    = 1,
                .buffer_slot = 1,
                .format 	 = SDL_GPU_VERTEXELEMENTFORMAT_UINT,
                .offset      = 0,
            },
        };

        SDL_GPUGraphicsPipelineCreateInfo world_graphics_pipeline_info = {
            .vertex_shader      = world_vertex_shader,
            .fragment_shader    = world_pixel_shader,
            .vertex_input_state = {
                .vertex_buffer_descriptions = vertex_buffer_descriptions,
                .num_vertex_buffers         = SDL_arraysize(vertex_buffer_descriptions),
                .vertex_attributes          = vertex_attributes,
                .num_vertex_attributes      = SDL_arraysize(vertex_attributes),
            },
            .primitive_type   = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
            .rasterizer_state = {
                .fill_mode  = SDL_GPU_FILLMODE_FILL,
                .cull_mode  = SDL_GPU_CULLMODE_BACK,
                .front_face = SDL_GPU_FRONTFACE_CLOCKWISE,
                .enable_depth_clip = true,
            },
            .target_info = {
                .color_target_descriptions = color_target_descriptions,
                .num_color_targets         = SDL_arraysize(color_target_descriptions),
            },
        };

        world_graphics_pipeline = SDL_CreateGPUGraphicsPipeline(device, &world_graphics_pipeline_info);
        if (!world_graphics_pipeline) {
            log_error("Failed to create world graphics pipeline. Exiting.");
            return 1;
        }

        SDL_ReleaseGPUShader(device, world_vertex_shader);
        SDL_ReleaseGPUShader(device, world_pixel_shader);
    }

    SDL_GPUTexture *player_texture = create_shader_texture("../assets/decorations/strawberry.png");
    if (!player_texture) {
        log_error("Failed to create shader texture. Exiting.");
        return 1;
    }

    int tile_border_size = 4;
    tile_atlas = sma_atlas_create(tile_atlas_size, tile_atlas_size);
    char *tile_atlas_texture_cpu = (char *)calloc(1, tile_atlas_size * tile_atlas_size * 4);
    for (int i = 0; i < SDL_arraysize(tile_infos); i++) {
        int width = 0, height = 0;  
        stbi_uc *data = stbi_load(tile_infos[i].asset_path, &width, &height, NULL, 4);
        if (!data) {
            log_error("Failed to load texture (\"%s\"). Exiting.", tile_infos[i].asset_path);
            return 1;
        }

        smol_atlas_item_t *atlas_item = sma_item_add(tile_atlas, width + 2 * tile_border_size, height + 2 * tile_border_size);
        if (!atlas_item) {
            log_error("Failed to add tile texture to atlas. Exiting.");
            return 1;
        }
        int item_x = sma_item_x(atlas_item);
        int item_y = sma_item_y(atlas_item);

        tile_infos[i].uv_min = V2_((item_x + tile_border_size)         / (float)tile_atlas_size, (item_y + tile_border_size)          / (float)tile_atlas_size);
        tile_infos[i].uv_max = V2_((item_x + tile_border_size + width) / (float)tile_atlas_size, (item_y + tile_border_size + height) / (float)tile_atlas_size);

        cpu_tile_infos_buffer[i] = V4_(tile_infos[i].uv_min, tile_infos[i].uv_max);

        blit(tile_atlas_texture_cpu, tile_atlas_size, item_x + tile_border_size, item_y + tile_border_size, (char *)data, width, width, height);
        
        #define image_index(image, width, components, x, y) (*(((char *)(image)) + (((y) * (width) + (x)) * (components))))
        for (int j = 0; j < tile_border_size; j++) {
            // HORIZONTAL EDGES
            blit(tile_atlas_texture_cpu, tile_atlas_size, item_x + tile_border_size, item_y + j,                             &image_index(data, width, 4, 0, 0),          width, width, 1);
            blit(tile_atlas_texture_cpu, tile_atlas_size, item_x + tile_border_size, item_y + tile_border_size + height + j, &image_index(data, width, 4, 0, height - 1), width, width, 1);
            // VERTICAL EDGES
            blit(tile_atlas_texture_cpu, tile_atlas_size, item_x + j,                            item_y + tile_border_size, &image_index(data, width, 4, 0,         0), width, 1, height);
            blit(tile_atlas_texture_cpu, tile_atlas_size, item_x + tile_border_size + width + j, item_y + tile_border_size, &image_index(data, width, 4, width - 1, 0), width, 1, height);
        }

        for (int y = 0; y < tile_border_size; y++) {
            for (int x = 0; x < tile_border_size; x++) {
                // TOP CORNERS
                *(Uint32 *)&image_index(tile_atlas_texture_cpu, tile_atlas_size, 4, item_x + x,                            item_y + y) = *(Uint32 *)&image_index(data, width, 4, 0,         0);
                *(Uint32 *)&image_index(tile_atlas_texture_cpu, tile_atlas_size, 4, item_x + tile_border_size + width + x, item_y + y) = *(Uint32 *)&image_index(data, width, 4, width - 1, 0);
                // BOTTOM CORNERS
                *(Uint32 *)&image_index(tile_atlas_texture_cpu, tile_atlas_size, 4, item_x + x,                            item_y + tile_border_size + height + y) = *(Uint32 *)&image_index(data, width, 4, 0,         height - 1);
                *(Uint32 *)&image_index(tile_atlas_texture_cpu, tile_atlas_size, 4, item_x + tile_border_size + width + x, item_y + tile_border_size + height + y) = *(Uint32 *)&image_index(data, width, 4, width - 1, height - 1);
            }
        }
        stbi_image_free(data);
    }
    sma_atlas_destroy(tile_atlas);

    SDL_GPUTexture *tile_atlas_texture = create_shader_texture("tile_atlas", tile_atlas_texture_cpu, tile_atlas_size, tile_atlas_size, 4);
    free(tile_atlas_texture_cpu);
    if (!tile_atlas_texture) {
        log_error("Failed to create tile atlas texture. Exiting.");
        return 1;
    }

    SDL_GPUSamplerCreateInfo point_sampler_info = {
        .min_filter  = SDL_GPU_FILTER_NEAREST,
        .mag_filter  = SDL_GPU_FILTER_NEAREST,
        .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,

        .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
        .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,

        .max_anisotropy    = 4.0f,
        .enable_anisotropy = true,
    };

    point_sampler = SDL_CreateGPUSampler(device, &point_sampler_info);
    SDL_GPUTextureSamplerBinding tile_atlas_texture_binding = { .texture = tile_atlas_texture, .sampler = point_sampler };

    vertex_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, sizeof(vertices), vertices, "vertex_buffer");
    if (!vertex_buffer) {
        log_error("Failed to create buffer. Exiting.");
        return 1;
    }

    index_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_INDEX, sizeof(indices), indices, "index_buffer");
    if (!index_buffer) {
        log_error("Failed to create buffer. Exiting.");
        return 1;
    }

    player_instance_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, sizeof(player_instance), &player_instance, "player_instance_buffer");
    if (!player_instance_buffer) {
        log_error("Failed to create buffer. Exiting.");
        return 1;
    }

    world_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, map_width * map_height * 4, map_tiles, "world_buffer");
    if (!world_buffer) {
        log_error("Failed to create buffer. Exiting.");
        return 1;
    }

    tile_infos_buffer = create_buffer(SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ, SDL_arraysize(cpu_tile_infos_buffer) * sizeof(*cpu_tile_infos_buffer), cpu_tile_infos_buffer, "tile_infos_buffer");
    if (!tile_infos_buffer) {
        log_error("Failed to create buffer. Exiting.");
        return 1;
    }

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

    ImGui::StyleColorsDark();

    if (io.ConfigFlags & ImGuiConfigFlags_IsSRGB){
        for (int i = 0; i < IM_ARRAYSIZE(imgui_context->Style.Colors); i++) {
            imgui_context->Style.Colors[i] = sRGB_to_linear(imgui_context->Style.Colors[i]);
        }
    }

    ImGui_ImplSDL3_InitForSDLGPU(window);
    ImGui_ImplSDLGPU3_InitInfo imgui_init_info = {
        .Device            = device,
        .ColorTargetFormat = SDL_GetGPUSwapchainTextureFormat(device, window),
        .MSAASamples       = SDL_GPU_SAMPLECOUNT_1,
    };
    ImGui_ImplSDLGPU3_Init(&imgui_init_info);

    bool show_demo_window = true;
    bool first_frame      = true;

    SDL_GetWindowSizeInPixels(window, &window_width, &window_height);

    // MSG Message;
    while (Running) {
        ZoneScopedN("Loop");
        ImGui_ImplSDLGPU3_NewFrame();
        ImGui_ImplSDL3_NewFrame();
        ImGui::NewFrame();

        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSizeConstraints(ImVec2(0, window_height), ImVec2(window_width, window_height));
        ImGui::SetNextWindowSize(ImVec2(0.2 * window_width, window_height), ImGuiCond_FirstUseEver);
        if (ImGui::Begin("Tile Picker", NULL, ImGuiWindowFlags_NoFocusOnAppearing)) {
            if (SelectableImage("##tile", selected_tile == -1, NULL, ImVec2(32, 32))) {
                selected_tile = -1;
            }

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

                ImVec2 uv0 = ImVec2(tile_infos[i].uv_min.x, tile_infos[i].uv_min.y);
                ImVec2 uv1 = ImVec2(tile_infos[i].uv_max.x, tile_infos[i].uv_max.y);

                float available = ImGui::GetContentRegionAvail().x - ImGui::GetItemRectMax().x;
                if (available >= 32)
                    ImGui::SameLine();

                if (SelectableImage("##tile", selected_tile == i, &tile_atlas_texture_binding, ImVec2(32, 32), uv0, uv1, SDL_max(selected_rotation, 0)))
                    selected_tile = i;

                ImGui::PopID();
            }

            if (selected_tile != -1) {
                ImGui::Text("Rotation:");

                ImVec2 uv0 = ImVec2(tile_infos[selected_tile].uv_min.x, tile_infos[selected_tile].uv_min.y);
                ImVec2 uv1 = ImVec2(tile_infos[selected_tile].uv_max.x, tile_infos[selected_tile].uv_max.y);
            
                if (SelectableImage("##None", selected_rotation == 0, &tile_atlas_texture_binding, ImVec2(32, 32), uv0, uv1, 0))
                    selected_rotation = 0;
                
                if (ImGui::GetContentRegionAvail().x - ImGui::GetItemRectMax().x >= 32)
                    ImGui::SameLine();

                if (SelectableImage("##90",   selected_rotation == 1, &tile_atlas_texture_binding, ImVec2(32, 32), uv0, uv1, 1))
                    selected_rotation = 1;

                if (ImGui::GetContentRegionAvail().x - ImGui::GetItemRectMax().x >= 32)
                    ImGui::SameLine();

                if (SelectableImage("##180",  selected_rotation == 2, &tile_atlas_texture_binding, ImVec2(32, 32), uv0, uv1, 2))
                    selected_rotation = 2;

                if (ImGui::GetContentRegionAvail().x - ImGui::GetItemRectMax().x >= 32)
                    ImGui::SameLine();

                if (SelectableImage("##270",  selected_rotation == 3, &tile_atlas_texture_binding, ImVec2(32, 32), uv0, uv1, 3))
                    selected_rotation = 3;

                if (ImGui::Selectable("Random", selected_rotation == -1))
                    selected_rotation = -1;
            }

            ImGui::DragFloat("fovy",            &per_frame.fovy_degrees);
            ImGui::DragFloat("camera_distance", &per_frame.camera_distance, 0.25f, 1.0f, INFINITY);
            ImGui::DragFloat("camera_tilt",     &per_frame.camera_tilt,     0.25f, 0.0f, 89.0f);
        }
        ImGui::End();

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

        if (first_frame) {
            ImGui::SetWindowFocus(NULL);
            first_frame = false;
        }

        SDL_GPUCommandBuffer *command_buffer = SDL_AcquireGPUCommandBuffer(device);
        if (!command_buffer) {
            log_error("Failed to acquire gpu command buffer (%s). Exiting.", SDL_GetError());
            return 1;
        }

        TracyCZoneN(tracy_AcquireSwapchainTexture, "AcquireSwapchainTexture", true);
        Uint32 width = 0, height = 0;
        SDL_GPUTexture *swapchain_texture = NULL;
        if (!SDL_WaitAndAcquireGPUSwapchainTexture(command_buffer, window, &swapchain_texture, &width, &height)) {
            log_error("Failed to acquire swapchain texture (%s). Exiting.", SDL_GetError());
            SDL_CancelGPUCommandBuffer(command_buffer);
            return 1;
        }
        TracyCZoneEnd(tracy_AcquireSwapchainTexture);

        {
            ZoneScopedN("Events");
            SDL_Event event;
            while (SDL_PollEvent(&event)) {
                ZoneScopedN("Event");
                ImGui_ImplSDL3_ProcessEvent(&event);

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

                    case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: {
                        window_width  = event.window.data1;
                        window_height = event.window.data2;
                    } break;

                    case SDL_EVENT_KEY_DOWN: {
                        if (io.WantCaptureKeyboard)
                            continue;

                        if (event.key.key == SDLK_UP || event.key.key == SDLK_W) {
                            player.pos_y = clamp(0, player.pos_y + 1, map_height - 2);
                        }

                        if (event.key.key == SDLK_LEFT || event.key.key == SDLK_A) {
                            player.pos_x = clamp(0, player.pos_x - 1, map_width - 2);
                        }

                        if (event.key.key == SDLK_DOWN || event.key.key == SDLK_S) {
                            player.pos_y = clamp(0, player.pos_y - 1, map_height - 2);
                        }

                        if (event.key.key == SDLK_RIGHT || event.key.key == SDLK_D) {
                            player.pos_x = clamp(0, player.pos_x + 1, map_width - 2);
                        }

                        if (event.key.key == SDLK_F1) {
                            save_map();
                        }

                        if (event.key.key == SDLK_F4) {
                            load_map();
                        }
                    } break;

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

                        V2 floor_intersection = get_floor_intersection_of_mouse(V2_(event.button.x, event.button.y));

                        Sint32 tile_x = roundf(floor_intersection.x);
                        Sint32 tile_y = roundf(floor_intersection.y);

                        if(0 <= tile_x && tile_x < map_width && 0 <= tile_y && tile_y < map_height) {
                            dragging_tile_change = true;

                            drag_start_pos[0] = tile_x;
                            drag_start_pos[1] = tile_y;
                        }
                        
                        SDL_Keymod modifiers = SDL_GetModState();
                        if (modifiers & SDL_KMOD_SHIFT && tile_x <= -1) {
                            if(modifiers & SDL_KMOD_CTRL)
                                change_map_size('W', -1);
                            else
                                change_map_size('W', 1);
                        }

                        if (modifiers & SDL_KMOD_SHIFT && tile_x == map_width) {
                            if (modifiers & SDL_KMOD_CTRL)
                                change_map_size('E', -1);
                            else
                                change_map_size('E', 1);
                        }

                        if (modifiers & SDL_KMOD_SHIFT && tile_y <= -1) {
                            if (modifiers & SDL_KMOD_CTRL)
                                change_map_size('N', -1);
                            else
                                change_map_size('N', 1);
                        }

                        if (modifiers & SDL_KMOD_SHIFT && tile_y == map_height) {
                            if (modifiers & SDL_KMOD_CTRL)
                                change_map_size('S', -1);
                            else
                                change_map_size('S', 1);
                        }
                    } break;
                    
                    case SDL_EVENT_MOUSE_BUTTON_UP: {
                        if (io.WantCaptureMouse)
                            continue;

                        if (selected_tile != -1 && dragging_tile_change) {
                            V2 floor_intersection = get_floor_intersection_of_mouse(V2_(event.button.x, event.button.y));
        
                            Sint32 tile_x = clamp(0, (Sint32)roundf(floor_intersection.x), map_width - 1);
                            Sint32 tile_y = clamp(0, (Sint32)roundf(floor_intersection.y), map_height - 1);

                            Sint32 start_x = min(tile_x, drag_start_pos[0]);
                            Sint32 start_y = min(tile_y, drag_start_pos[1]);

                            Sint32 end_x = max(tile_x, drag_start_pos[0]);
                            Sint32 end_y = max(tile_y, drag_start_pos[1]);

                            for (Sint32 y = start_y; y <= end_y; y++) {
                                for (Sint32 x = start_x; x <= end_x; x++) {
                                    if (selected_rotation == -1) {
                                        Sint32 rotation = SDL_rand(4);
                                        map_tiles[x + map_width * y] = ((rotation & 3) << 16) | selected_tile;
                                    } else {
                                        map_tiles[x + map_width * y] = ((selected_rotation & 3) << 16) | selected_tile;
                                    }
                                }
                            }
                        
                            update_buffer(world_buffer, 0, map_width * map_height * sizeof(Uint32), map_tiles);
                        }

                        dragging_tile_change = false;
                    } break;;
                }
            }
        }

        if (!swapchain_texture) {
            SDL_CancelGPUCommandBuffer(command_buffer);
            ImGui::Render();
            ImDrawData *draw_data = ImGui::GetDrawData();
            continue;
        }
        
        {
            ZoneScopedN("Update");
            {
                ZoneScopedN("player_instance_buffer");
                player_instance.pos.x = player.pos_x;
                player_instance.pos.y = player.pos_y;

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

            {
                ZoneScopedN("per_frame");
                per_frame.aspect_ratio = ((float) window_width / (float) window_height);
                per_frame.map_width    = map_width;
                per_frame.camera_x     = player.pos_x;
                per_frame.camera_y     = player.pos_y;

                SDL_PushGPUVertexUniformData(command_buffer, 0, &per_frame, sizeof(per_frame));
            }

            {
                ZoneScopedN("matrices");
                view_matrix         = view        (V3_((float)player.pos_x, (float)player.pos_y, 0), radians(per_frame.camera_tilt), per_frame.camera_distance);
                inverse_view_matrix = inverse_view(V3_((float)player.pos_x, (float)player.pos_y, 0), radians(per_frame.camera_tilt), per_frame.camera_distance);

                projection_matrix         = projection        (radians(per_frame.fovy_degrees), per_frame.aspect_ratio, NEAR_PLANE);
                inverse_projection_matrix = inverse_projection(radians(per_frame.fovy_degrees), per_frame.aspect_ratio, NEAR_PLANE);
            }
        }

        SDL_GPUColorTargetInfo color_target_info = {
            .texture     = swapchain_texture,
            .clear_color = { .r = 1.0f, .g = 0.0f, .b = 1.0f, .a = 1.0f },
            .load_op     = SDL_GPU_LOADOP_CLEAR,
            .store_op    = SDL_GPU_STOREOP_STORE,
        };

        SDL_GPURenderPass *render_pass = SDL_BeginGPURenderPass(command_buffer, &color_target_info, 1, NULL);
        if (!render_pass) {
            log_error("Failed to begin render pass. Exiting.");
            return 1;
        }

        { // Draw Map
            ZoneScopedN("Draw Map");
            SDL_GPUBufferBinding index_buffer_binding = { .buffer = index_buffer, .offset = 0 };
            SDL_GPUBufferBinding vertex_buffers[] = {
                { .buffer = vertex_buffer, .offset = 0 },
                { .buffer = world_buffer,  .offset = 0 },
            };
            SDL_GPUTextureSamplerBinding texture_bindings[] = {
                { .texture = tile_atlas_texture, .sampler = point_sampler },
            };

            SDL_BindGPUGraphicsPipeline(render_pass, world_graphics_pipeline);
            SDL_BindGPUIndexBuffer(render_pass, &index_buffer_binding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
            SDL_BindGPUVertexBuffers(render_pass, 0, vertex_buffers, SDL_arraysize(vertex_buffers));
            SDL_BindGPUVertexStorageBuffers(render_pass, 0, &tile_infos_buffer, 1);
            SDL_BindGPUFragmentSamplers(render_pass, 0, texture_bindings, SDL_arraysize(texture_bindings));
            SDL_DrawGPUIndexedPrimitives(render_pass, 6, map_height * map_width, 0, 0, 0);
        }

        { // Draw Player
            ZoneScopedN("Draw Player");
            SDL_GPUBufferBinding vertex_buffers[] = {
                { .buffer = vertex_buffer,          .offset = 0 },
                { .buffer = player_instance_buffer, .offset = 0 },
            };
            SDL_GPUTextureSamplerBinding texture_bindings[] = {
                { .texture = player_texture, .sampler = point_sampler },
            };

            SDL_BindGPUGraphicsPipeline(render_pass, basic_graphics_pipeline);
            SDL_BindGPUVertexBuffers(render_pass, 0, vertex_buffers, SDL_arraysize(vertex_buffers));
            SDL_BindGPUFragmentSamplers(render_pass, 0, texture_bindings, SDL_arraysize(texture_bindings));
            SDL_DrawGPUIndexedPrimitives(render_pass, 6, 1, 0, 0, 0);
        }
        SDL_EndGPURenderPass(render_pass);

        {
            ZoneScopedN("ImGui Render");
            ImGui::Render();
            ImDrawData *draw_data = ImGui::GetDrawData();
            Imgui_ImplSDLGPU3_PrepareDrawData(draw_data, command_buffer);

            SDL_GPUColorTargetInfo imgui_color_target_info = {
                .texture     = swapchain_texture,
                .load_op     = SDL_GPU_LOADOP_LOAD,
                .store_op    = SDL_GPU_STOREOP_STORE,
            };

            SDL_GPURenderPass *imgui_render_pass = SDL_BeginGPURenderPass(command_buffer, &imgui_color_target_info, 1, NULL);
            ImGui_ImplSDLGPU3_RenderDrawData(draw_data, command_buffer, imgui_render_pass);
            SDL_EndGPURenderPass(imgui_render_pass);
        }

        {
            ZoneScopedN("SubmitGPUCommandBuffer");
            if (!SDL_SubmitGPUCommandBuffer(command_buffer)) {
                log_error("Failed to submit gpu command buffer (%s). Exiting.", SDL_GetError());
                return 1;
            }
        }
        FrameMark;
    }
    return 0;
}
