// generate thumbnail for music files - look for album art on google
#include "thumb.h"
#include <Emotion.h>

// XXX: can do progressive resize down ie scale to 512 then take 512 and
// halve to 256 then halve it to 128 etc. rather than render from orig to
// target size....

static Evas_Object *im = NULL;
static Eina_Bool alpha = EINA_FALSE;
static int iw = 0, ih = 0;

static Eina_List *results = NULL;

static void *mem_data = NULL;
static int mem_size = 0;
static int query_pass = 0;

static char *title = NULL;
static char *artist = NULL;
static char *album = NULL;

typedef struct
{
   char *url;
   int w, h;
   unsigned long long fitness;
} Result;

static void
_thumb_image_setup(void)
{ // create and show image
   im = evas_object_image_filled_add(evas_object_evas_get(subwin));
   evas_object_show(im);
}

static void
_thumb_image_mem_set(void *data, int size)
{ // set file to image, get size & alpha
   evas_object_image_memfile_set(im, data, size, "jpg", NULL);
   evas_object_image_size_get(im, &iw, &ih);
   alpha = evas_object_image_alpha_get(im);
}

static void
_thumb_image_file_set(const char *file)
{ // set file to image, get size & alpha
   evas_object_image_file_set(im, file, NULL);
   evas_object_image_size_get(im, &iw, &ih);
   alpha = evas_object_image_alpha_get(im);
}

static Eina_Bool
_cb_vid_open_done_timeout(void *data EINA_UNUSED)
{
   elm_exit();
   return EINA_FALSE;
}

static void
_cb_vid_open_done(void *data EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED)
{ // we finished opening - get netadata
   const char *s;

   s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_TITLE);
   if (s) title = strdup(s);
   s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ARTIST);
   if (s) artist = strdup(s);
   s = emotion_object_meta_info_get(obj, EMOTION_META_INFO_TRACK_ALBUM);
   if (s) album = strdup(s);
   // finish loop
   elm_exit();
}

static void
_video_metadata_get(const char *path)
{
   Ecore_Timer *t;
   Evas_Object *o = emotion_object_add(evas_object_evas_get(subwin));

   evas_object_smart_callback_add(o, "open_done", _cb_vid_open_done, NULL);
   emotion_object_file_set(o, path);
   emotion_object_audio_mute_set(o, EINA_TRUE);
   emotion_object_audio_volume_set(o, 0.0);
   // a timeout for the loop
   t = ecore_timer_add(10.0, _cb_vid_open_done_timeout, NULL);
   elm_run();
   ecore_timer_del(t);
   evas_object_del(o);
}

static void
_cb_results(void *data EINA_UNUSED, Eina_List *results_orig, Eina_List *results_cached EINA_UNUSED)
{
   Eina_List *l;
   Search_Result *res;
   Result *r;
   unsigned long long fit_size, fit_square, fit_jpg, fit_listpos, fit_pass;

   // we need to re-score results first square better than not suqare
   // next - higher est better than lower rest
   // ends in .jpg, .jpeg better than not
   fit_listpos = 100;
   EINA_LIST_FOREACH(results_orig, l, res)
     {
        // skip results that are 0 sized or with no url
        if ((res->w <= 0) || (res->h <= 0) || (!res->url)) continue;
        // new result
        r = calloc(1, sizeof(Result));
        if (!r) continue;
        r->w = res->w;
        r->h = res->h;
        r->url = strdup(res->url);
        // jpegs preferred
        if ((eina_fnmatch("*.jpg", res->url, EINA_FNMATCH_CASEFOLD)) ||
            (eina_fnmatch("*.jpeg", res->url, EINA_FNMATCH_CASEFOLD)) ||
            (eina_fnmatch("*.jpe", res->url, EINA_FNMATCH_CASEFOLD)))
          fit_jpg = 100;
        else
          fit_jpg = 50;
        // bigger is better
        fit_size = (r->w / 10) * (r->h / 10);
        // if it's bigger than 1000x1000 it's not really better
        if (fit_size > 10000) fit_size = 10000;
        // more square is better
        fit_square = (100 * r->w) / r->h;
        if (fit_square > 100) fit_square = (100 * r->h) / r->w;
        // first pass gets a higher multiplier than latter passes
        fit_pass = ((10 - query_pass) * 100) / 10;
        // store fitness and result
        r->fitness = fit_listpos * fit_size  * fit_square * fit_jpg * fit_pass;
        results = eina_list_append(results, r);
        // list position fitness goes down by .9 of previous list pos fitness
        fit_listpos = (90 * fit_listpos) / 100;
        if (fit_listpos < 1) fit_listpos = 1;
     }
}

