#include "common.h"

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H

#include "file.h"
#include "font.h"

extern FT_Library ft_lib;

static int      font_cache_usage = 0;
static int      font_cache = 0;
static char   **fpath = NULL;
static int      fpath_num = 0;
static Imlib_Object_List *fonts = NULL;

static ImlibFont *__imlib_font_load(const char *name, int faceidx, int size);
static int      font_modify_cache_cb(Imlib_Hash * hash, const char *key,
                                     void *data, void *fdata);
static int      font_flush_free_glyph_cb(Imlib_Hash * hash, const char *key,
                                         void *data, void *fdata);

/* FIXME now! listdir() from evas_object_text.c */

/* separate fontname and size, find font file, start __imlib_font_load() then */
ImlibFont      *
__imlib_font_load_joined(const char *fontname)
{
    int             j, k, size, faceidx, namelen;
    char           *name, *file = NULL, *tmp;
    ImlibFont      *fn;

    /* split font name (in format name[:faceidx]/size) */
    for (j = strlen(fontname) - 1; (j >= 0) && (fontname[j] != '/'); j--)
        ;

    /* no "/" in font after the first char */
    if (j <= 0)
        return NULL;

    /* get size */
    size = atoi(&(fontname[j + 1]));

    /* split font faceidx index (in format name[:faceidx]/size) */
    faceidx = 0;
    for (k = j - 1; k > 0; k--)
    {
        if (fontname[k] >= '0' && fontname[k] <= '9')
            continue;
        if (fontname[k] != ':')
            break;
        faceidx = atoi(&(fontname[k + 1]));
        if (faceidx < 0)
            faceidx = 0;
        j = k;
        break;
    }

    namelen = j;
    /* split name in front off */
    name = malloc(namelen + 1);
    memcpy(name, fontname, namelen);
    name[namelen] = 0;

    /* find file if it exists */
    tmp = malloc(namelen + 4 + 1);
    if (!tmp)
        goto done;

    sprintf(tmp, "%s.ttf", name);
    if (__imlib_FileIsFile(tmp))
        file = strdup(tmp);
    else
    {
        sprintf(tmp, "%s.TTF", name);
        if (__imlib_FileIsFile(tmp))
            file = strdup(tmp);
        else
        {
            sprintf(tmp, "%s", name);
            if (__imlib_FileIsFile(tmp))
                file = strdup(tmp);
        }
    }
    free(tmp);

    if (!file)
    {
        for (j = 0; (j < fpath_num) && (!file); j++)
        {
            tmp = malloc(strlen(fpath[j]) + 1 + namelen + 4 + 1);
            if (!tmp)
                goto done;

            sprintf(tmp, "%s/%s.ttf", fpath[j], name);
            if (__imlib_FileIsFile(tmp))
                file = strdup(tmp);
            else
            {
                sprintf(tmp, "%s/%s.TTF", fpath[j], name);
                if (__imlib_FileIsFile(tmp))
                    file = strdup(tmp);
                else
                {
                    sprintf(tmp, "%s/%s", fpath[j], name);
                    if (__imlib_FileIsFile(tmp))
                        file = strdup(tmp);
                }
            }
            free(tmp);
        }
    }

  done:
    free(name);

    /* didn't find a file? abort */
    if (!file)
        return NULL;

    fn = __imlib_font_load(file, faceidx, size);
    free(file);
    return fn;
}

