#ifndef prototype1_src_l2_lucy_rendering_h
#define prototype1_src_l2_lucy_rendering_h

#include "glyph_cache.h"
#include "../../../gen/l1/pixel_masses.h"
#include "../../../gen/l1/geom.h"

// todo: rewrite this crrp crap using instances
typedef struct{
    vec4 color;
    vec2 pos;
    vec2 tex_cord;
    U32 tex_ind;
} LucyVertex;


typedef struct {
    vec4 color;
    vec2 lt_pos;
    vec2 br_pos;
    vec2 lt_tex;
    vec2 br_tex;
} LucyGlyphInstance;

typedef struct {
    MargaretEngineReference ve;
    LucyGlyphCache* cache;
    VkPipelineLayout pipeline_layout;
    VkPipeline pipeline;

    U64 glyphs_count;
    MargaretSubbuf staging_vbo;
    MargaretSubbuf vbo;
    bool need_to_transfer;
} LucyRenderer;

typedef struct {
    float width, height;
} LucyRendererPushConstants;

LucyRenderer LucyRenderer_new(
    MargaretEngineReference ve, LucyGlyphCache* cache, SpanU8 root_dir,
    VkRenderPass render_pass, U32 renderpass_subpass){

    VkPipelineLayout pipeline_layout;
    check(vkCreatePipelineLayout(ve.device, &(VkPipelineLayoutCreateInfo){
        .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
        .setLayoutCount = 1,
        .pSetLayouts = &cache->descriptor_set_layout,
        .pushConstantRangeCount = 1,
        .pPushConstantRanges = (VkPushConstantRange[]){{
            .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(LucyRendererPushConstants)
        }},
    }, NULL, &pipeline_layout) == VK_SUCCESS);

    VkPipeline pipeline = margaret_create_triangle_pipeline_one_attachment(ve.device,
        render_pass, renderpass_subpass, (MargaretMostImportantPipelineOptions){
            .pipeline_layout = pipeline_layout,
            .vertex_shader_code = read_file_by_path(VecU8_fmt("%s/gen/l_adele/lucy/vert.spv", root_dir)),
            .fragment_shader_code = read_file_by_path(VecU8_fmt("%s/gen/l_adele/lucy/frag.spv", root_dir)),
            .vertexBindingDescriptionCount = 1,
            .pVertexBindingDescriptions = (VkVertexInputBindingDescription[]){
                { .binding = 0, .stride = sizeof(LucyVertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX } },
            .vertexAttributeDescriptionCount = 4,
            .pVertexAttributeDescriptions = (VkVertexInputAttributeDescription[]){
                {.location = 0, .binding = 0,
                    .format = VK_FORMAT_R32G32B32A32_SFLOAT, .offset = offsetof(LucyVertex, color)},
                {.location = 1, .binding = 0,
                    .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(LucyVertex, pos)},
                {.location = 2, .binding = 0,
                    .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(LucyVertex, tex_cord)},
                {.location = 3, .binding = 0,
                    .format = VK_FORMAT_R32_UINT, .offset = offsetof(LucyVertex, tex_ind)},
            },
            .depthTestEnable = false, .depthWriteEnable = false, .blendEnable = true,
        });

    return (LucyRenderer){.ve = ve, .cache = cache, .pipeline_layout = pipeline_layout, .pipeline = pipeline,
        .glyphs_count = 0, .staging_vbo = MargaretBufAllocator_alloc(ve.staging_buffers, 67),
        .vbo = MargaretBufAllocator_alloc(ve.dev_local_buffers, 67)
    };
}

/* When another_frame starts, you are safe to call this function, but you also have to call it
 * before LucyRenderer_another_frame
 */
void LucyRenderer_clear(LucyRenderer* self){
    self->glyphs_count = 0;
}

/* When another_frame starts, you are safe to call this function, but you also have to call it
 * before LucyRenderer_another_frame, because _another_frame method records transfer of data
 */
void LucyRenderer_add_text(
    LucyRenderer* self, RBTreeNodeLucyFaceFixedSize* ffs, vec4 color,
    U32 additional_y_advance, SpanU8 text, ivec2 start_pos
    ){
    self->need_to_transfer = true;
    U32 font_height = ffs->key;
    ivec2 pos = (ivec2){start_pos.x, start_pos.y + (S32)font_height};
    /* `text` variable will be modified during decoding */
    while (text.len > 0) {
        U32 codepoint = SpanU8_decode_as_utf8(&text);
        if (codepoint == (U32)'\n') {
            pos = (ivec2){start_pos.x, pos.y + (S32)font_height + (S32)additional_y_advance};
            continue;
        }
        BufRBTree_MapU32ToLucyStoredGlyph *glyphs = &ffs->value.glyphs;
        U64 map_it = BufRBTree_MapU32ToLucyStoredGlyph_find(glyphs, codepoint);
        if (map_it == 0) {
            /* We probably should have requested LucyCache to load more glyphs or draw 'unknown character'
             * character, but we just skip things. We will force someone else to do that job */
            continue;
        }
        assert(map_it > 0 && map_it < glyphs->tree.len);
        LucyStoredGlyph* glyph = &glyphs->el.buf[map_it - 1].value;
        if (glyph->w > 0 && glyph->h > 0) {
            OptionLucyImage* img_slot = VecOptionLucyImage_mat(&self->cache->image_slots, glyph->img_slot_id);
            assert(img_slot->variant == Option_Some);
            LucyImage* img = &img_slot->some;
            float atlas_w = (float)img->img.width;
            float atlas_h = (float)img->img.height;
            U32 desc_elem_id = glyph->img_slot_id;
            ivec2 positioned = ivec2_add_ivec2(pos, glyph->bearing);

            U64 needed_vbo_length = (self->glyphs_count + 1) * 6 * sizeof(LucyVertex);
            if (self->staging_vbo.len < needed_vbo_length) {
                printf("LucyRenderer Staging Buffer: Gotta replace %lu with %lu\n",
                    self->staging_vbo.len, needed_vbo_length);
                MargaretBufAllocator_expand_or_move_old_host_visible(
                    self->ve.staging_buffers, &self->staging_vbo, needed_vbo_length);
            }

            LucyVertex* vbo_data = (LucyVertex*)MargaretSubbuf_get_mapped(&self->staging_vbo);
            vbo_data += self->glyphs_count * 6;

            LucyVertex v0 = {
                .color = color, .pos = (vec2){(float)positioned.x, (float)positioned.y},
                .tex_cord = (vec2){
                    (float)(glyph->pos_on_atlas.x) / atlas_w,
                    (float)(glyph->pos_on_atlas.y) / atlas_h
                }, .tex_ind = desc_elem_id
            };
            LucyVertex v1 = {
                .color = color, .pos = (vec2){(float)(positioned.x + glyph->w), (float)positioned.y},
                .tex_cord = (vec2){
                    (float)(glyph->pos_on_atlas.x + glyph->w) / atlas_w,
                    (float)(glyph->pos_on_atlas.y) / atlas_h
                }, .tex_ind = desc_elem_id
            };
            LucyVertex v2 = {
                .color = color, .pos = (vec2){(float)positioned.x, (float)(positioned.y + glyph->h)},
                .tex_cord = (vec2){
                    (float)(glyph->pos_on_atlas.x) / atlas_w,
                    (float)(glyph->pos_on_atlas.y + glyph->h) / atlas_h
                }, .tex_ind = desc_elem_id
            };
            LucyVertex v3 = {
                .color = color, .pos = (vec2){(float)(positioned.x + glyph->w), (float)(positioned.y + glyph->h)},
                .tex_cord = (vec2){
                    (float)(glyph->pos_on_atlas.x + glyph->w) / atlas_w,
                    (float)(glyph->pos_on_atlas.y + glyph->h) / atlas_h
                }, .tex_ind = desc_elem_id
            };
            *(vbo_data++) = v1;
            *(vbo_data++) = v0;
            *(vbo_data++) = v2;
            *(vbo_data++) = v1;
            *(vbo_data++) = v2;
            *(vbo_data++) = v3;

            self->glyphs_count++;
        }
        pos.x += (S32)glyph->advance_x;
    }
}

/* It only records transfer commands (transfer command buffer is passed in MargaretEngineReference object) */
void LucyRenderer_another_frame(LucyRenderer* self){
    U64 needed_vbo_length = self->glyphs_count * 6 * sizeof(LucyVertex);
    if (self->vbo.len < needed_vbo_length) {
        MargaretBufAllocator_expand_or_free_old(self->ve.dev_local_buffers, &self->vbo, needed_vbo_length);
    }
    if ((self->need_to_transfer) && self->glyphs_count > 0) {
        printf("LucyRenderer: we are doing copying\n");

        self->need_to_transfer = false;
        margaret_rec_cmd_copy_buffer_one_to_one_part(self->ve.transfer_cmd_buffer,
            &self->staging_vbo, &self->vbo, 0, needed_vbo_length);
    }
}

// todo: merge with the function above. In the future all the crrp will be recorded simultaneously
void LucyRenderer_another_frame_rec_drawing(
    LucyRenderer* self, VkCommandBuffer drawing_cmd_buf, VkExtent2D image_extent){
    vkCmdBindPipeline(drawing_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipeline);
    vkCmdPushConstants(drawing_cmd_buf, self->pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT,
        0, sizeof(LucyRendererPushConstants),
        &(LucyRendererPushConstants){ .width = (float)image_extent.width, .height = (float)image_extent.height });
    vkCmdBindVertexBuffers(drawing_cmd_buf, 0, 1,
        (VkBuffer[]){MargaretSubbuf_get_buffer(&self->vbo)}, (VkDeviceSize[]){self->vbo.start});
    vkCmdBindDescriptorSets(drawing_cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipeline_layout, 0,
        1, (VkDescriptorSet[]){self->cache->descriptor_set}, 0, NULL);
    vkCmdDraw(drawing_cmd_buf, self->glyphs_count * 6, 1, 0, 0);
}

#endif