// Generate embedded 4-bit-alpha font tables from FreeType's native monochrome renderer.
//
// Build/run example:
//   cc scripts/generate-fonts-ftmono.c $(freetype-config --cflags --libs) -o /tmp/kinda-fontgen-ftmono
//   /tmp/kinda-fontgen-ftmono /System/Library/Fonts/Supplemental/Verdana.ttf src/ui/generated_fonts.rs
//
// This path is intended for 1-bit e-paper output. It preserves TrueType mono
// hinting instead of thresholding an antialiased outline after rasterization.

#include <ft2build.h>
#include FT_FREETYPE_H

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct FontSize {
    const char *name;
    int pixels;
};

struct Glyph {
    uint32_t codepoint;
    uint8_t width;
    uint8_t height;
    uint32_t bitmap_offset;
};

struct Buffer {
    uint8_t *data;
    size_t len;
    size_t cap;
};

static const struct FontSize SIZES[] = {
    {"QUATTRO_SMALL", 24},
    {"QUATTRO_BODY", 30},
    {"QUATTRO_READER", 34},
    {"QUATTRO_TITLE", 38},
};

static const uint32_t EXTRA_CODEPOINTS[] = {
    0x00A0,
    0x2018,
    0x2019,
    0x201C,
    0x201D,
    0x2013,
    0x2014,
    0x2026,
};

static int div_ceil_int(int value, int divisor) {
    return (value + divisor - 1) / divisor;
}

static int round_26_6(FT_Pos value) {
    if (value >= 0) {
        return (int)((value + 32) >> 6);
    }

    return -(int)(((-value) + 32) >> 6);
}

static int ceil_26_6(FT_Pos value) {
    if (value >= 0) {
        return (int)((value + 63) >> 6);
    }

    return -(int)((-value) >> 6);
}

static void buffer_push(struct Buffer *buffer, uint8_t value) {
    if (buffer->len == buffer->cap) {
        size_t next_cap = buffer->cap == 0 ? 4096 : buffer->cap * 2;
        uint8_t *next = realloc(buffer->data, next_cap);
        if (next == NULL) {
            fprintf(stderr, "out of memory\n");
            exit(2);
        }
        buffer->data = next;
        buffer->cap = next_cap;
    }

    buffer->data[buffer->len++] = value;
}

static int mono_bitmap_on(const FT_Bitmap *bitmap, unsigned int x, unsigned int y) {
    int pitch = bitmap->pitch;
    const uint8_t *row;
    if (pitch < 0) {
        row = bitmap->buffer + (bitmap->rows - 1 - y) * (unsigned int)(-pitch);
    } else {
        row = bitmap->buffer + y * (unsigned int)pitch;
    }

    return (row[x / 8] & (0x80 >> (x & 7))) != 0;
}

static void set_alpha4(uint8_t *cell, int width, int x, int y, uint8_t alpha) {
    int pixel = y * width + x;
    int byte = pixel / 2;
    if ((pixel & 1) == 0) {
        cell[byte] = (uint8_t)((cell[byte] & 0x0F) | (alpha << 4));
    } else {
        cell[byte] = (uint8_t)((cell[byte] & 0xF0) | alpha);
    }
}

static int load_codepoints(uint32_t *codepoints, int max_codepoints) {
    int count = 0;
    for (uint32_t codepoint = 32; codepoint < 127; codepoint++) {
        if (count >= max_codepoints) {
            return count;
        }
        codepoints[count++] = codepoint;
    }

    for (size_t i = 0; i < sizeof(EXTRA_CODEPOINTS) / sizeof(EXTRA_CODEPOINTS[0]); i++) {
        if (count >= max_codepoints) {
            return count;
        }
        codepoints[count++] = EXTRA_CODEPOINTS[i];
    }

    return count;
}

static void emit_bytes(FILE *out, const struct Buffer *bitmap) {
    for (size_t i = 0; i < bitmap->len; i++) {
        if (i % 16 == 0) {
            fputs("    ", out);
        }
        fprintf(out, "0x%02x,", bitmap->data[i]);
        if (i % 16 == 15 || i + 1 == bitmap->len) {
            fputc('\n', out);
        } else {
            fputc(' ', out);
        }
    }
}

