/*
 * fbfrender - FBF font cache and renderer
 *
 * Copyright (c) 2001, 2002 Fabrice Bellard.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qe.h"

#include "fbfrender.h"
#include "libfbf.h"

//#define CONFIG_FILE_FONTS

static UniFontData *first_font;
static UniFontData *fallback_font;

/**********************************************/
/* glyph cache handling */
#define HASH_SIZE      263
#define MAX_CACHE_SIZE (256 * 1024)

static GlyphCache *hash_table[HASH_SIZE];
static int cache_size = 0;
static GlyphCache first_cache_entry;

static void glyph_cache_init(void)
{
    first_cache_entry.next = &first_cache_entry;
    first_cache_entry.prev = &first_cache_entry;
}

static inline int glyph_hash(int index, int size, int style)
{
    unsigned int h;

    h = index + (style << 24) + (size << 16);
    return h % HASH_SIZE;
}

static GlyphCache *get_cached_glyph(QEFont *font, int index)
{
    GlyphCache *p;
    int h;

    h = glyph_hash(index, font->size, font->style);
    p = hash_table[h];
    while (p != NULL) {
        if (p->index == index &&
            p->size == font->size &&
            p->style == font->style)
            goto found;
        p = p->hash_next;
    }
    return NULL;
 found:
    /* suppress in linked list */
    p->next->prev = p->prev;
    p->prev->next = p->next;
    /* move to front */
    first_cache_entry.next->prev = p;
    p->next = first_cache_entry.next;
    first_cache_entry.next = p;
    p->prev = &first_cache_entry;

    return p;
}

static GlyphCache *add_cached_glyph(QEFont *font, int index, int data_size)
{
    GlyphCache **pp, *p;
    int h;

    cache_size += sizeof(GlyphCache) + data_size + 4;

    while (cache_size > MAX_CACHE_SIZE) {
        /* suppress oldest entry */
        p = first_cache_entry.prev;
        cache_size -= sizeof(GlyphCache) + p->data_size + 4;
        p->next->prev = p->prev;
        p->prev->next = p->next;
        /* suppress from hash table (should use double linked list ?) */
        h = glyph_hash(p->index, p->size, p->style);
        pp = &hash_table[h];
        while (*pp != p) pp = &(*pp)->hash_next;
        *pp = p->hash_next;

        qe_free(&p);
    }

    p = qe_malloc_hack(GlyphCache, data_size);
    if (!p)
        return NULL;

    h = glyph_hash(index, font->size, font->style);
    pp = &hash_table[h];
    p->hash_next = *pp;
    *pp = p;
    p->index = index;
    p->size = font->size;
    p->style = font->style;
    p->data_size = data_size;
    p->private = NULL;
    first_cache_entry.next->prev = p;
    p->next = first_cache_entry.next;
    first_cache_entry.next = p;
    p->prev = &first_cache_entry;

    return p;
}

static GlyphCache *fbf_decode_glyph1(QEFont *font, int code)
{
    UniFontData *uf = font->priv_data;
    int glyph_index, size, src_width, src_height;
    GlyphCache *glyph_cache;
    GlyphEntry *fbf_glyph_entry;

    glyph_index = fbf_unicode_to_glyph(uf, code);
    if (glyph_index < 0)
        return NULL;

    /* decompress a glyph if needed */
    if (fbf_decode_glyph(uf,
                         &fbf_glyph_entry,
                         glyph_index) < 0)
        return NULL;
    src_width = fbf_glyph_entry->w;
    src_height = fbf_glyph_entry->h;

    size = src_width * src_height;
    glyph_cache = add_cached_glyph(font, code, size);
    if (!glyph_cache)
        return NULL;
    /* convert to bitmap */
    {
        int x, y, bit, pitch;
        unsigned char *bitmap;

        bitmap = fbf_glyph_entry->bitmap;
        pitch = (src_width + 7) >> 3;
        for (y = 0; y < src_height; y++) {
            for (x = 0; x < src_width; x++) {
                bit = (bitmap[pitch * y + (x >> 3)] >>
                       (7 - (x & 7))) & 1;
                glyph_cache->data[src_width * y + x] = -bit;
            }
        }
    }

    glyph_cache->w = src_width;
    glyph_cache->h = src_height;
    glyph_cache->x = fbf_glyph_entry->x;
    glyph_cache->y = fbf_glyph_entry->y;
    glyph_cache->xincr = fbf_glyph_entry->xincr;
    return glyph_cache;
}


