#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "evas_common_private.h"
#include "evas_private.h"
#include "evas_vg_private.h"

static Vg_Cache* vg_cache = NULL;

struct ext_loader_s
{
   unsigned int length;
   const char *extension;
   const char *loader;
};

struct ext_saver_s
{
   unsigned int length;
   const char *extension;
   const char *saver;
};

#define MATCHING(Ext, Module) { sizeof(Ext)-1, Ext, Module }

static const struct ext_loader_s loaders[] =
{ /* map extensions to loaders to use for good first-guess tries */
   MATCHING(".eet", "eet"),
   MATCHING(".edj", "eet"),
   MATCHING(".svg", "svg"),
   MATCHING(".svgz", "svg"),
   MATCHING(".svg.gz", "svg")
};

static const char *loaders_name[] =
{ /* in order of most likely needed */
  "eet", "json", "svg"
};

static const struct ext_saver_s savers[] =
{ /* map extensions to savers to use for good first-guess tries */
   MATCHING(".eet", "eet"),
   MATCHING(".edj", "eet"),
   MATCHING(".svg", "svg")
};

static Evas_Module *
_find_loader_module(const char *file)
{
   const char           *loader = NULL, *end;
   Evas_Module          *em = NULL;
   unsigned int          i;
   int                   len, len2;
   len = strlen(file);
   end = file + len;
   for (i = 0; i < (sizeof (loaders) / sizeof(struct ext_loader_s)); i++)
     {
        len2 = loaders[i].length;
        if (len2 > len) continue;
        if (!strcasecmp(end - len2, loaders[i].extension))
          {
             loader = loaders[i].loader;
             break;
          }
     }
   if (loader)
     em = evas_module_find_type(EVAS_MODULE_TYPE_VG_LOADER, loader);
   return em;
}

static Vg_File_Data *
_vg_load_from_file(const Eina_File *file, const char *key)
{
   Evas_Module       *em;
   Evas_Vg_Load_Func *loader;
   int                error = EVAS_LOAD_ERROR_GENERIC;
   Vg_File_Data      *vfd;
   unsigned int i;

   const char *file_name = eina_file_filename_get(file);
   em = _find_loader_module(file_name);
   if (em)
     {
        loader = em->functions;
        vfd = loader->file_open((Eina_File *) file, key, &error);
        if (vfd)
          {
             vfd->loader = loader;
             return vfd;
          }
     }
   else
     {
        for (i = 0; i < sizeof (loaders_name) / sizeof (char *); i++)
          {
             em = evas_module_find_type(EVAS_MODULE_TYPE_VG_LOADER, loaders_name[i]);
             if (em)
               {
                  loader = em->functions;
                  vfd = loader->file_open((Eina_File *) file, key, &error);
                  if (vfd)
                    {
                       vfd->loader = loader;
                       return vfd;
                    }
               }
          }
     }
   WRN("Exhausted all means to load vector file = %s", file_name);
   return NULL;
}

static Evas_Module *
_find_saver_module(const char *file)
{
   const char           *saver = NULL, *end;
   Evas_Module          *em = NULL;
   unsigned int          i;
   int                   len, len2;
   len = strlen(file);
   end = file + len;
   for (i = 0; i < (sizeof (savers) / sizeof(struct ext_saver_s)); i++)
     {
        len2 = savers[i].length;
        if (len2 > len) continue;
        if (!strcasecmp(end - len2, savers[i].extension))
          {
             saver = savers[i].saver;
             break;
          }
     }
   if (saver)
     em = evas_module_find_type(EVAS_MODULE_TYPE_VG_SAVER, saver);
   return em;
}

static void
_evas_cache_vg_data_free_cb(void *data)
{
   Vg_File_Data *vfd = data;
   vfd->loader->file_close(vfd);
}

static void
_evas_cache_vg_entry_free_cb(void *data)
{
   Vg_Cache_Entry *vg_entry = data;

   if (vg_entry->vfd)
     {
        vg_entry->vfd->ref--;

        if (vg_entry->vfd->ref <= 0)
          {
             if (vg_entry->vfd->shareable)
               {
                  Eina_Strbuf *hash_key = eina_strbuf_new();
                  eina_strbuf_append_printf(hash_key, "%s/%s/%p",
                                            eina_file_filename_get(vg_entry->file),
                                            vg_entry->key,
                                            vg_entry->evas);
                  if (!eina_hash_del(vg_cache->vfd_hash, eina_strbuf_string_get(hash_key), vg_entry->vfd))
                    ERR("Failed to delete vfd = (%p) from hash", vg_entry->vfd);
                  eina_strbuf_free(hash_key);
               }
             else
               {
                  vg_entry->vfd->loader->file_close(vg_entry->vfd);
               }
          }
     }

   eina_stringshare_del(vg_entry->key);
   free(vg_entry->hash_key);
   efl_unref(vg_entry->root);
   free(vg_entry);
}

