#ifndef prototype1_src_l2_lucy_glyph_cache_h
#define prototype1_src_l2_lucy_glyph_cache_h

#include "../margaret/vulkan_utils.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include "../../../gen/l1/VecAndSpan_U32Segment.h"
#include "../../../gen/l1/vulkan/VecVkDescriptorImageInfo.h"
#include "../../../gen/l1/pixel_masses.h"

#include "../../l1_5/core/buff_rb_tree_node.h"
#include "../../l1_5/core/rb_tree_node.h"

#define LUCY_MAX_DESCRIPTOR_COUNT 100

typedef struct {
    /* This value is actually Option<MargaretSubbuf>. If staging_buffer is already deleted (after it is no longer used),
     * staging_buffer.len will be 0 */
    MargaretSubbuf staging_buffer;
    MargaretImg img;
    VkImageView img_view;
    U64 usage;
    U64 pos_in_desc_array;
    /* 0 if this image isn't scheduled for deletion on th next cycle.
     * 1 if it is */
    int scheduled_for_deletion;
} LucyImage;
#include "../../../gen/l1/eve/lucy/ListLucyImage.h"

typedef ListNodeLucyImage* RefListNodeLucyImage;
#include "../../../gen/l1/eve/lucy/VecRefListNodeLucyImage.h"

typedef struct {
    ListNodeLucyImage* img;
    U32 w, h;
    U32 advance_x;
    ivec2 bearing;
    uvec2 pos_on_atlas;
} LucyStoredGlyph;

typedef struct {
    U32 key;
    LucyStoredGlyph value;
} KVPU32ToLucyStoredGlyph;
#include "../../../gen/l1/eve/lucy/VecKVPU32ToLucyStoredGlyph.h"
#include "../../../gen/l1_5/eve/lucy/BufRBTree_MapU32ToLucyStoredGlyph.h"

typedef struct LucyFace LucyFace;

typedef struct{
    LucyFace* p;
    BufRBTree_MapU32ToLucyStoredGlyph glyphs;
} LucyFaceFixedSize;

void LucyFaceFixedSize_drop(LucyFaceFixedSize self){
    BufRBTree_MapU32ToLucyStoredGlyph_drop(self.glyphs);
}

#include "../../../gen/l1_5/eve/lucy/RBTree_MapU32ToLucyFaceFixedSize.h"

// This is a very useful alias
typedef RBTreeNode_KVPU32ToLucyFaceFixedSize RBTreeNodeLucyFaceFixedSize;

typedef struct LucyGlyphCache LucyGlyphCache;

struct LucyFace {
    LucyGlyphCache* p;
    FT_Face ft_face;
    RBTree_MapU32ToLucyFaceFixedSize sizes;
};

struct LucyGlyphCache {
    MargaretEngineReference ve;
    ListLucyImage images;
    VkDescriptorSetLayout descriptor_set_layout;
    VkDescriptorSet descriptor_set;

    /* to_be_freed_of_old_staging_next_cycle never intersect with to_be_copied_to_device_next_cycle */
    VecRefListNodeLucyImage to_be_freed_of_old_staging_next_cycle;
    VecRefListNodeLucyImage to_be_copied_to_device_next_cycle;
    /* deletion will be performed last  */
    VecRefListNodeLucyImage to_be_deleted;
    /* This filed is actually used in later stages of 'another_frame' sequence. It is used by font renderer
     * to know if sus stuff had occurred. At the beginning of my _another_frame method it gets turned false
     * automatically, but during my _another_frame it can get raised true to indicate reshuffling of images */
    bool changes_happened;
};