/*
 * main function : get one glyph. Return -1 if no glyph found, 0 if OK.
 */
GlyphCache *decode_cached_glyph(QEditScreen *s, QEFont *font, int code)
{
    GlyphCache *g;
    QEFont *font1;

    g = get_cached_glyph(font, code);
    if (!g) {
        g = fbf_decode_glyph1(font, code);
        if (!g) {
            /* try with fallback font */
            font1 = select_font(s, font->style | (1 << QE_FONT_FALLBACK_SHIFT),
                                font->size);
            g = fbf_decode_glyph1(font1, code);
            release_font(s, font1);
            if (!g)
                return NULL;
            /* indicates that it is a fallback glyph so that the
               correct font height can be computed */
            g->is_fallback = 1;
        } else {
            g->is_fallback = 0;
        }
    }
    return g;
}

void fbf_text_metrics(QEditScreen *s, QEFont *font,
                      QECharMetrics *metrics,
                      const char32_t *str, int len)
{
    GlyphCache *g;
    int i, x;
    char32_t cc;

    metrics->font_ascent = font->ascent;
    metrics->font_descent = font->descent;
    x = 0;
    for (i = 0;i < len; i++) {
        cc = str[i];
        g = decode_cached_glyph(s, font, cc);
        if (g) {
            x += g->xincr;
            if (g->is_fallback) {
                /* if alternate font used, modify metrics */
                metrics->font_ascent = max_int(metrics->font_ascent,
                                               fallback_font->ascent);
                metrics->font_descent = max_int(metrics->font_descent,
                                                fallback_font->descent);
            }
        }
    }
    metrics->width = x;
}

#define MAX_MATCHES 32

QEFont *fbf_open_font(qe__unused__ QEditScreen *s, int style, int size)
{
    QEFont *font;
    UniFontData *uf, *uf_found;
    int d, dmin;
    UniFontData *fonts[MAX_MATCHES];
    int nb_fonts, i, family_type;

    font = qe_mallocz(QEFont);
    if (!font)
        return NULL;

    if ((style & QE_FONT_FALLBACK_MASK) != 0) {
        uf_found = fallback_font;
    } else {
        /* convert to unifont family types */
        family_type = (style & QE_FONT_FAMILY_MASK);
        if (family_type) family_type--;

        /* first match style */
        nb_fonts = 0;
        for (uf = first_font; uf != NULL; uf = uf->next_font) {
            if (uf->family_type == family_type) {
                fonts[nb_fonts++] = uf;
            }
        }
        if (!nb_fonts) {
            uf_found = first_font;
        } else {
            /* select closest size */
            uf_found = NULL;
            dmin = INT_MAX;
            for (i = 0;i < nb_fonts;i++) {
                uf = fonts[i];
                d = abs(uf->pt_size - size);
                if (d < dmin) {
                    uf_found = uf;
                    dmin = d;
                }
            }
        }
    }
    font->priv_data = uf_found;
    font->ascent = uf_found->ascent;
    font->descent = uf_found->descent;
    return font;
}

void fbf_close_font(qe__unused__ QEditScreen *s, QEFont **fontp)
{
    qe_free(fontp);
}

static void *my_malloc(qe__unused__ void *opaque, int size)
{
    return qe_malloc_bytes(size);
}

static void my_free(qe__unused__ void *opaque, void *ptr)
{
    qe_free(&ptr);
}


#ifdef CONFIG_FILE_FONTS

/* disk font opening */

int my_fbf_seek(void *infile, long pos)
{
    return fseek((FILE *)infile, pos, SEEK_SET);
}

int my_fbf_read(void *infile, unsigned char *buf, int len)
{
    return fread(buf, 1, len, (FILE *)infile);
}

int my_fbf_getc(void *infile)
{
    return fgetc((FILE *)infile);
}

