/* Maintain a data structure describing a monospaced bitmap font */

#include "hack.h"
#include "font.h"

static uint32 read_u32(const unsigned char *);
static void add_unicode_index(
            struct BitmapFont *font,
            uint32 ch,
            unsigned index);
static uint32 *uni_8to32(const char *);

struct BitmapFont *
load_font(const char *filename)
{
    FILE *fp;
    struct BitmapFont *font = NULL;
    unsigned char header[32];
    size_t size;
    uint32 magic;
    uint32 version;
    uint32 headersize;
    uint32 fontflags;
    uint32 length;
    uint32 charsize;
    uint32 height;
    uint32 width;
    uint32 bwidth, memsize;
    uint32 i;

    fp = fopen(filename, "rb");
    if (fp == NULL) goto error;

    /* Read the PSF header */
    size = fread(header, 1, sizeof(header), fp);
    if (size != sizeof(header)) goto error;

    /* Convert from little endian order */
    magic      = read_u32(header +  0);
    if (magic != 0x864AB572) goto error;
    version    = read_u32(header +  4);
    if (version != 0) goto error;
    headersize = read_u32(header +  8);
    if (headersize < sizeof(header)) goto error;
    fontflags  = read_u32(header + 12);
    length     = read_u32(header + 16);
    charsize   = read_u32(header + 20);
    height     = read_u32(header + 24);
    width      = read_u32(header + 28);

    /* Check that the declared character size can hold the declared width
       and height */
    bwidth = (width + 7) / 8;
    memsize = bwidth * height;
    if (memsize > charsize) goto error;

    /* Allocate a font structure */
    font = (struct BitmapFont *) alloc(sizeof(struct BitmapFont));
    memset(font, 0, sizeof(struct BitmapFont));

    /* Dimensions of the font */
    font->width = width;
    font->height = height;
    font->num_glyphs = length;

    /* The glyph array */
    font->glyphs = (unsigned char **) alloc(length * sizeof(unsigned char *));
    memset(font->glyphs, 0, length * sizeof(unsigned char *));

    /* Read the glyphs */
    fseek(fp, headersize, SEEK_SET);
    for (i = 0; i < length; ++i) {
        font->glyphs[i] = (unsigned char *) alloc(memsize);
        size = fread(font->glyphs[i], 1, memsize, fp);
        if (size != memsize) goto error;
        fseek(fp, charsize - memsize, SEEK_CUR);
    }

    if (fontflags & 0x01) {
        /* Read the Unicode table */
        char buf[128], buf2[128+1];
        unsigned bufsize, strsize;
        char *p;
        uint32 *codepoints;

        bufsize = 0;
        i = 0;
        while (i < length) {
            unsigned j;

            size = fread(buf + bufsize, 1, sizeof(buf) - bufsize, fp);
            if (ferror(fp)) goto error;
            bufsize += size;
            if (bufsize == 0) goto error; /* unexpected EOF */

            p = memchr(buf, 0xFF, bufsize);
            if (p != NULL) { /* end marker found */
                strsize = p - buf;
                memcpy(buf2, buf, strsize);
                buf2[strsize] = '\0';
                bufsize -= strsize + 1;
                memmove(buf, buf + strsize + 1, bufsize);
            } else { /* partial string */
                strsize = bufsize - 1;
                /* Roll back to character boundary in case of partial character */
                while (strsize != 0 && (buf[strsize] & 0xC0) == 0x80)
                    --strsize;
                if (strsize == 0) /* avoid infinite loop */
                    strsize = (bufsize < 4) ? bufsize : 4;
                memcpy(buf2, buf, strsize);
                buf2[strsize] = '\0';
                bufsize -= strsize;
                memmove(buf, buf + strsize, bufsize);
            }
            codepoints = uni_8to32(buf2);
            for (j = 0; codepoints[j] != 0; ++j) {
                add_unicode_index(font, codepoints[j], i);
            }
            free(codepoints);
            if (p != NULL)
                ++i;
        }
    } else {
        /* Fake a Unicode table, assuming that ASCII glyphs are in the
           expected places */
        for (i = 0x20; i <= 0x7E; ++i) {
            add_unicode_index(font, i, i);
        }
    }

    fclose(fp);
    return font;

error:
    if (fp != NULL) fclose(fp);
    free_font(font);
    return NULL;
}