LucyGlyphCache LucyGlyphCache_new(MargaretEngineReference ve){
    VkDescriptorSetLayout my_desc_set_layout;
    check(vkCreateDescriptorSetLayout(ve.device, &(VkDescriptorSetLayoutCreateInfo){
        .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
        .bindingCount = 1,
        .pBindings = (VkDescriptorSetLayoutBinding[]){{
            .binding = 0,
            .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
            .descriptorCount = LUCY_MAX_DESCRIPTOR_COUNT,
            .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT,
        }},
    }, NULL, &my_desc_set_layout) == VK_SUCCESS);
    VkDescriptorSet descriptor_set = margaret_allocate_descriptor_set(ve.device, ve.descriptor_pool,
        my_desc_set_layout);
    return (LucyGlyphCache){.ve = ve, .images = ListLucyImage_new(),
        .descriptor_set_layout = my_desc_set_layout, .descriptor_set = descriptor_set,
        .to_be_freed_of_old_staging_next_cycle = VecRefListNodeLucyImage_new(),
        .to_be_copied_to_device_next_cycle = VecRefListNodeLucyImage_new(),
        .to_be_deleted = VecRefListNodeLucyImage_new(), .changes_happened = false};
}

void LucyFaceFixedSize_get_rid_of_myself(LucyFaceFixedSize* self){
    LucyGlyphCache* cache = self->p->p;
    BufRBTree_MapU32ToLucyStoredGlyph* glyphs = &self->glyphs;
    for (size_t gid = 0; gid < glyphs->el.len; gid++) {
        ListNodeLucyImage* img = glyphs->el.buf[gid].value.img;
        assert(img->el.usage > 0);
        if (--img->el.usage) {
            assert(!img->el.scheduled_for_deletion);
            img->el.scheduled_for_deletion = 1;
            VecRefListNodeLucyImage_append(&cache->to_be_deleted, img);
        }
    }
    BufRBTree_MapU32ToLucyStoredGlyph_sink(glyphs);
}

typedef struct {
    RBTreeNodeLucyFaceFixedSize* sized_face;
    VecU32Segment codepoint_ranges;
} LucyGlyphCachingRequest;

void LucyGlyphCachingRequest_drop(LucyGlyphCachingRequest self){
    VecU32Segment_drop(self.codepoint_ranges);
}

#include "../../../gen/l1/eve/lucy/VecAndSpan_LucyGlyphCachingRequest.h"

/* Helper structure */
typedef struct {
    LucyFaceFixedSize* sized_face;
    U32 codepoint;
    TextureDataR8 bitmap;
    /* Will be determined in the next phase */
    uvec2 pos;
    ListNodeLucyImage* img;
} LucyPositionedStagingGlyph;

bool LucyPositionedStagingGlyph_less_LucyPositionedStagingGlyph(
    const LucyPositionedStagingGlyph* A, const LucyPositionedStagingGlyph* B){
    return A->bitmap.height < B->bitmap.height;
}

void LucyPositionedStagingGlyph_drop(LucyPositionedStagingGlyph self){
    TextureDataR8_drop(self.bitmap);
}

/* Instantiation for helper type */
#include "../../../gen/l1/eve/lucy/VecLucyPositionedStagingGlyph.h"

