#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;
}
