/* a rendering.c mixin */
#ifndef TEXT_H
#define TEXT_H


#include "../util.h"
#include "townengine/config.h"
#include "townengine/context.h"

#include <glad/glad.h>
#include <stb_truetype.h>


#define ASCII_START 32
#define ASCII_END 128
#define NUM_DISPLAY_ASCII ((ASCII_END - ASCII_START) + 1)


struct font_data {
    stbtt_packedchar char_data[NUM_DISPLAY_ASCII];
    stbtt_fontinfo info;

    const char *file_path;
    unsigned char *file_bytes;
    size_t file_bytes_len;

    GLuint texture;

    int height_px;
    float scale_factor;
    int ascent;
    int descent;
    int line_gap;
};


static struct font_data *text_load_font_data(const char *path, int height_px) {
    struct font_data *font_data = ccalloc(1, sizeof *font_data);
    font_data->file_path = path;
    font_data->height_px = height_px;

    unsigned char* bitmap = ccalloc(TEXT_FONT_TEXTURE_SIZE * TEXT_FONT_TEXTURE_SIZE, 1);

    {
        unsigned char *buf = NULL; 
        int64_t buf_len = file_to_bytes(path, &buf);
        stbtt_InitFont(&font_data->info, buf, stbtt_GetFontOffsetForIndex(buf, 0));
        
        /* might as well get these now, for later */
        font_data->file_bytes = buf;
        font_data->file_bytes_len = buf_len;
        font_data->scale_factor = stbtt_ScaleForPixelHeight(&font_data->info, (float)height_px);
        stbtt_GetFontVMetrics(
            &font_data->info,
            &font_data->ascent,
            &font_data->descent,
            &font_data->line_gap
        );
        font_data->ascent = (int)((float)font_data->ascent * font_data->scale_factor);
        font_data->descent = (int)((float)font_data->descent * font_data->scale_factor);
        font_data->line_gap = (int)((float)font_data->line_gap * font_data->scale_factor);

        stbtt_pack_context pctx;
        stbtt_PackBegin(&pctx, bitmap, TEXT_FONT_TEXTURE_SIZE, TEXT_FONT_TEXTURE_SIZE, 0, 1, NULL);
        stbtt_PackSetOversampling(&pctx, TEXT_FONT_OVERSAMPLING, TEXT_FONT_OVERSAMPLING);
        stbtt_PackFontRange(&pctx, buf, 0, (float)height_px, ASCII_START, NUM_DISPLAY_ASCII, font_data->char_data);
        stbtt_PackEnd(&pctx);
    }

    glGenTextures(1, &font_data->texture);
    glBindTexture(GL_TEXTURE_2D, font_data->texture);
    glTexImage2D(
        GL_TEXTURE_2D,
        0,
        GL_ALPHA,
        TEXT_FONT_TEXTURE_SIZE,
        TEXT_FONT_TEXTURE_SIZE,
        0,
        GL_ALPHA,
        GL_UNSIGNED_BYTE,
        bitmap
    );
    free(bitmap);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, TEXT_FONT_FILTERING);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, TEXT_FONT_FILTERING);
    glBindTexture(GL_TEXTURE_2D, 0);


    return font_data;
}


static void text_destroy_font_data(struct font_data *font_data) {
    free(font_data->file_bytes);
    glDeleteTextures(1, &font_data->texture);
    free(font_data);
}


static void text_draw_with(struct font_data* font_data, char* text, t_fvec2 position, t_color color) {
    glBindTexture(GL_TEXTURE_2D, font_data->texture);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_FALSE);
    glDisable(GL_ALPHA_TEST);

    glColor4ub(color.r, color.g, color.b, color.a);

    glBegin(GL_QUADS);
    for (const char *p = text; *p != '\0'; ++p) {
        const char c = *p;

        /* outside the range of what we want to display */
        //if (c < ASCII_START || c > ASCII_END)
        if (c < ASCII_START)
            continue;

        /* stb_truetype.h conveniently provides everything we need to draw here! */
        stbtt_aligned_quad quad;
        stbtt_GetPackedQuad(
            font_data->char_data,
            TEXT_FONT_TEXTURE_SIZE,
            TEXT_FONT_TEXTURE_SIZE,
            c - ASCII_START,
            &position.x,
            &position.y,
            &quad,
            true
        );

        /* have to do this so the "origin" is at the top left */
        /* maybe there's a better way, or maybe this isn't a good idea... */
        /* who knows */
        quad.y0 += (float)font_data->ascent;
        quad.y1 += (float)font_data->ascent;

        /* TODO: you know... */
        glTexCoord2f(quad.s0, quad.t0);
        glVertex2f(quad.x0, quad.y0);
        glTexCoord2f(quad.s1, quad.t0);
        glVertex2f(quad.x1, quad.y0);
        glTexCoord2f(quad.s1, quad.t1);
        glVertex2f(quad.x1, quad.y1);
        glTexCoord2f(quad.s0, quad.t1);
        glVertex2f(quad.x0, quad.y1);
    }
    glEnd();

    glColor4ub(255, 255, 255, 255);
    glBindTexture(GL_TEXTURE_2D, 0);
}


static void ensure_font_cache(const char *font_path, int height_px) {
    /* HACK: stupid, bad, don't do this */
    bool is_cached = false;
    for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
        struct font_data *font_data = ctx.text_cache.data[i];
        if ((strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) {
            is_cached = true;
            break;
        }
    }
    if (!is_cached) {
        struct font_data *new_font_data = text_load_font_data(font_path, height_px);
        arrput(ctx.text_cache.data, new_font_data);
    }
}


static struct font_data *get_font_data(const char *font_path, int height_px) {
    struct font_data *font_data = NULL;
    for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
        font_data = ctx.text_cache.data[i];
        if ((strcmp(font_path, font_data->file_path) == 0) && height_px == font_data->height_px) {
            break;
        }
    }
    return font_data;
}


static void render_text(const struct text_primitive *text) {
    struct font_data *font_data = get_font_data(text->font, text->height_px);
    text_draw_with(font_data, text->text, text->position, text->color);
}


void text_cache_init(struct text_cache *cache) {
    arrsetlen(cache->data, 0);
}


void text_cache_deinit(struct text_cache *cache) {
    for (size_t i = 0; i < arrlenu(ctx.text_cache.data); ++i) {
        text_destroy_font_data(ctx.text_cache.data[i]);
    }

    arrfree(cache->data);
}


void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path) {
    ensure_font_cache(font_path, height_px);

    /* the string might not be around by the time it's used, so copy it */
    /* TODO: arena */
    /* NOTE: can we trust strlen? */
    char *dup_string = cmalloc(strlen(string) + 1);
    strcpy(dup_string, string);

    struct text_primitive text = {
        .color = color,
        .position = position,
        .text = dup_string,
        .font = font_path,
        .height_px = height_px,
    };

    struct primitive_2d primitive = {
        .type = PRIMITIVE_2D_TEXT,
        .text = text,
    };

    arrput(ctx.render_queue_2d, primitive);
}


int get_text_width(char *string, int height_px, const char *font_path) {
    ensure_font_cache(font_path, height_px);
    struct font_data *font_data = get_font_data(font_path, height_px);

    int length = 0;
    for (const char *p = string; *p != '\0'; ++p) {
        const char c = *p;
        int advance_width = 0;
        int left_side_bearing = 0;
        stbtt_GetCodepointHMetrics(&font_data->info, (int)c, &advance_width, &left_side_bearing);
        length += advance_width;
    }
    
    return (int)((float)length * font_data->scale_factor);
}


#endif