/* Helper function */
void LucyGlyphCache_add_glyphs__close_img(
    LucyGlyphCache* cache, ListNodeLucyImage* img, U32 img_width, U32 img_height
    ){
    assert(img->el.usage > 0);
    img_width = MAX_U32(img_width, 10);  // Just a precaution. empty buffers aren't supported by Margaret
    img_height = MAX_U32(img_height, 10);
    VecRefListNodeLucyImage_append(&cache->to_be_copied_to_device_next_cycle, img);
    img->el.staging_buffer = MargaretBufAllocator_alloc(cache->ve.staging_buffers, img_width * img_height * 1);
    img->el.img = MargaretImgAllocator_alloc(cache->ve.dev_local_images, img_width, img_height, VK_FORMAT_R8_UNORM,
        VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
    img->el.img_view = margaret_create_view_for_image(cache->ve.device, img->el.img.a.image,
        VK_FORMAT_R8_UNORM, VK_IMAGE_ASPECT_COLOR_BIT);
}

void LucyGlyphCache_add_glyphs(VecLucyGlyphCachingRequest requests_for_faces){
    if (requests_for_faces.len == 0)
        return;
    LucyGlyphCache* cache = requests_for_faces.buf[0].sized_face->value.p->p;
    for (size_t fi = 0; fi < requests_for_faces.len; fi++) {
        assert(cache == requests_for_faces.buf[fi].sized_face->value.p->p);
    }
    VkPhysicalDeviceProperties properties;
    vkGetPhysicalDeviceProperties(cache->ve.physical_device, &properties);
    U32 max_dim = properties.limits.maxImageDimension2D;
    check(max_dim >= 10);


    VecLucyPositionedStagingGlyph ready = VecLucyPositionedStagingGlyph_new();
    for (size_t fi = 0; fi < requests_for_faces.len; fi++) {
        LucyGlyphCachingRequest req = requests_for_faces.buf[fi];
        FT_Face ft_face = req.sized_face->value.p->ft_face;
        U32 font_height = req.sized_face->key;
        check(FT_Set_Pixel_Sizes(ft_face, 0, font_height) == 0);
        BufRBTree_MapU32ToLucyStoredGlyph* glyph_set = &req.sized_face->value.glyphs;
        /* Phase 1, where we add some elements to glyph_set, but we don't
         * know how many image we will have and we don't know where new glyphs will be placed */
        for (size_t ri = 0; ri < req.codepoint_ranges.len; ri++) {
            U32 range_start = req.codepoint_ranges.buf[ri].start;
            U32 range_end = range_start + req.codepoint_ranges.buf[ri].len;
            for (U32 codepoint = range_start; codepoint < range_end; codepoint++) {
                if (BufRBTree_MapU32ToLucyStoredGlyph_find(glyph_set, codepoint) != 0)
                    continue;
                FT_UInt glyph_index = FT_Get_Char_Index(ft_face, (FT_ULong)codepoint);
                check(FT_Load_Glyph(ft_face, glyph_index, 0) == 0);
                FT_GlyphSlot slot = ft_face->glyph;
                FT_Bitmap* bitmap = &slot->bitmap;
                TextureDataR8 my_bitmap = TextureDataR8_new(bitmap->width, bitmap->rows);
                check(bitmap->pixel_mode == FT_PIXEL_MODE_GRAY);
                if (slot->format != FT_GLYPH_FORMAT_BITMAP) {
                    check(FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL) == 0);
                }
                /* Here we dismiss very big glyphs. This guarantees that each glyph on it's own fits into VkImage */
                check(bitmap->width <= max_dim && bitmap->rows <= max_dim);
                assert(bitmap->rows == 0 || bitmap->width != 0 || bitmap->buffer != NULL);
                for (S64 y = 0; y < bitmap->rows; y++) {
                    for (S64 x = 0; x < bitmap->width; x++) {
                        *TextureDataR8_mat(&my_bitmap, x, y) = *(bitmap->buffer + y * bitmap->pitch + x * sizeof(U8));
                    }
                }
                BufRBTree_MapU32ToLucyStoredGlyph_insert(glyph_set, codepoint, (LucyStoredGlyph){
                    .w = bitmap->width, .h = bitmap->rows,
                    .advance_x = slot->advance.x >> 6,
                    .bearing = (ivec2){ slot->bitmap_left, -slot->bitmap_top },
                    /* x_on_atlas, y_on_atlas and img will be set later, when `ready` vector is ready */
                });
                VecLucyPositionedStagingGlyph_append(&ready, (LucyPositionedStagingGlyph){
                    .sized_face = &req.sized_face->value, .codepoint = codepoint, .bitmap = my_bitmap,
                    /* pos and img will be filled later by packing algorithm */
                });
            }
        }
        /* Phase 2. Here we determine (in ready vector) where each new glyph sits (atlas image + pos).
         * But we won't copy TextureDataR8 to staging buffer before everything is known */
        VecLucyPositionedStagingGlyph_sort(&ready);
        /* Variables, that have to be reset after each image overflow */
        U32 starting_x = 0;
        VecU32 landscape = VecU32_new_reserved(200);
        U32 img_width = 0, img_height = 0;
        ListNodeLucyImage* img = ListLucyImage_insert(&cache->images, (LucyImage){0});
        for (size_t j = 0; j < ready.len; j++) {
            LucyPositionedStagingGlyph* p_glyph;
            one_more_chance:
            p_glyph = &ready.buf[j];
            U64 new_width_required = p_glyph->bitmap.width + starting_x;
            if (new_width_required > max_dim) {
                /* Resetting row */
                starting_x = 0;
                goto one_more_chance;
            }
            for (U32 h = img_width; h < new_width_required; h++) {
                VecU32_append(&landscape, 0);
            }
            img_width = MAX_U64(img_width, new_width_required);
            U32 height_here = 0;
            for (size_t x = 0; x < p_glyph->bitmap.width; x++) {
                height_here = MAX_U32(height_here, *VecU32_at(&landscape, starting_x + x));
            }
            U64 new_height_required = height_here + p_glyph->bitmap.height;
            if (new_height_required > max_dim) {
                /* Resetting image */
                LucyGlyphCache_add_glyphs__close_img(cache, img, img_width, img_height);
                starting_x = 0;
                landscape.len = 0;
                img_width = 0;
                img_height = 0;
                img = ListLucyImage_insert(&cache->images, (LucyImage){0});
                goto one_more_chance;
            }
            /* Success */
            for (size_t x = 0; x < p_glyph->bitmap.width; x++) {
                *VecU32_mat(&landscape, starting_x + x) = new_height_required;
            }
            img_height = MAX_U64(img_height, new_height_required);
            p_glyph->img = img;
            p_glyph->pos = (uvec2){starting_x, height_here};
            img->el.usage++;  /* p_glyph uses it, that's a rock fact */
            BufRBTree_MapU32ToLucyStoredGlyph *glyphs = &p_glyph->sized_face->glyphs;
            U64 map_it = BufRBTree_MapU32ToLucyStoredGlyph_find(glyphs, p_glyph->codepoint);
            assert(map_it > 0 && map_it < glyphs->tree.len);
            LucyStoredGlyph* actual_glyph = &glyphs->el.buf[map_it - 1].value;
            actual_glyph->pos_on_atlas = (uvec2){starting_x, height_here};
            actual_glyph->img = img;
            starting_x += p_glyph->bitmap.width;
        }
        LucyGlyphCache_add_glyphs__close_img(cache, img, img_width, img_height);
        /* Phase 3. We have all the data. Now what?
         * Now we fill staging buffers with glyphs bitsets from `ready` vector */
        for (size_t j = 0; j < ready.len; j++) {
            LucyPositionedStagingGlyph* p_glyph = &ready.buf[j];
            U64 staging_width = p_glyph->img->el.img.width;
            U8* staging = (U8*)MargaretSubbuf_get_mapped(&p_glyph->img->el.staging_buffer);
            for (U64 y = 0; y < p_glyph->bitmap.height; y++) {
                U64 Y = y + p_glyph->pos.y;
                for (U64 x = 0; x < p_glyph->bitmap.width; x++) {
                    U64 X = x + p_glyph->pos.x;
                    staging[Y * staging_width + X] = *TextureDataR8_at(&p_glyph->bitmap, x, y);
                }
            }
        }
    }
    VecLucyPositionedStagingGlyph_drop(ready);
    VecLucyGlyphCachingRequest_drop(requests_for_faces);
}