static Eina_Bool
_vg_file_save(Vg_File_Data *vfd, const char *file, const char *key, const Efl_File_Save_Info *info)
{
   Evas_Module       *em;
   Evas_Vg_Save_Func *saver;
   Evas_Load_Error    error = EVAS_LOAD_ERROR_GENERIC;
   int                compress = 9;

   if (!file) return EINA_FALSE;

   if (info) compress = info->compression;

   em = _find_saver_module(file);
   if (em)
     {
        saver = em->functions;
        error = saver->file_save(vfd, file, key, compress);
     }

   if (error)
     return EINA_FALSE;

   return EINA_TRUE;
}

static void
_root_update(Vg_Cache_Entry *vg_entry)
{
   Vg_File_Data *vfd = vg_entry->vfd;

   /* Optimization: static viewbox may have same root data regardless of size.
      So we can't use the root data directly, but copy it for each vg_entries.
      In the meantime, non-static viewbox root data may have difference instance for each
      size. So it's affordable to share the root data for each vg_entries. */
   if (vfd->static_viewbox)
     {
        /* TODO: Yet trivial but still we may have a better solution to
           avoid this unnecessary copy. If the ector surface key is not
           to this root pointer. */
        vg_entry->root = efl_duplicate(vfd->root);
     }
   //Shareable??
   else if (vg_entry->root != vfd->root)
     {
        if (vg_entry->root) efl_unref(vg_entry->root);
        vg_entry->root = efl_ref(vfd->root);
     }
}

static void
_local_transform(Efl_VG *root, double w, double h, Vg_File_Data *vfd)
{
   double sx = 0, sy= 0, scale;
   Eina_Matrix3 m;

   if (!vfd->static_viewbox) return;
   if (EINA_DBL_EQ(vfd->view_box.w, w) && EINA_DBL_EQ(vfd->view_box.h, h)) return;

   sx = w / vfd->view_box.w;
   sy = h / vfd->view_box.h;

   scale = sx < sy ? sx : sy;
   eina_matrix3_identity(&m);

   // align hcenter and vcenter
   if (vfd->preserve_aspect)
     {
        eina_matrix3_translate(&m, (w - vfd->view_box.w * scale)/2.0, (h - vfd->view_box.h * scale)/2.0);
        eina_matrix3_scale(&m, scale, scale);
        eina_matrix3_translate(&m, -vfd->view_box.x, -vfd->view_box.y);
     }
   else
     {
        eina_matrix3_scale(&m, sx, sy);
        eina_matrix3_translate(&m, -vfd->view_box.x, -vfd->view_box.y);
     }
   efl_canvas_vg_node_transformation_set(root, &m);
}

void
evas_cache_vg_init(void)
{
   if (vg_cache)
     {
        vg_cache->ref++;
        return;
     }
   vg_cache = calloc(1, sizeof(Vg_Cache));
   if (!vg_cache)
     {
        CRI("Failed to alloc Vg_Cache");
        return;
     }

   vg_cache->vfd_hash = eina_hash_string_superfast_new(_evas_cache_vg_data_free_cb);
   vg_cache->vg_entry_hash = eina_hash_string_superfast_new(_evas_cache_vg_entry_free_cb);
   vg_cache->ref++;
}

void *
evas_cache_vg_surface_key_get(Efl_Canvas_Vg_Node *root, int w, int h, int frame_idx)
{
   //This make a unique key pointer by arguments.
   Eina_Strbuf *hash_key = eina_strbuf_new();
   eina_strbuf_append_printf(hash_key, "%p/%d/%d/%d", root, w, h, frame_idx);
   const char *new_key = eina_strbuf_string_get(hash_key);
   if (!new_key)
     {
        eina_strbuf_free(hash_key);
        return NULL;
     }

   Eina_List *l;
   char *key;
   EINA_LIST_FOREACH(vg_cache->vg_surface_keys, l, key)
     {
        //Exisiting key!
        if (!strcmp(key, new_key))
          {
             eina_strbuf_free(hash_key);
             return key;
          }
     }

   //New key comes.
   key = eina_strbuf_string_steal(hash_key);
   vg_cache->vg_surface_keys = eina_list_append(vg_cache->vg_surface_keys, key);

   eina_strbuf_free(hash_key);

   return (void *) key;
}