static void emit_font(FILE *out, FT_Face face, const char *name, int pixels, const uint32_t *codepoints, int codepoint_count) {
    if (FT_Set_Pixel_Sizes(face, 0, (FT_UInt)pixels) != 0) {
        fprintf(stderr, "could not set pixel size %d\n", pixels);
        exit(3);
    }

    int cell_height = ceil_26_6(face->size->metrics.height);
    int baseline = ceil_26_6(face->size->metrics.ascender);
    if (cell_height <= 0) {
        cell_height = pixels;
    }

    struct Glyph *glyphs = calloc((size_t)codepoint_count, sizeof(struct Glyph));
    if (glyphs == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(2);
    }

    struct Buffer bitmap = {0};
    int glyph_count = 0;

    for (int i = 0; i < codepoint_count; i++) {
        uint32_t codepoint = codepoints[i];
        FT_UInt glyph_index = FT_Get_Char_Index(face, codepoint);
        if (glyph_index == 0) {
            continue;
        }

        if (FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER | FT_LOAD_TARGET_MONO) != 0) {
            continue;
        }

        FT_GlyphSlot slot = face->glyph;
        FT_Bitmap *source = &slot->bitmap;
        int advance = round_26_6(slot->advance.x);
        int left = slot->bitmap_left;
        int top = slot->bitmap_top;
        int right = left + (int)source->width;
        int cell_width = advance;
        if (right > cell_width) {
            cell_width = right;
        }
        if (cell_width <= 0) {
            cell_width = (int)source->width;
        }
        if (cell_width <= 0) {
            cell_width = 1;
        }
        if (cell_width > 255 || cell_height > 255) {
            fprintf(stderr, "glyph U+%04X too large at %dpx\n", codepoint, pixels);
            exit(4);
        }

        int cell_len = div_ceil_int(cell_width * cell_height, 2);
        uint8_t *cell = calloc((size_t)cell_len, 1);
        if (cell == NULL) {
            fprintf(stderr, "out of memory\n");
            exit(2);
        }

        int x_origin = left < 0 ? 0 : left;
        int y_origin = baseline - top;
        for (unsigned int y = 0; y < source->rows; y++) {
            int target_y = y_origin + (int)y;
            if (target_y < 0 || target_y >= cell_height) {
                continue;
            }
            for (unsigned int x = 0; x < source->width; x++) {
                int target_x = x_origin + (int)x;
                if (target_x < 0 || target_x >= cell_width) {
                    continue;
                }
                if (mono_bitmap_on(source, x, y)) {
                    set_alpha4(cell, cell_width, target_x, target_y, 15);
                }
            }
        }

        uint32_t offset = (uint32_t)bitmap.len;
        for (int j = 0; j < cell_len; j++) {
            buffer_push(&bitmap, cell[j]);
        }
        free(cell);

        glyphs[glyph_count++] = (struct Glyph){
            .codepoint = codepoint,
            .width = (uint8_t)cell_width,
            .height = (uint8_t)cell_height,
            .bitmap_offset = offset,
        };
    }

    fprintf(out, "// %d px; cell height %d px; %d glyphs; %zu mono alpha bytes.\n", pixels, cell_height, glyph_count, bitmap.len);
    fprintf(out, "pub(crate) static %s: BitmapFont = BitmapFont {\n", name);
    fprintf(out, "    cell_height: %d,\n", cell_height);
    fprintf(out, "    glyphs: &%s_GLYPHS,\n", name);
    fprintf(out, "    bitmap: &%s_BITMAP,\n", name);
    fprintf(out, "};\n\n");

    fprintf(out, "#[rustfmt::skip]\n");
    fprintf(out, "static %s_GLYPHS: [Glyph; %d] = [\n", name, glyph_count);
    for (int i = 0; i < glyph_count; i++) {
        fprintf(out, "    Glyph { codepoint: 0x%04x, width: %u, height: %u, bitmap_offset: %u },\n",
                glyphs[i].codepoint, glyphs[i].width, glyphs[i].height, glyphs[i].bitmap_offset);
    }
    fprintf(out, "];\n\n");

    fprintf(out, "#[rustfmt::skip]\n");
    fprintf(out, "static %s_BITMAP: [u8; %zu] = [\n", name, bitmap.len);
    emit_bytes(out, &bitmap);
    fprintf(out, "];\n\n");

    free(glyphs);
    free(bitmap.data);
}

int main(int argc, char **argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s FONT.ttf OUTPUT.rs\n", argv[0]);
        return 2;
    }

    FT_Library library;
    FT_Face face;
    if (FT_Init_FreeType(&library) != 0) {
        fprintf(stderr, "could not init freetype\n");
        return 3;
    }
    if (FT_New_Face(library, argv[1], 0, &face) != 0) {
        fprintf(stderr, "could not open font %s\n", argv[1]);
        return 3;
    }

    uint32_t codepoints[128];
    int codepoint_count = load_codepoints(codepoints, 128);

    FILE *out = fopen(argv[2], "w");
    if (out == NULL) {
        perror("fopen");
        return 3;
    }

    fprintf(out, "// @generated by scripts/generate-fonts-ftmono.c. Do not edit by hand.\n");
    fprintf(out, "// Source font: %s\n", argv[1]);
    fprintf(out, "// Rasterizer: FreeType mono hinting\n\n");
    fprintf(out, "use crate::ui::typography::{BitmapFont, Glyph};\n\n");

    for (size_t i = 0; i < sizeof(SIZES) / sizeof(SIZES[0]); i++) {
        emit_font(out, face, SIZES[i].name, SIZES[i].pixels, codepoints, codepoint_count);
    }

    fclose(out);
    FT_Done_Face(face);
    FT_Done_FreeType(library);
    return 0;
}