/* This must not happen before all the LucyFaceFixedSizes are destroyed */
void LucyGlyphCache_drop(LucyGlyphCache self){
    assert(self.images.first == NULL);
    VecRefListNodeLucyImage_drop(self.to_be_freed_of_old_staging_next_cycle);
    VecRefListNodeLucyImage_drop(self.to_be_copied_to_device_next_cycle);
    VecRefListNodeLucyImage_drop(self.to_be_deleted);
}

void

void LucyGlyphCache_another_frame(LucyGlyphCache* self){
    self->changes_happened = false;
    for (size_t i = 0; i < self->to_be_freed_of_old_staging_next_cycle.len; i++) {
        LucyImage* img = &self->to_be_freed_of_old_staging_next_cycle.buf[i]->el;
        assert(img->staging_buffer.len != 0);
        MargaretBufAllocator_free(self->ve.staging_buffers, img->staging_buffer);
        img->staging_buffer.len = 0;
    }
    for (size_t i = 0; i < self->to_be_copied_to_device_next_cycle.len; i++) {
        ListNodeLucyImage* img_node = self->to_be_copied_to_device_next_cycle.buf[i];
        LucyImage* img = &img_node->el;
        assert(img->staging_buffer.len != 0);
        if (img->scheduled_for_deletion)
            continue;
        margaret_rec_cmd_copy_buffer_to_image_one_to_one_color_aspect(self->ve.transfer_cmd_buffer,
            &img->staging_buffer, &img->img, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
            VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_ACCESS_SHADER_READ_BIT);
        VecRefListNodeLucyImage_append(&self->to_be_freed_of_old_staging_next_cycle, img_node);
    }
    /* We technically could carry out each deletion request in O(1) and each img creation request in O(1),
     * but who cares, it's no problem going over the entire descriptor set when something get's added or deleted */
    for (size_t i = 0; i < self->to_be_deleted.len; i++) {
        ListNodeLucyImage* img_node = self->to_be_deleted.buf[i];
        LucyImage* img = &img_node->el;
        assert(img->scheduled_for_deletion);
        assert(img->usage == 0);
        if (img->staging_buffer.len != 0)
            MargaretBufAllocator_free(self->ve.staging_buffers, img->staging_buffer);
        MargaretImgAllocator_free(self->ve.dev_local_images, img->img.a);
        ListLucyImage_erase_by_it(&self->images, img_node);
    }
    if ((self->to_be_copied_to_device_next_cycle.len > 0) || (self->to_be_deleted.len > 0)) {
        U32 descriptor_i = 0;
        VecVkDescriptorImageInfo desc_elements = VecVkDescriptorImageInfo_new();
        for (ListNodeLucyImage* list_node = self->images.first; list_node; list_node = list_node->next) {
            if (descriptor_i == LUCY_MAX_DESCRIPTOR_COUNT) {
                abortf("Today you are out of luck\n");
            }
            LucyImage* img = &list_node->el;
            img->pos_in_desc_array = descriptor_i;
            VecVkDescriptorImageInfo_append(&desc_elements, (VkDescriptorImageInfo){
                .sampler = self->ve.nearest_sampler, .imageView = img->img_view,
                .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
            });
            descriptor_i++;
        }
        vkUpdateDescriptorSets(self->ve.device, 1, &(VkWriteDescriptorSet){
                .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
                .dstSet = self->descriptor_set, .dstBinding = 0, .dstArrayElement = 0,
                .descriptorCount = desc_elements.len,
                .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                .pImageInfo = desc_elements.buf
            }, 0, NULL);
        VecVkDescriptorImageInfo_drop(desc_elements);
        self->changes_happened = true;
    }
    self->to_be_freed_of_old_staging_next_cycle.len = 0;
    self->to_be_copied_to_device_next_cycle.len = 0;
    self->to_be_deleted.len = 0;
}