void
free_font(struct BitmapFont *font)
{
    unsigned i, j;

    if (font == NULL) return;

    if (font->glyphs != NULL) {
        for (i = 0; i < font->num_glyphs; ++i)
            free(font->glyphs[i]);
        free(font->glyphs);
    }

    for (i = 0; i < SIZE(font->unicode); ++i) {
        if (font->unicode[i] == NULL) continue;
        for (j = 0; j < 256; ++j)
            free(font->unicode[i][j]);
        free(font->unicode[i]);
    }

    free(font);
}

const unsigned char *
get_font_glyph(struct BitmapFont *font, uint32 ch, boolean unicode)
{
    unsigned index;

    if (unicode) {
        index = 0;
        if (ch <= 0x10FFFF && (ch & 0xFFFFD800) != 0xD800) {
            unsigned i, j, k;

            i = (unsigned) (ch >> 16);
            j = (unsigned) ((ch >> 8) & 0xFF);
            k = (unsigned) (ch & 0xFF);
            if (font->unicode[i] != NULL
            &&  font->unicode[i][j] != NULL) {
                index = font->unicode[i][j][k];
            }
        }
    } else {
        index = ch;
    }

    if (index >= font->num_glyphs)
        index = 0;

    return font->glyphs[index];
}

static void
add_unicode_index(struct BitmapFont *font, uint32 ch, unsigned index)
{
    unsigned i, j, k;

    i = (unsigned) (ch >> 16);
    j = (unsigned) ((ch >> 8) & 0xFF);
    k = (unsigned) (ch & 0xFF);

    if (font->unicode[i] == NULL) {
        /* Create the second level node */
        font->unicode[i] = (unsigned **) alloc(256 * sizeof(unsigned *));
        memset(font->unicode[i], 0, 256 * sizeof(unsigned *));
    }
    if (font->unicode[i][j] == NULL) {
        /* Create the third level node */
        font->unicode[i][j] = (unsigned *) alloc(256 * sizeof(unsigned));
        memset(font->unicode[i][j], 0, 256 * sizeof(unsigned));
    }
    font->unicode[i][j][k] = index;
}

static uint32
read_u32(const unsigned char *buf)
{
    return ((uint32) buf[0] <<  0)
         | ((uint32) buf[1] <<  8)
         | ((uint32) buf[2] << 16)
         | ((uint32) buf[3] << 24);
}

static uint32 *
uni_8to32(const char *inp)
{
    size_t i, j;
    uint32 *out;

    /* Output string */
    out = (uint32 *) alloc((strlen(inp) + 1) * sizeof(out[0]));

    i = 0;
    j = 0;
    while (inp[i] != 0) {
        unsigned char byte = inp[i++];
        uint32 ch32;
        uint32 min = 0;
        unsigned count = 0;

        if (byte < 0x80) {
            ch32 = byte;
        } else if (byte < 0xC0) {
            ch32 = 0xFFFD;
        } else if (byte < 0xE0) {
            ch32 = byte & 0x1F;
            min = 0x80;
            count = 1;
        } else if (byte < 0xF0) {
            ch32 = byte & 0x0F;
            min = 0x800;
            count = 2;
        } else if (byte < 0xF5) {
            ch32 = byte & 0x07;
            min = 0x10000;
            count = 3;
        } else {
            ch32 = 0xFFFD;
        }

        for (; count != 0; --count) {
            byte = inp[i];
            if ((byte & 0xC0) != 0x80) {
                break;
            }
            ++i;
            ch32 = (ch32 << 6) | (byte & 0x3F);
        }
        if (count != 0 || ch32 < min || ((ch32 & 0xFFFFF800) == 0xD800)) {
            ch32 = 0xFFFD;
        }
        out[j++] = ch32;
    }

    out[j] = 0;
    return out;
}