static ImlibFont *
__imlib_font_load(const char *name, int faceidx, int size)
{
    int             error;
    ImlibFont      *fn;
    char           *file;

    fn = __imlib_font_find(name, size);
    if (fn)
        return fn;

    __imlib_font_init();

    fn = malloc(sizeof(ImlibFont));
    file = (char *)name;

    error = FT_New_Face(ft_lib, file, faceidx, &(fn->ft.face));
    if (error)
    {
        free(fn);
        return NULL;
    }
    error = FT_Set_Char_Size(fn->ft.face, 0, (size * 64), 96, 96);
    if (error)
        error = FT_Set_Pixel_Sizes(fn->ft.face, 0, size);
    if (error)
    {
        int             i;
        int             chosen_size = 0;
        int             chosen_width = 0;

        for (i = 0; i < fn->ft.face->num_fixed_sizes; i++)
        {
            int             s;
            int             d, cd;

            s = fn->ft.face->available_sizes[i].height;
            cd = chosen_size - size;
            if (cd < 0)
                cd = -cd;
            d = s - size;
            if (d < 0)
                d = -d;
            if (d < cd)
            {
                chosen_width = fn->ft.face->available_sizes[i].width;
                chosen_size = s;
            }
            if (d == 0)
                break;
        }
        error = FT_Set_Pixel_Sizes(fn->ft.face, chosen_width, chosen_size);
        if (error)
        {
            /* couldn't choose the size anyway... what now? */
        }
    }

    error = FT_Select_Charmap(fn->ft.face, ft_encoding_unicode);
    if (error)
    {
    }

    fn->file = strdup(file);
    fn->name = strdup(file);
    fn->size = size;

    fn->glyphs = NULL;

    fn->usage = 0;

    fn->references = 1;

    fn->fallback_prev = NULL;
    fn->fallback_next = NULL;

    fonts = __imlib_object_list_prepend(fonts, fn);
    return fn;
}

void
__imlib_font_free(ImlibFont *fn)
{
    __imlib_font_remove_from_fallback_chain_imp(fn);

    fn->references--;
    if (fn->references == 0)
    {
        __imlib_font_modify_cache_by(fn, 1);
        __imlib_font_flush();
    }
}

int
__imlib_font_insert_into_fallback_chain_imp(ImlibFont *fn, ImlibFont *fallback)
{
    /* avoid infinite recursion */
    if (fn == fallback)
        return 1;

    /* now remove the given fallback font from any chain it's already in */
    __imlib_font_remove_from_fallback_chain_imp(fallback);

    /* insert fallback into fn's font chain */
    ImlibFont      *tmp = fn->fallback_next;

    fn->fallback_next = fallback;
    fallback->fallback_prev = fn;
    fallback->fallback_next = tmp;
    if (tmp)
        tmp->fallback_prev = fallback;
    return 0;
}

void
__imlib_font_remove_from_fallback_chain_imp(ImlibFont *fn)
{
    /* if fn has a previous font in its font chain, then make its fallback_next fn's fallback_next since fn is going away */
    if (fn->fallback_prev)
        fn->fallback_prev->fallback_next = fn->fallback_next;
    fn->fallback_prev = NULL;
    fn->fallback_next = NULL;
}

static int
font_modify_cache_cb(Imlib_Hash *hash, const char *key, void *data, void *fdata)
{
    int            *dir;
    Imlib_Font_Glyph *fg;

    fg = data;
    dir = fdata;
    font_cache_usage += (*dir) * ((fg->glyph_out->bitmap.width * fg->glyph_out->bitmap.rows) + sizeof(Imlib_Font_Glyph) + sizeof(Imlib_Object_List) + 400);     /* fudge values */
    return 1;
}

void
__imlib_font_modify_cache_by(ImlibFont *fn, int dir)
{
    int             sz_name = 0, sz_file = 0, sz_hash = 0;

    if (fn->name)
        sz_name = strlen(fn->name);
    if (fn->file)
        sz_file = strlen(fn->file);
    if (fn->glyphs)
        sz_hash = sizeof(Imlib_Hash);
    __imlib_hash_foreach(fn->glyphs, font_modify_cache_cb, &dir);
    font_cache_usage += dir * (sizeof(ImlibFont) + sz_name + sz_file + sz_hash + sizeof(FT_FaceRec) + 16384);   /* fudge values */
}

int
__imlib_font_cache_get(void)
{
    return font_cache;
}

void
__imlib_font_cache_set(int size)
{
    font_cache = size;
    __imlib_font_flush();
}

void
__imlib_font_flush(void)
{
    if (font_cache_usage < font_cache)
        return;
    while (font_cache_usage > font_cache)
        __imlib_font_flush_last();
}