void
evas_cache_vg_shutdown(void)
{
   if (!vg_cache) return;
   vg_cache->ref--;
   if (vg_cache->ref > 0) return;

   char *key;
   EINA_LIST_FREE(vg_cache->vg_surface_keys, key)
     free(key);
   eina_list_free(vg_cache->vg_surface_keys);

   eina_hash_free(vg_cache->vg_entry_hash);
   eina_hash_free(vg_cache->vfd_hash);
   free(vg_cache);
   vg_cache = NULL;
}

Vg_File_Data *
evas_cache_vg_file_open(const Eina_File *file, const char *key, Evas *e, Eina_Bool shareable)
{
   Vg_File_Data *vfd;
   Eina_Strbuf *hash_key;

   if (shareable)
     {
        hash_key = eina_strbuf_new();
        eina_strbuf_append_printf(hash_key, "%s/%s/%p", eina_file_filename_get(file), key, e);
        vfd = eina_hash_find(vg_cache->vfd_hash, eina_strbuf_string_get(hash_key));
        if (!vfd)
          {
             vfd = _vg_load_from_file(file, key);
             //File exists.
             if (vfd) eina_hash_add(vg_cache->vfd_hash, eina_strbuf_string_get(hash_key), vfd);
          }
        eina_strbuf_free(hash_key);
     }
   else
     {
        vfd = _vg_load_from_file(file, key);
     }
   if (vfd) vfd->shareable = shareable;
   return vfd;
}

Vg_Cache_Entry*
evas_cache_vg_entry_resize(Vg_Cache_Entry *vg_entry, int w, int h)
{
   return evas_cache_vg_entry_create(vg_entry->evas, vg_entry->file, vg_entry->key, w, h, vg_entry->vfd->vp_list);
}

Vg_Cache_Entry*
evas_cache_vg_entry_create(Evas *evas,
                           const Eina_File *file,
                           const char *key,
                           int w, int h, Eina_List *vp_list)
{
   Vg_Cache_Entry* vg_entry;
   Eina_Strbuf *hash_key;

   if (!vg_cache) return NULL;

   //TODO: zero-sized entry is useless. how to skip it?
   hash_key = eina_strbuf_new();
   eina_strbuf_append_printf(hash_key, "%p/%p/%s/%d/%d/%p", evas, file, key, w, h, vp_list);
   vg_entry = eina_hash_find(vg_cache->vg_entry_hash, eina_strbuf_string_get(hash_key));
   if (!vg_entry)
     {
        vg_entry = calloc(1, sizeof(Vg_Cache_Entry));
        if (!vg_entry)
          {
             CRI("Failed to alloc Vg_Cache_Entry");
             eina_strbuf_free(hash_key);
             return NULL;
          }
        vg_entry->file = file;
        vg_entry->key = eina_stringshare_add(key);
        vg_entry->w = w;
        vg_entry->h = h;
        vg_entry->evas = evas;
        vg_entry->hash_key = eina_strbuf_string_steal(hash_key);
        eina_hash_direct_add(vg_cache->vg_entry_hash, vg_entry->hash_key, vg_entry);
     }
   eina_strbuf_free(hash_key);
   vg_entry->ref++;
   vg_entry->vfd = evas_cache_vg_file_open(file, key, vg_entry->evas, vp_list ? EINA_FALSE : EINA_TRUE);
   //No File??
   if (!vg_entry->vfd)
     {
        evas_cache_vg_entry_del(vg_entry);
        return NULL;
     }
   vg_entry->vfd->ref++;
   vg_entry->vfd->vp_list = vp_list;

   return vg_entry;
}

double
evas_cache_vg_anim_duration_get(const Vg_Cache_Entry* vg_entry)
{
   if (!vg_entry->vfd->anim_data) return 0;
   return vg_entry->vfd->anim_data->duration;
}

unsigned int
evas_cache_vg_anim_frame_count_get(const Vg_Cache_Entry* vg_entry)
{
   if (!vg_entry) return 0;
   Vg_File_Data *vfd = vg_entry->vfd;
   if (!vfd || !vfd->anim_data) return 0;
   return vfd->anim_data->frame_cnt;
}

