/* Based on Allen Webster's dwrite rasterizer example -
 * https://github.com/4th-dimention/examps */

extern "C"
{
#include "ttf.h"
#include "scratch.h"
#include "util.h"
#include "string.h"
#include "memory.h"
#include "arena.h"
#include "font.h"
}

#pragma warning(push, 0)
# include <dwrite.h>
# include <dwrite_3.h>
#pragma warning(pop)

#pragma comment(lib, "dwrite")
#pragma comment(lib, "gdi32")

/* TODO: Determine DPI accurately? */
#define DPI (96.0f)

#define CPPSTR(cstr_lit) { (sizeof((cstr_lit)) - 1), (u8 *)(cstr_lit) }

/* ========================== *
 * Global state
 * ========================== */

GLOBAL struct {
    /* FIXME: Do we need to wrap this in a mutex? */
    IDWriteFactory5 *factory;
} G = { 0 }, DEBUG_ALIAS(G, L_ttf_dwrite);

/* ========================== *
 * Decode font
 * ========================== */

INTERNAL i32 round_up(f32 x)
{
    i32 r = (i32)x;
    if ((f32)r < x) {
        r += 1;
    }
    return r;
}

/* Call this during font system startup */
struct ttf_startup_receipt ttf_startup(void)
{
    ASSERT(!G.factory);
    /* FIXME: I think IDWriteFactory5 only exists on later updates of windows
     * 10? Need to verify. Maybe should just use a custom loader. (We're only
     * using a factory5 since I think WriteInMemoryFileLoader wasn't
     * implemented until then) */
#if COMPILER_CLANG
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wlanguage-extension-token"  /* for __uuidof */
#endif
    HRESULT error = DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_SHARED,
        __uuidof(IDWriteFactory5),
        (IUnknown **)&G.factory
    );
#if COMPILER_CLANG
# pragma clang diagnostic pop
#endif
    if (error != S_OK) {
        sys_panic(STR("Error creating DWrite factory"));
    }

    return { 0 };
}

struct ttf_decode_result ttf_decode(struct arena *arena, struct buffer encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count)
{
    COLORREF bg_color = RGB_32(0,0,0);
    COLORREF fg_color = RGB_32(255,255,255);

    IDWriteFactory5 *factory = G.factory;

    /* TODO: handle errors */
    HRESULT error = 0;
    (UNUSED)error;

    /* File */
    IDWriteFontFile *font_file = NULL;
    {
        /* Create in memory loader */
        IDWriteInMemoryFontFileLoader *loader = NULL;
        error = factory->CreateInMemoryFontFileLoader(&loader);
        error = factory->RegisterFontFileLoader(loader);

        IDWriteFontSetBuilder1 *builder = NULL;
        error = factory->CreateFontSetBuilder(&builder);
        error = loader->CreateInMemoryFontFileReference(factory, encoded.data, (u32)encoded.size, NULL, &font_file);
        error = builder->AddFontFile(font_file);
    }