/* This function does not check font file for correctness, use only with trusted fonts */
LucyFace* LucyFace_new(FT_Library lib, LucyGlyphCache* cache, VecU8 path){
    VecU8_append(&path, 0); // Making it null-terminated
    FT_Face face;
    FT_Error ret = FT_New_Face(lib, (const char*)path.buf, 0, &face);
    check(ret == 0);
    VecU8_drop(path);
    LucyFace* hand = safe_malloc(sizeof(LucyFace));
    *hand = (LucyFace){.p = cache, .ft_face = face, .sizes = RBTree_MapU32ToLucyFaceFixedSize_new()};
    return hand;
}

RBTreeNodeLucyFaceFixedSize* LucyFace_of_size(LucyFace* self, U32 size){
    RBTreeNodeLucyFaceFixedSize* nahodka = RBTree_MapU32ToLucyFaceFixedSize_find(&self->sizes, size);
    if (nahodka)
        return nahodka;
    RBTree_MapU32ToLucyFaceFixedSize_insert(&self->sizes, size, (LucyFaceFixedSize){
        .p = self, .glyphs = BufRBTree_MapU32ToLucyStoredGlyph_new()
    });
    // todo: add a method to RBTree for proper node insertion. This is just pure crap
    return RBTree_MapU32ToLucyFaceFixedSize_find(&self->sizes, size);
}

#endif