static int
font_flush_free_glyph_cb(Imlib_Hash *hash, const char *key, void *data,
                         void *fdata)
{
    Imlib_Font_Glyph *fg;

    fg = data;
    FT_Done_Glyph(fg->glyph);
    free(fg);
    return 1;
}

void
__imlib_font_flush_last(void)
{
    Imlib_Object_List *l;
    ImlibFont      *fn = NULL;

    for (l = fonts; l; l = l->next)
    {
        ImlibFont      *fn_tmp;

        fn_tmp = (ImlibFont *) l;
        if (fn_tmp->references == 0)
            fn = fn_tmp;
    }
    if (!fn)
        return;

    fonts = __imlib_object_list_remove(fonts, fn);
    __imlib_font_modify_cache_by(fn, -1);

    __imlib_hash_foreach(fn->glyphs, font_flush_free_glyph_cb, NULL);
    __imlib_hash_free(fn->glyphs);

    free(fn->file);
    free(fn->name);
    FT_Done_Face(fn->ft.face);
    free(fn);
}

ImlibFont      *
__imlib_font_find(const char *name, int size)
{
    Imlib_Object_List *l;

    for (l = fonts; l; l = l->next)
    {
        ImlibFont      *fn;

        fn = (ImlibFont *) l;
        if ((fn->size == size) && (!strcmp(name, fn->name)))
        {
            if (fn->references == 0)
                __imlib_font_modify_cache_by(fn, -1);
            fn->references++;
            fonts = __imlib_object_list_remove(fonts, fn);
            fonts = __imlib_object_list_prepend(fonts, fn);
            return fn;
        }
    }
    return NULL;
}

/* font pathes */
void
__imlib_font_add_font_path(const char *path)
{
    fpath_num++;
    if (!fpath)
        fpath = malloc(sizeof(char *));
    else
        fpath = realloc(fpath, (fpath_num * sizeof(char *)));
    fpath[fpath_num - 1] = strdup(path);
}

void
__imlib_font_del_font_path(const char *path)
{
    int             i, j;

    for (i = 0; i < fpath_num; i++)
    {
        if (!strcmp(path, fpath[i]))
        {
            free(fpath[i]);
            fpath_num--;
            for (j = i; j < fpath_num; j++)
                fpath[j] = fpath[j + 1];
            if (fpath_num > 0)
                fpath = realloc(fpath, fpath_num * sizeof(char *));
            else
            {
                free(fpath);
                fpath = NULL;
            }
        }
    }
}

int
__imlib_font_path_exists(const char *path)
{
    int             i;

    for (i = 0; i < fpath_num; i++)
    {
        if (!strcmp(path, fpath[i]))
            return 1;
    }
    return 0;
}

char          **
__imlib_font_list_font_path(int *num_ret)
{
    *num_ret = fpath_num;
    return fpath;
}

/* fonts list */
char          **
__imlib_font_list_fonts(int *num_ret)
{
    int             i, j, d, l = 0;
    char          **list = NULL, **dir, *path;
    FT_Error        error;
    char           *p;

    __imlib_font_init();

    for (i = 0; i < fpath_num; i++)
    {
        dir = __imlib_FileDir(fpath[i], &d);
        if (dir)
        {
            for (j = 0; j < d; j++)
            {
                path = malloc(strlen(fpath[i]) + strlen(dir[j]) + 2);
                sprintf(path, "%s/%s", fpath[i], dir[j]);
                /* trim .ttf if it is there */
                if ((p = strrchr(dir[j], '.')))
                    *p = '\0';
                if (!__imlib_ItemInList(list, l, dir[j]))
                {
                    if (__imlib_FileIsFile(path))
                    {
                        FT_Face         f;

                        error = FT_New_Face(ft_lib, path, 0, &f);
                        if (!error)
                        {
                            FT_Done_Face(f);
                            l++;
                            if (list)
                                list = realloc(list, sizeof(char *) * l);
                            else
                                list = malloc(sizeof(char *));
                            list[l - 1] = strdup(dir[j]);
                        }
                    }
                }
                free(path);
            }
            __imlib_FileFreeDirList(dir, d);
        }
    }
    *num_ret = l;
    return list;
}