    /* Face */
    IDWriteFontFace *font_face = NULL;
    error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face);

    /* Render settings */
    IDWriteRenderingParams *default_rendering_params = NULL;
    error = factory->CreateRenderingParams(&default_rendering_params);
    IDWriteRenderingParams *rendering_params = NULL;
    FLOAT gamma = default_rendering_params->GetGamma();
    FLOAT enhanced_contrast = default_rendering_params->GetEnhancedContrast();
    FLOAT clear_type_level = default_rendering_params->GetClearTypeLevel();
    error = factory->CreateCustomRenderingParams(gamma,
                                                 enhanced_contrast,
                                                 clear_type_level,
                                                 DWRITE_PIXEL_GEOMETRY_FLAT,
                                                 DWRITE_RENDERING_MODE_DEFAULT,
                                                 &rendering_params);

    /* Interop */
    IDWriteGdiInterop *dwrite_gdi_interop = NULL;
    error = factory->GetGdiInterop(&dwrite_gdi_interop);

    /* Get Metrics */
    DWRITE_FONT_METRICS metrics = { 0 };
    font_face->GetMetrics(&metrics);

    f32 pixel_per_em = point_size * (DPI / 72.0f);
    f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm);

    i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit);
    i32 raster_target_h = raster_target_w;

    f32 raster_target_x = (f32)(raster_target_w / 2);
    f32 raster_target_y = raster_target_x;

    ASSERT((f32)((i32)raster_target_x) == raster_target_x);
    ASSERT((f32)((i32)raster_target_y) == raster_target_y);

    /* Glyph count */
    u16 glyph_count = font_face->GetGlyphCount();

    /* Render target */
    IDWriteBitmapRenderTarget *render_target = NULL;
    /* FIXME: errors when point_size too high */
    error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target);
    render_target->SetPixelsPerDip(1.0);

    /* Clear the render target */
    HDC dc = 0;
    {
        dc = render_target->GetMemoryDC();
        HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
        SetDCPenColor(dc, bg_color);
        SelectObject(dc, GetStockObject(DC_BRUSH));
        SetDCBrushColor(dc, bg_color);
        Rectangle(dc, 0, 0, raster_target_w, raster_target_h);
        SelectObject(dc, original);
    }

    /* Allocate font memory */
    struct font_glyph *glyphs = (struct font_glyph *)arena_push_array_zero(arena, struct font_glyph, glyph_count);

    /* Allocate (starting) atlas memory
     * NOTE: This is unecessary since atlas memory will grow anyway. Could
     * just start w/ atlas height 0.
     */
    u64 atlas_w = 1024;
    u64 atlas_h = 1;
    u32 *atlas_memory = arena_push_array_zero(arena, u32, atlas_w * atlas_h);

    /* Fill CPU side atlas & metric data */
    u32 out_offset_x = 0;
    u32 out_offset_y = 0;
    u32 row_height = 0;
    for (u16 i = 0; i < glyph_count; ++i) {
        /* Render glyph to target */
        DWRITE_GLYPH_RUN glyph_run = { 0 };
        glyph_run.fontFace = font_face;
        glyph_run.fontEmSize = pixel_per_em;
        glyph_run.glyphCount = 1;
        glyph_run.glyphIndices = &i;

        RECT bounding_box = { 0 };
        error = render_target->DrawGlyphRun(
            raster_target_x,
            raster_target_y,
            DWRITE_MEASURING_MODE_NATURAL,
            &glyph_run,
            rendering_params,
            fg_color,
            &bounding_box
        );

        if (bounding_box.left < 0
            || bounding_box.top < 0
            || bounding_box.right > raster_target_w
            || bounding_box.bottom > raster_target_h) {
            /* Skip */
            continue;
        }

        /* Compute glyph metrics */
        DWRITE_GLYPH_METRICS glyph_metrics = { 0 };
        error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, false);

        f32 off_x = (f32)bounding_box.left - raster_target_x;
        f32 off_y = (f32)bounding_box.top - raster_target_y;
        f32 advance = (f32)glyph_metrics.advanceWidth * pixel_per_design_unit;
        i32 tex_w = bounding_box.right - bounding_box.left;
        i32 tex_h = bounding_box.bottom - bounding_box.top;

        struct font_glyph *glyph = &glyphs[i];
        glyph->off_x = off_x;
        glyph->off_y = off_y;
        glyph->advance = round_up(advance);
        glyph->width = (f32)tex_w;
        glyph->height = (f32)tex_h;

        /* Get the bitmap */
        HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
        DIBSECTION dib = { 0 };
        GetObject(bitmap, sizeof(dib), &dib);

        /* Start new row if necessary */
        if ((out_offset_x + tex_w) >= atlas_w) {
            out_offset_y += row_height;
            out_offset_x = 0;
            row_height = 0;
        }

        /* Grow atlas height */
        if ((out_offset_y + tex_h) > atlas_h) {
            u64 diff = (out_offset_y + tex_h) - atlas_h;
            /* NOTE: This allocation must be contiguous with the initial atlas
             * allocation (IE: No non-atlas arena PUSHes)  */
            arena_push_array_zero(arena, u32, diff * atlas_w);
            atlas_h += diff;
        }

        /* Set bounding box metrics (now that we know atlas x & y) */
        glyph->atlas_rect = { 0 };
        glyph->atlas_rect.x = (f32)out_offset_x;
        glyph->atlas_rect.y = (f32)out_offset_y;
        glyph->atlas_rect.width = (f32)tex_w;
        glyph->atlas_rect.height = (f32)tex_h;

        /* Fill atlas */
        u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4;
        u32 *in_data = (u32 *)dib.dsBm.bmBits;
        u32 *out_data = atlas_memory;
        for (i32 y = 0; y < tex_h; ++y) {
            u64 out_y = out_offset_y + y;
            u64 in_y = (u64)bounding_box.top + y;
            for (i32 x = 0; x < tex_w; ++x) {
                u64 out_x = out_offset_x + x;
                u64 in_x = (u64)bounding_box.left + x;
                u32 *out_pixel = out_data + (out_x + (out_y * atlas_w));
                u32 *in_pixel = in_data + (in_x + (in_y * in_pitch));
                *out_pixel = RGBA_32(0xFF, 0xFF, 0xFF, *in_pixel & 0xFF);
            }
        }
        out_offset_x += tex_w;

        /* Grow row height */
        if ((u32)tex_h > row_height) {
            row_height = (u32)tex_h;
        }

        /* Clear the render target */
        {
            HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
            SetDCPenColor(dc, bg_color);
            SelectObject(dc, GetStockObject(DC_BRUSH));
            SetDCBrushColor(dc, bg_color);
            Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom);
            SelectObject(dc, original);
        }
    }

    /* Construct indices array */
    u16 *cache_indices = NULL;
    if (cache_codes_count > 0) {
        cache_indices = arena_push_array_zero(arena, u16, cache_codes_count);
        font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices);
    }

    /* Release */
    dwrite_gdi_interop->Release();
    rendering_params->Release();
    default_rendering_params->Release();
    // NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process
    // font_face->Release();
    font_file->Release();
    //loader->Release();
    factory->Release();

    /* Return */
    struct ttf_decode_result result = { 0 };
    result.glyphs = glyphs;
    result.glyphs_count = glyph_count;
    result.cache_indices = cache_indices;
    result.image_data.pixels = (u32 *)atlas_memory;
    result.image_data.width = (u32)atlas_w;
    result.image_data.height = (u32)atlas_h;
    return result;
}