Eina_Bool
evas_cache_vg_anim_sector_set(const Vg_Cache_Entry* vg_entry, const char *name, int startframe, int endframe)
{
   if (!vg_entry) return EINA_FALSE;
   if (!vg_entry->vfd->anim_data) return EINA_FALSE;
   if (!vg_entry->vfd->anim_data->markers) return EINA_FALSE;
   if (!name) return EINA_FALSE;

   Vg_File_Anim_Data_Marker *marker;
   Vg_File_Anim_Data_Marker new_marker;
   int i = 0;

   EINA_INARRAY_FOREACH(vg_entry->vfd->anim_data->markers, marker)
     {
        if (!strcmp(marker->name, name))
          {
             marker->startframe = startframe;
             marker->endframe = endframe;
             return EINA_TRUE;
          }
        i++;
     }

   new_marker.name = eina_stringshare_add(name);
   new_marker.startframe = startframe;
   new_marker.endframe = endframe;
   eina_inarray_push(vg_entry->vfd->anim_data->markers, &new_marker);

   return EINA_TRUE;
}

Eina_Bool
evas_cache_vg_anim_sector_get(const Vg_Cache_Entry* vg_entry, const char *name, int* startframe, int* endframe)
{
   if (!vg_entry) return EINA_FALSE;
   if (!vg_entry->vfd->anim_data) return EINA_FALSE;
   if (!vg_entry->vfd->anim_data->markers) return EINA_FALSE;
   if (!name) return EINA_FALSE;

   Vg_File_Anim_Data_Marker *marker;
   EINA_INARRAY_FOREACH(vg_entry->vfd->anim_data->markers, marker)
     {
        if (!strcmp(marker->name, name))
          {
             if (startframe) *startframe = marker->startframe;
             if (endframe) *endframe = marker->endframe;
             return EINA_TRUE;
          }
     }
   return EINA_FALSE;
}

Efl_VG*
evas_cache_vg_tree_get(Vg_Cache_Entry *vg_entry, unsigned int frame_num)
{
   if (!vg_entry) return NULL;
   if ((vg_entry->w < 1) || (vg_entry->h < 1)) return NULL;

   Vg_File_Data *vfd = vg_entry->vfd;
   if (!vfd) return NULL;

   //No need to update.
   if (vfd->anim_data)
     {
        if ((vg_entry->w == vfd->view_box.w) &&
            (vg_entry->h == vfd->view_box.h))
          {
             if (vg_entry->root &&
                 vfd->anim_data->frame_num == frame_num)
               return vg_entry->root;
          }
     }
   else
     {
        if (vg_entry->root)
          return vg_entry->root;
     }

   if (!vfd->static_viewbox)
     {
        vfd->view_box.w = vg_entry->w;
        vfd->view_box.h = vg_entry->h;
     }

   if (vfd->anim_data) vfd->anim_data->frame_num = frame_num;

   if (!vfd->loader->file_data(vfd)) return NULL;

   _root_update(vg_entry);

   _local_transform(vg_entry->root, vg_entry->w, vg_entry->h, vfd);

   return vg_entry->root;
}

void
evas_cache_vg_entry_value_provider_update(Vg_Cache_Entry *vg_entry, Eina_List *vp_list)
{
   if (!vg_entry) return;

   Vg_File_Data *vfd = vg_entry->vfd;
   if (!vfd) return;

   vfd->vp_list = vp_list;
}

void
evas_cache_vg_entry_del(Vg_Cache_Entry *vg_entry)
{
   if (!vg_cache || !vg_entry) return;
   vg_entry->ref--;
   if (vg_entry->ref > 0) return;
   if (!eina_hash_del(vg_cache->vg_entry_hash, vg_entry->hash_key, vg_entry))
     ERR("Failed to delete vg_entry = (%p) from hash", vg_entry);
}

Eina_Size2D
evas_cache_vg_entry_default_size_get(const Vg_Cache_Entry *vg_entry)
{
   if (!vg_entry) return EINA_SIZE2D(0, 0);
   return EINA_SIZE2D(vg_entry->vfd->w, vg_entry->vfd->h);
}

Eina_Bool
evas_cache_vg_entry_file_save(Vg_Cache_Entry *vg_entry, const char *file, const char *key, const Efl_File_Save_Info *info)
{
   Vg_File_Data *vfd =
      evas_cache_vg_file_open(vg_entry->file, vg_entry->key, vg_entry->evas
                              ,vg_entry->vfd ? (vg_entry->vfd->vp_list ? EINA_FALSE : EINA_TRUE): EINA_TRUE);

   if (!vfd) return EINA_FALSE;

   return _vg_file_save(vfd, file, key, info);
}

Eina_Bool
evas_cache_vg_file_save(Efl_VG *root, int w, int h, const char *file, const char *key, const Efl_File_Save_Info *info)
{
   Vg_File_Data vfd = {};

   if (!root) return EINA_FALSE;

   vfd.view_box.x = w;
   vfd.view_box.y = h;
   vfd.root = root;
   vfd.preserve_aspect = EINA_FALSE;

   return _vg_file_save(&vfd, file, key, info);
}