static int
_cb_fitness_sort(const void *data1, const void *data2)
{
   const Result *r1 = data1, *r2 = data2;

   if      (r1->fitness < r2->fitness) return 1;
   else if (r1->fitness > r2->fitness) return -1;
   return 0;
}

static void
_cb_url_bin(void *data EINA_UNUSED, const void *result, size_t size)
{ // handle in memory fetch of image
   // too big - 64M
   if (size > (64 * 1024 * 1024)) return;
   if (mem_data) free(mem_data);
   mem_data = malloc(size);
   mem_size = size;
   memcpy(mem_data, result, size);
   elm_exit();
}

static void
_thumb_online_search(const char *path)
{
   Eina_Strbuf *query_buf;
   const char *file, *ext;
   Eina_List *l;
   Result *r;
   int i;

   // get file and where extension starts to be removed
   file = ecore_file_file_get(path);
   ext = strchr(file, '.');

   _video_metadata_get(path);

   if ((title) || (album) || (artist))
     {
        query_buf = eina_strbuf_new();
        if (artist)
          {
             eina_strbuf_append(query_buf, artist);
             eina_strbuf_append(query_buf, " ");
          }
        if (artist)
          {
             eina_strbuf_append(query_buf, artist);
             eina_strbuf_append(query_buf, " ");
          }
        if (title)
          {
             eina_strbuf_append(query_buf, title);
             eina_strbuf_append(query_buf, " ");
          }
        thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL);
        eina_strbuf_free(query_buf);
        // we have real metasata - make filename searches lhave lower
        // fitness by bumping query pass
        query_pass += 4;
        free(title);
        free(album);
        free(artist);
     }

   // search using filename as our search
   // search for munged filename + "album art"
   query_buf = eina_strbuf_new();
   if (ext) // append all but extension and dot
     eina_strbuf_append_n(query_buf, file, ext - file);
   else // append the whole filename
     eina_strbuf_append(query_buf, file);
   eina_strbuf_append(query_buf, " album art");
   thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL);
   eina_strbuf_free(query_buf);
   query_pass++;

   // now search for just the munged filename without any extra string
   query_buf = eina_strbuf_new();
   if (ext) // append all but extension and dot
     eina_strbuf_append_n(query_buf, file, ext - file);
   else // append the whole filename
     eina_strbuf_append(query_buf, file);
   thumb_search_image(eina_strbuf_string_get(query_buf), _cb_results, NULL);
   eina_strbuf_free(query_buf);

   // sort results by fitness
   results = eina_list_sort(results, eina_list_count(results),
                            _cb_fitness_sort);

   i = 0;
   EINA_LIST_FOREACH(results, l, r)
     {
        // get image  max 64M
        thumb_url_bin_get(r->url, 64 * 1024 * 1024, _cb_url_bin, NULL);
        elm_run();
        if (mem_data)
          {
             // add filled image to then size accordingly
             _thumb_image_mem_set(mem_data, mem_size);
             free(mem_data);
             mem_data = NULL;
             if ((iw > 0) && (ih > 0)) break;
             i++;
          }
        // tried 10 - give up
        if (i >= 10) break;
     }
   if (mem_data) free(mem_data);
}