static int fbf_load_font_file(const char *filename)
{
    UniFontData *uf;
    FILE *f;

    printf("loading %s\n", filename);

    f = fopen(filename, "r");
    if (!f)
        return -1;

    uf = qe_mallocz(UniFontData);
    if (!uf) {
        fclose(f);
        return -1;
    }

    /* init memory */
    uf->mem_opaque = NULL;
    uf->fbf_malloc = my_malloc;
    uf->fbf_free = my_free;

    /* init file I/O */
    uf->infile = f;
    uf->fbf_seek = my_fbf_seek;
    uf->fbf_read = my_fbf_read;
    uf->fbf_getc = my_fbf_getc;

    if (fbf_load_font(uf) < 0) {
        qe_free(&uf);
        fclose(f);
        return -1;
    }

    /* add font */
    uf->next_font = first_font;
    first_font = uf;
    return 0;
}

int fbf_render_init(const char *font_path)
{
    FindFileState *ffs;
    char filename[MAX_FILENAME_SIZE];

    glyph_cache_init();
    first_font = NULL;

    ffs = find_file_open(font_path, "*.fbf", FF_PATH | FF_NODIR);
    if (!ffs)
        return -1;

    for (;;) {
        if (find_file_next(ffs, filename, sizeof(filename)))
            break;
        if (fbf_load_font_file(filename) < 0) {
            fprintf(stderr, "Could not load font '%s'\n", filename);
        }
    }
    return 0;
}

void fbf_render_cleanup(void)
{
    UniFontData *uf, *uf1;

    for (uf = first_font; uf != NULL; uf = uf1) {
        uf1 = uf->next_font;
        /* close font data structures */
        fbf_free_font(uf);
        /* close font file */
        fclose(uf->infile);
    }
    first_font = NULL;
}

#else

typedef struct MemoryFile {
    const unsigned char *base;
    int offset;
    int size;
} MemoryFile;

static int my_fbf_seek(void *infile, long pos)
{
    MemoryFile *f = infile;
    f->offset = pos;
    return pos;
}

static int my_fbf_read(void *infile, unsigned char *buf, int len)
{
    MemoryFile *f = infile;
    int len1;
    len1 = f->size - f->offset;
    if (len > len1)
        len = len1;
    memcpy(buf, f->base + f->offset, len);
    f->offset += len;
    return len;
}

static int my_fbf_getc(void *infile)
{
    MemoryFile *f = infile;
    if (f->offset < f->size)
        return f->base[f->offset++];
    else
        return EOF;
}

static int fbf_load_font_memory(const unsigned char *data,
                                int data_size)
{
    UniFontData *uf;
    MemoryFile *f;

    f = qe_mallocz(MemoryFile);
    if (!f)
        return -1;
    f->base = data;
    f->size = data_size;

    uf = qe_mallocz(UniFontData);
    if (!uf) {
        qe_free(&f);
        return -1;
    }

    /* init memory */
    uf->mem_opaque = NULL;
    uf->fbf_malloc = my_malloc;
    uf->fbf_free = my_free;

    /* init file I/O */
    uf->infile = f;
    uf->fbf_seek = my_fbf_seek;
    uf->fbf_read = my_fbf_read;
    uf->fbf_getc = my_fbf_getc;

    if (fbf_load_font(uf) < 0) {
        qe_free(&uf);
        qe_free(&f);
        return -1;
    }

    /* add font */
    uf->next_font = first_font;
    first_font = uf;

    /* we consider that unifont is the fallback font */
    /* XXX: use generic system as in x11.c */
    if (!strcasecmp(uf->family_name, "unifont"))
        fallback_font = uf;
    return 0;
}

int fbf_render_init(qe__unused__ const char *font_path)
{
    const struct fbf_font *pp;

    glyph_cache_init();
    first_font = NULL;
    for (pp = fbf_fonts; pp->data != NULL; pp++) {
        fbf_load_font_memory(pp->data, pp->size);
    }
    if (!fallback_font)
        fallback_font = first_font;
    return 0;
}

void fbf_render_cleanup(void)
{
    while (first_font) {
        UniFontData *uf = first_font;
        first_font = uf->next_font;
        /* close font data structures */
        fbf_free_font(uf);
        /* close font file */
        qe_free(&uf->infile);
        qe_free(&uf);
    }
}

#endif