static char *
_thumb_explicit_find(const char *path)
{
   char *tmp = alloca(strlen(path) + 1 + 100);
   char *dir, *fraw, *s;
   const char *fname, *e, *c;
   const char *ext[] = {
      "png", "PNG",
      "jpg", "JPG",
      "jpeg", "JPEG",
      "jpe", "JPE",
      NULL };
   const char *cover[] = {
      "cover",   "Cover",   "COVER",
      "front",   "Front",   "FRONT",
      "folder",  "Folder",  "FOLDER",
      ".cover",  ".Cover",  ".COVER",
      ".front",  ".Front",  ".FRONT",
      ".folder", ".Folder", ".FOLDER",
      NULL };
   int i, j;

   // from here example comments assume /dir/file.mp3 as the path
   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/file.mp3.png etc.
        sprintf(tmp, "%s.%s", path, e);
        if (ecore_file_exists(tmp)) return strdup(tmp);
     }

   dir = ecore_file_dir_get(path);
   if (!dir)
     { // if no dir we are /file.mp3 thus "" works find for following code
        dir = strdup("");
        if (!dir) return NULL;
     }
   fname = ecore_file_file_get(path);
   if (!fname)
     { // this shouldn't happen - but handle it anyway
        free(dir);
        return NULL;
     }
   fraw  = strdup(fname); // fraw will be filename for e.g. filename.mp3
   if (!fraw)
     {
        free(dir);
        return NULL;
     }
   s = strrchr(fraw, '.');
   if (s) *s = 0;

   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/file.png etc.
        sprintf(tmp, "%s/.%s.%s", dir, fraw, e);
        if (ecore_file_exists(tmp)) goto found;
     }
   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/file.mp3.png etc.
        sprintf(tmp, "%s/.%s.%s", dir, fname, e);
        if (ecore_file_exists(tmp)) goto found;
     }
   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/.file.png etc.
        sprintf(tmp, "%s/.%s.%s", dir, fraw, e);
        if (ecore_file_exists(tmp)) goto found;
     }
   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/.file.mp3.png etc.
        sprintf(tmp, "%s/.thumb/%s.%s", dir, fname, e);
        if (ecore_file_exists(tmp)) goto found;
     }
   for (i = 0; (e = ext[i]) && e; i++)
     { // /dir/.thumb/file.png etc.
        sprintf(tmp, "%s/.thumb/%s.%s", dir, fraw, e);
        if (ecore_file_exists(tmp)) goto found;
     }

   // XXX: should we do this for every file in that dir? really?
   for (j = 0; (c = cover[j]) && c; j++)
     { // /dir/cover.png etc. - single img for everything in the dir...
        for (i = 0; (e = ext[i]) && e; i++)
          {
             sprintf(tmp, "%s/%s.%s", dir, c, e);
             if (ecore_file_exists(tmp)) goto found;
          }
     }

   free(dir);
   free(fraw);
   return NULL;
found:
   free(dir);
   free(fraw);
   return strdup(tmp);
}

int
thumb_music(Eet_File *ef, const char *path, const char *mime EINA_UNUSED, const char *thumb EINA_UNUSED)
{
   const int sizes[] = { 512, 256, 128, 64, 32, 16, 0 };
   int w, h, i;
   char buf[128];
   char *thumb_file;


   _thumb_image_setup();

   // look for an explicitly "requested" file for the thumb path
   thumb_file = _thumb_explicit_find(path);

   // if we didn't find an explicit matching thumb path in dir or nearby...
   if (thumb_file) _thumb_image_file_set(thumb_file);
   // explicit thumb not found or the load failed as image size is not sane
   if ((iw <= 0) || (ih < 0)) _thumb_online_search(path);

   // if size is bunk - we can't load it...
   if ((iw <= 0) || (ih < 0)) return 2;

   // write a big 1024x1024 preview (but no larger than the original image)
   w = iw; h = ih; scale_out(&w, &h, 1024, 1024, EINA_FALSE);

   // resize to target size
   evas_object_resize(im, w, h);
   evas_object_move(im, (1024 - w) / 2, 0);
   evas_object_resize(subwin, 1024, 1024);
   // render our current state and pick up pixel results
   elm_win_render(subwin);
   // save out preview size
   snprintf(buf, sizeof(buf), "image/preview");
   thumb_image_write(ef, buf, image, alpha, EINA_TRUE);
   snprintf(buf, sizeof(buf), "%i %i", w, h);
   eet_write(ef, "image/preview/size", buf, strlen(buf) + 1,
             EET_COMPRESSION_NONE);

   // multiple thumb sizes so can load/pick the best one at runtime
   for (i = 0; sizes[i] != 0; i++)
     {
        // scale down and keep aspect
        w = iw; h = ih; scale_out(&w, &h, sizes[i], sizes[i], EINA_FALSE);
        // resize to target size
        evas_object_resize(im, w, h);
        evas_object_move(im, (sizes[i] - w) / 2, 0);
        evas_object_resize(subwin, sizes[i], sizes[i]);
        // render our current state and pick up pixel results
        elm_win_render(subwin);
        // save out thumb size
        snprintf(buf, sizeof(buf), "image/thumb/%i", sizes[i]);
        thumb_image_write(ef, buf, image, alpha, EINA_TRUE);
     }

   return 0;
}
