#include "private.h"

#include <Elementary.h>
#include <Ethumb_Client.h>
#include <Emotion.h>
#include <Efreet.h>
#include <stdlib.h>
#include <unistd.h>
#include "media.h"
#include "config.h"
#include "theme.h"
#include "termiolink.h"

typedef struct tag_Media Media;

struct tag_Media
{
   Evas_Object_Smart_Clipped_Data __clipped_data;
   Evas_Object *clip, *o_img, *o_tmp, *o_ctrl, *o_busy, *o_event;
   Ecore_Timer *anim;
   Ecore_Timer *smooth_timer;
   Ecore_Job *restart_job;
   Ecore_Con_Url *url;
   Ecore_Event_Handler *url_prog_hand, *url_compl_hand;
   Ethumb_Client_Async *et_req;
   const char *src;
   const char *ext;
   const char *realf;
   const Config *config;
   double download_perc;
   int tmpfd;
   int w, h;
   int iw, ih;
   int sw, sh;
   int fr, frnum, loops;
   int mode;
   Media_Type type;
   int resizes;
   struct {
      Evas_Coord x, y;
      unsigned char down : 1;
   } down;
   unsigned char nosmooth : 1;
   unsigned char downloading : 1;
   unsigned char queued : 1;
   unsigned char pos_drag : 1;
};

static Evas_Smart *_smart = NULL;
static Evas_Smart_Class _parent_sc = EVAS_SMART_CLASS_INIT_NULL;

#include "extns.h"

static const char *
_is_fmt(const char *f, const char **extn)
{
   int i;
   size_t len;

   len = strlen(f);
   for (i = 0; extn[i]; i++)
     {
        size_t l = strlen(extn[i]);
        if (len < l)
          continue;
        if (!strcasecmp(extn[i], f + len - l))
          return extn[i];
     }
   return NULL;
}

//////////////////////// thumb

static Ethumb_Client *et_client = NULL;
static Eina_Bool et_connected = EINA_FALSE;
static Eina_List *et_queue = NULL;

static void _et_init(void);
static int _type_thumb_init2(Evas_Object *obj);

static void
_et_disconnect(void *_data EINA_UNUSED, Ethumb_Client *c)
{
   if (c != et_client) return;
   ethumb_client_disconnect(et_client);
   et_connected = EINA_FALSE;
   et_client = NULL;
   if (et_queue) _et_init();
}

static void
_et_connect(void *_data EINA_UNUSED, Ethumb_Client *c, Eina_Bool ok)
{
   if (ok)
     {
        Evas_Object *o;

        et_connected = EINA_TRUE;
        ethumb_client_on_server_die_callback_set(c, _et_disconnect,
                                                 NULL, NULL);
        EINA_LIST_FREE(et_queue, o)
          _type_thumb_init2(o);
     }
   else
     et_client = NULL;
}

static void
_et_init(void)
{
   if (et_client) return;

   ethumb_client_init();
   et_client = ethumb_client_connect(_et_connect, NULL, NULL);
}

static void
_type_thumb_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   if ((w <= 0) || (h <= 0) || (sd->iw <= 0) || (sd->ih <= 0))
     {
        w = 1;
        h = 1;
     }
   else
     {
        int iw = 1, ih = 1;

        iw = w;
        ih = (sd->ih * w) / sd->iw;
        if (ih > h)
          {
             ih = h;
             iw = (sd->iw * h) / sd->ih;
             if (iw > w) iw = w;
          }
        if ((iw > sd->iw) || (ih > sd->ih))
          {
             iw = sd->iw;
             ih = sd->ih;
          }
        x += ((w - iw) / 2);
        y += ((h - ih) / 2);
        w = iw;
        h = ih;
     }
   evas_object_move(sd->o_img, x, y);
   evas_object_resize(sd->o_img, w, h);
}

static void
_cb_thumb_preloaded(void *data,
                    Evas *_e EINA_UNUSED,
                    Evas_Object *_obj EINA_UNUSED,
                    void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   Evas_Coord ox, oy, ow, oh;
   if (!sd) return;

   evas_object_geometry_get(data, &ox, &oy, &ow, &oh);
   _type_thumb_calc(data, ox, oy, ow, oh);
   evas_object_show(sd->o_img);
   evas_object_show(sd->clip);
}

static void
_et_done(Ethumb_Client *_c EINA_UNUSED,
         const char *file, const char *key, void *data)
{
   Evas_Object *obj = data;
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;

//   if (c != et_client) return;
   sd->et_req = NULL;
   evas_object_event_callback_add(sd->o_img, EVAS_CALLBACK_IMAGE_PRELOADED,
                                  _cb_thumb_preloaded, obj);
   evas_object_image_file_set(sd->o_img, file, key);
   evas_object_image_size_get(sd->o_img, &(sd->iw), &(sd->ih));
   evas_object_image_preload(sd->o_img, EINA_FALSE);
}

static void
_et_error(Ethumb_Client *_c EINA_UNUSED, void *data)
{
   Evas_Object *obj = data;
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;

//   if (c != et_client) return;
   sd->et_req = NULL;
}

static int
_type_thumb_init2(Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   Eina_Bool ok;

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   if ((sd->realf) && (sd->realf[0] != '/'))
     {
        /* TODO: Listen for theme cache changes */
        static const char *icon_theme = NULL;
        const char *fl = NULL;

        if (!icon_theme)
          {
             Efreet_Icon_Theme *theme;

             theme = efreet_icon_theme_find(getenv("E_ICON_THEME"));
             if (!theme)
               {
                  const char **itr;
                  static const char *themes[] = {
                       "Human", "oxygen", "gnome", "hicolor", NULL
                  };
                  for (itr = themes; *itr; itr++)
                    {
                       theme = efreet_icon_theme_find(*itr);
                       if (!theme) continue;
                       //try to fetch the icon, if we don't find it, continue at other themes
                       icon_theme = eina_stringshare_add(theme->name.internal);
                       fl = efreet_icon_path_find(icon_theme, sd->realf, sd->iw);
                       if (fl) break;
                    }
               }
             else
               {
                  icon_theme = eina_stringshare_add(theme->name.internal);
               }
          }
        if (!fl)
          fl = efreet_icon_path_find(icon_theme, sd->realf, sd->iw);
        ok = ethumb_client_file_set(et_client, fl, NULL);
        if (!ok)
          return -1;
     }
   else
     {
        ok = ethumb_client_file_set(et_client, sd->realf, NULL);
        if (!ok)
          return -1;
     }

   sd->et_req = ethumb_client_thumb_async_get(et_client, _et_done,
                                              _et_error, obj);
   sd->queued = EINA_FALSE;
   return 0;
}

static int
_type_thumb_init(Evas_Object *obj)
{
   Evas_Object *o;
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   sd->type = MEDIA_TYPE_THUMB;
   _et_init();
   o = sd->o_img = evas_object_image_filled_add(evas_object_evas_get(obj));
   evas_object_image_load_orientation_set(o, EINA_TRUE);
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_raise(sd->o_event);
   sd->iw = 64;
   sd->ih = 64;
   if (!et_connected)
     {
        et_queue = eina_list_append(et_queue, obj);
        sd->queued = EINA_TRUE;
        return 0;
     }
   return _type_thumb_init2(obj);
}

//////////////////////// img
static void
_cb_img_preloaded(void *data,
                  Evas *_e EINA_UNUSED,
                  Evas_Object *_obj EINA_UNUSED,
                  void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   evas_object_show(sd->o_img);
   evas_object_show(sd->clip);
}

static Eina_Bool
_cb_img_frame(void *data)
{
   double t;
   int fr;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return EINA_FALSE;
   sd->fr++;
   fr = ((sd->fr - 1) % (sd->frnum)) + 1;
   if ((sd->fr >= sd->frnum) && (fr == 1))
     {
        int loops;

        if (evas_object_image_animated_loop_type_get(sd->o_img) ==
            EVAS_IMAGE_ANIMATED_HINT_NONE)
          {
             sd->anim = NULL;
             return EINA_FALSE;
          }
        sd->loops++;
        loops = evas_object_image_animated_loop_count_get(sd->o_img);
        if (loops != 0) // loop == 0 -> loop forever
          {
             if (loops < sd->loops)
               {
                  sd->anim = NULL;
                  return EINA_FALSE;
               }
          }
     }
   evas_object_image_animated_frame_set(sd->o_img, fr);
   t = evas_object_image_animated_frame_duration_get(sd->o_img, fr, 0);
   ecore_timer_interval_set(sd->anim, t);
   return EINA_TRUE;
}

static int
_type_img_anim_handle(Evas_Object *obj)
{
   double t;
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   if (!evas_object_image_animated_get(sd->o_img))
     return 0;

   sd->fr = 1;
   sd->frnum = evas_object_image_animated_frame_count_get(sd->o_img);
   if (sd->frnum < 2)
     return 0;

   t = evas_object_image_animated_frame_duration_get(sd->o_img, sd->fr, 0);
   sd->anim = ecore_timer_add(t, _cb_img_frame, obj);
   return 0;
}

static int
_type_img_init(Evas_Object *obj)
{
   Evas_Object *o;
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   o = sd->o_img = evas_object_image_filled_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_raise(sd->o_event);
   evas_object_event_callback_add(o, EVAS_CALLBACK_IMAGE_PRELOADED,
                                  _cb_img_preloaded, obj);
   evas_object_image_load_orientation_set(o, EINA_TRUE);
   evas_object_image_file_set(o, sd->realf, NULL);
   evas_object_image_size_get(o, &(sd->iw), &(sd->ih));
   evas_object_image_preload(o, EINA_FALSE);
   return _type_img_anim_handle(obj);
}

static void
_type_img_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((w <= 0) || (h <= 0) || (sd->iw <= 0) || (sd->ih <= 0))
     {
        w = 1;
        h = 1;
     }
   else
     {
        int iw = 1, ih = 1;

        if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_BG)
          {
             iw = w;
             ih = (sd->ih * w) / sd->iw;
             if (ih < h)
               {
                  ih = h;
                  iw = (sd->iw * h) / sd->ih;
                  if (iw < w) iw = w;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_POP)
          {
             iw = w;
             ih = (sd->ih * w) / sd->iw;
             if (ih > h)
               {
                  ih = h;
                  iw = (sd->iw * h) / sd->ih;
                  if (iw > w) iw = w;
               }
             if ((iw > sd->iw) || (ih > sd->ih))
               {
                  iw = sd->iw;
                  ih = sd->ih;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_STRETCH)
          {
             iw = w;
             ih = h;
          }
        x += ((w - iw) / 2);
        y += ((h - ih) / 2);
        w = iw;
        h = ih;
     }
   evas_object_move(sd->o_img, x, y);
   evas_object_resize(sd->o_img, w, h);
}

//////////////////////// scalable img
static void
_cb_scale_preloaded(void *data,
                    Evas *_e EINA_UNUSED,
                    Evas_Object *_obj EINA_UNUSED,
                    void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   if (!sd->o_tmp)
     {
        evas_object_show(sd->o_img);
        evas_object_show(sd->clip);
     }
   else
     {
        evas_object_del(sd->o_img);
        sd->o_img = sd->o_tmp;
        sd->o_tmp = NULL;
        evas_object_show(sd->o_img);
        evas_object_show(sd->clip);
     }
}

static int
_type_scale_init(Evas_Object *obj)
{
   Evas_Object *o;
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   o = sd->o_img = evas_object_image_filled_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_raise(sd->o_event);
   evas_object_event_callback_add(o, EVAS_CALLBACK_IMAGE_PRELOADED,
                                  _cb_scale_preloaded, obj);
   evas_object_image_load_orientation_set(o, EINA_TRUE);
   evas_object_image_file_set(o, sd->realf, NULL);
   evas_object_image_size_get(o, &(sd->iw), &(sd->ih));
   evas_object_image_preload(o, EINA_FALSE);

   return 0;
}

static void
_type_scale_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if ((w <= 0) || (h <= 0) || (sd->iw <= 0) || (sd->ih <= 0))
     {
        w = 1;
        h = 1;
     }
   else
     {
        int iw = 1, ih = 1;

        if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_BG)
          {
             iw = w;
             ih = (sd->ih * w) / sd->iw;
             if (ih < h)
               {
                  ih = h;
                  iw = (sd->iw * h) / sd->ih;
                  if (iw < w) iw = w;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_POP)
          {
             iw = w;
             ih = (sd->ih * w) / sd->iw;
             if (ih > h)
               {
                  ih = h;
                  iw = (sd->iw * h) / sd->ih;
                  if (iw > w) iw = w;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_STRETCH)
          {
             iw = w;
             ih = h;
          }
        x += ((w - iw) / 2);
        y += ((h - ih) / 2);
        w = iw;
        h = ih;
     }
   if (!sd->nosmooth)
     {
        Evas_Coord lw, lh;

        lw = w;
        lh = h;
        if (lw < 256) lw = 256;
        if (lh < 256) lh = 256;
        if ((lw != sd->sw) || (lh != sd->sh))
          {
             Evas_Object *o;

             o = sd->o_tmp = evas_object_image_filled_add(evas_object_evas_get(obj));
             evas_object_smart_member_add(o, obj);
             evas_object_clip_set(o, sd->clip);
             evas_object_raise(sd->o_event);
             evas_object_event_callback_add(o, EVAS_CALLBACK_IMAGE_PRELOADED,
                                            _cb_scale_preloaded, obj);
             evas_object_image_load_orientation_set(o, EINA_TRUE);
             evas_object_image_file_set(o, sd->realf, NULL);
             evas_object_image_load_size_set(sd->o_tmp, lw, lh);
             evas_object_image_preload(o, EINA_FALSE);
          }
        sd->sw = lw;
        sd->sh = lh;
     }
   if (sd->o_tmp)
     {
        evas_object_move(sd->o_tmp, x, y);
        evas_object_resize(sd->o_tmp, w, h);
     }
   evas_object_move(sd->o_img, x, y);
   evas_object_resize(sd->o_img, w, h);
}

//////////////////////// edj
static void
_cb_edje_preloaded(void *data,
                   Evas_Object *_obj EINA_UNUSED,
                   const char *_sig EINA_UNUSED,
                   const char *_src EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   evas_object_show(sd->o_img);
   evas_object_show(sd->clip);
}

static int
_type_edje_init(Evas_Object *obj)
{
   Evas_Object *o;
   int i;
   const char *groups[] =
     {
        "terminology/background",
        "e/desktop/background",
        NULL
     };
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   o = sd->o_img = edje_object_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_raise(sd->o_event);
   for (i = 0; groups[i]; i++)
     {
        if (edje_object_file_set(o, sd->realf, groups[i]))
          {
             edje_object_signal_callback_add(o, "preload,done", "",
                                             _cb_edje_preloaded, obj);
             edje_object_preload(o, EINA_FALSE);
             theme_auto_reload_enable(o);
             return 0;
          }
     }
   return 0;
}

static void
_type_edje_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_move(sd->o_img, x, y);
   evas_object_resize(sd->o_img, w, h);
}

//////////////////////// movie
static void _type_mov_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h);

static void
_cb_mov_frame_decode(void *data,
                     Evas_Object *_obj EINA_UNUSED,
                     void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   Evas_Coord ox, oy, ow, oh;
   double len, pos;

   if (!sd) return;
   evas_object_geometry_get(data, &ox, &oy, &ow, &oh);
   evas_object_show(sd->o_img);
   evas_object_show(sd->clip);
   _type_mov_calc(data, ox, oy, ow, oh);

   if (sd->pos_drag) return;
   len = emotion_object_play_length_get(sd->o_img);
   pos = emotion_object_position_get(sd->o_img);
   pos /= len;
   edje_object_part_drag_value_set(sd->o_ctrl, "terminology.posdrag", pos, pos);
}

static void
_cb_mov_frame_resize(void *data,
                     Evas_Object *_obj EINA_UNUSED,
                     void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   Evas_Coord ox, oy, ow, oh;

   if (!sd) return;
   evas_object_geometry_get(data, &ox, &oy, &ow, &oh);
   _type_mov_calc(data, ox, oy, ow, oh);
}

static void
_cb_mov_len_change(void *data,
                   Evas_Object *_obj EINA_UNUSED,
                   void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
}

static void
_cb_mov_restart(void *data)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   sd->restart_job = NULL;
   emotion_object_position_set(sd->o_img, 0.0);
   emotion_object_play_set(sd->o_img, EINA_TRUE);
}

static void
_cb_mov_decode_stop(void *data,
                    Evas_Object *_obj EINA_UNUSED,
                    void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   if (sd->restart_job) ecore_job_del(sd->restart_job);
   sd->restart_job = ecore_job_add(_cb_mov_restart, data);
   evas_object_smart_callback_call(data, "loop", NULL);
}

static void
_cb_mov_progress(void *data,
                 Evas_Object *_obj EINA_UNUSED,
                 void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   DBG("progress: '%s' '%3.3f",
       emotion_object_progress_info_get(sd->o_img),
       emotion_object_progress_status_get(sd->o_img));
}

static void
_cb_mov_ref(void *data,
            Evas_Object *_obj EINA_UNUSED,
            void *_event EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   DBG("ref: '%s' num '%i'",
       emotion_object_ref_file_get(sd->o_img),
       emotion_object_ref_num_get(sd->o_img));
}

static void
_cb_media_play(void *data,
               Evas_Object *_obj EINA_UNUSED,
               const char *_emission EINA_UNUSED,
               const char *_source EINA_UNUSED)
{
   media_play_set(data, EINA_TRUE);
}

static void
_cb_media_pause(void *data,
                Evas_Object *_obj EINA_UNUSED,
                const char *_emission EINA_UNUSED,
                const char *_source EINA_UNUSED)
{
   media_play_set(data, EINA_FALSE);
}

static void
_cb_media_stop(void *data,
               Evas_Object *_obj EINA_UNUSED,
               const char *_emission EINA_UNUSED,
               const char *_source EINA_UNUSED)
{
   media_stop(data);
}

static void
_cb_media_vol(void *data,
              Evas_Object *_obj EINA_UNUSED,
              const char *_emission EINA_UNUSED,
              const char *_source EINA_UNUSED)
{
   double vx, vy;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   edje_object_part_drag_value_get(sd->o_ctrl, "terminology.voldrag", &vx, &vy);
   media_volume_set(data, vx + vy);
}

static void
_cb_media_pos_drag_start(void *data,
                         Evas_Object *_obj EINA_UNUSED,
                         const char *_emission EINA_UNUSED,
                         const char *_source EINA_UNUSED)
{
   double vx, vy;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   sd->pos_drag = 1;
   edje_object_part_drag_value_get(sd->o_ctrl, "terminology.posdrag", &vx, &vy);
   media_position_set(data, vx + vy);
}

static void
_cb_media_pos_drag_stop(void *data,
                        Evas_Object *_obj EINA_UNUSED,
                        const char *_emission EINA_UNUSED,
                        const char *_source EINA_UNUSED)
{
   double pos, len;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd || !sd->pos_drag) return;
   sd->pos_drag = 0;
   len = emotion_object_play_length_get(sd->o_img);
   pos = emotion_object_position_get(sd->o_img);
   pos /= len;
   edje_object_part_drag_value_set(sd->o_ctrl, "terminology.posdrag", pos, pos);
}

static void
_cb_media_pos(void *data,
              Evas_Object *_obj EINA_UNUSED,
              const char *_emission EINA_UNUSED,
              const char *_source EINA_UNUSED)
{
   double vx, vy;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;
   edje_object_part_drag_value_get(sd->o_ctrl, "terminology.posdrag", &vx, &vy);
   media_position_set(data, vx + vy);
}

static int
_type_mov_init(Evas_Object *obj)
{
   Evas_Object *o;
   double vol;
   Media *sd = evas_object_smart_data_get(obj);

   EINA_SAFETY_ON_NULL_RETURN_VAL(sd, -1);

   emotion_init();
   o = sd->o_img = emotion_object_add(evas_object_evas_get(obj));
   evas_object_smart_callback_add(o, "frame_decode",
                                  _cb_mov_frame_decode, obj);
   evas_object_smart_callback_add(o, "frame_resize",
                                  _cb_mov_frame_resize, obj);
   evas_object_smart_callback_add(o, "length_change",
                                  _cb_mov_len_change, obj);
   evas_object_smart_callback_add(o, "decode_stop",
                                  _cb_mov_decode_stop, obj);
   evas_object_smart_callback_add(o, "progress_change",
                                  _cb_mov_progress, obj);
   evas_object_smart_callback_add(o, "ref_change",
                                  _cb_mov_ref, obj);
   emotion_object_file_set(o, sd->realf);
   if (((sd->mode & MEDIA_OPTIONS_MASK) & MEDIA_RECOVER)
       && (sd->type == MEDIA_TYPE_MOV) && (sd->o_img))
     emotion_object_last_position_load(sd->o_img);
   else
     media_position_set(obj, 0.0);
   evas_object_smart_member_add(o, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_raise(sd->o_event);

   o = sd->o_ctrl = edje_object_add(evas_object_evas_get(obj));
   theme_apply(o, sd->config, "terminology/mediactrl",
               NULL, NULL, EINA_FALSE);
   vol = emotion_object_audio_volume_get(sd->o_img);
   edje_object_part_drag_value_set(o, "terminology.voldrag", vol, vol);
   edje_object_signal_callback_add(o, "play", "",
                                   _cb_media_play, obj);
   edje_object_signal_callback_add(o, "pause", "",
                                   _cb_media_pause, obj);
   edje_object_signal_callback_add(o, "stop", "",
                                   _cb_media_stop, obj);
   edje_object_signal_callback_add(o, "pos,drag,start", "",
                                   _cb_media_pos_drag_start, obj);
   edje_object_signal_callback_add(o, "pos,drag,stop", "",
                                   _cb_media_pos_drag_stop, obj);
   edje_object_signal_callback_add(o, "pos,drag", "",
                                   _cb_media_pos, obj);
   edje_object_signal_callback_add(o, "vol,drag", "",
                                   _cb_media_vol, obj);
   evas_object_clip_set(o, sd->clip);
   evas_object_show(o);

   media_play_set(obj, EINA_TRUE);
   if (sd->config->mute)
     media_mute_set(obj, EINA_TRUE);
   if (sd->config->visualize)
     media_visualize_set(obj, EINA_TRUE);

   return 0;
}

static void
_type_mov_calc(Evas_Object *obj, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;

   evas_object_move(sd->o_ctrl, x, y);
   evas_object_resize(sd->o_ctrl, w, h);

   emotion_object_size_get(sd->o_img, &(sd->iw), &(sd->ih));
   if ((w <= 0) || (h <= 0) || (sd->iw <= 0) || (sd->ih <= 0))
     {
        w = 1;
        h = 1;
     }
   else
     {
        int iw = 1, ih = 1;
        double ratio;

        ratio = emotion_object_ratio_get(sd->o_img);
        if (ratio > 0.0) sd->iw = (sd->ih * ratio) + 0.5;
        else ratio = (double)sd->iw / (double)sd->ih;

        if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_BG)
          {
             iw = w;
             ih = w / ratio;
             if (ih < h)
               {
                  ih = h;
                  iw = h * ratio;
                  if (iw < w) iw = w;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_POP)
          {
             iw = w;
             ih = w / ratio;
             if (ih > h)
               {
                  ih = h;
                  iw = h * ratio;
                  if (iw > w) iw = w;
               }
          }
        else if ((sd->mode & MEDIA_SIZE_MASK) == MEDIA_STRETCH)
          {
             iw = w;
             ih = h;
          }
        x += ((w - iw) / 2);
        y += ((h - ih) / 2);
        w = iw;
        h = ih;
     }
   evas_object_move(sd->o_img, x, y);
   evas_object_resize(sd->o_img, w, h);
}

static void _smart_calculate(Evas_Object *obj);

static void
_smart_add(Evas_Object *obj)
{
   Media *sd;
   Evas_Object *o;

   sd = calloc(1, sizeof(Media));
   EINA_SAFETY_ON_NULL_RETURN(sd);
   evas_object_smart_data_set(obj, sd);

   _parent_sc.add(obj);

   o = evas_object_rectangle_add(evas_object_evas_get(obj));
   evas_object_smart_member_add(o, obj);
   sd->clip = o;
   evas_object_color_set(o, 255, 255, 255, 255);
}

static void
_smart_del(Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   if (((sd->mode & MEDIA_OPTIONS_MASK) & MEDIA_SAVE)
       && (sd->type == MEDIA_TYPE_MOV) && (sd->o_img))
     emotion_object_last_position_save(sd->o_img);
   if (sd->url)
     {
        ecore_event_handler_del(sd->url_prog_hand);
        ecore_event_handler_del(sd->url_compl_hand);
        ecore_con_url_free(sd->url);
     }
   sd->url = NULL;
   sd->url_prog_hand = NULL;
   sd->url_compl_hand = NULL;
   if (sd->tmpfd >= 0)
     {
        if (sd->realf) unlink(sd->realf);
        close(sd->tmpfd);
     }
   eina_stringshare_del(sd->src);
   eina_stringshare_del(sd->realf);
   if (sd->clip) evas_object_del(sd->clip);
   if (sd->o_img) evas_object_del(sd->o_img);
   if (sd->o_tmp) evas_object_del(sd->o_tmp);
   if (sd->o_ctrl) evas_object_del(sd->o_ctrl);
   if (sd->o_busy) evas_object_del(sd->o_busy);
   if (sd->o_event) evas_object_del(sd->o_event);
   if (sd->anim) ecore_timer_del(sd->anim);
   if (sd->smooth_timer) sd->smooth_timer = ecore_timer_del(sd->smooth_timer);
   if (sd->restart_job) ecore_job_del(sd->restart_job);
   if ((et_client) && (sd->et_req))
     ethumb_client_thumb_async_cancel(et_client, sd->et_req);
   if (sd->queued) et_queue = eina_list_remove(et_queue, obj);
   sd->et_req = NULL;

   _parent_sc.del(obj);
   free(sd);
}

static void
_smart_resize(Evas_Object *obj, Evas_Coord w, Evas_Coord h)
{
   Media *sd = evas_object_smart_data_get(obj);
   Evas_Coord ox, oy, ow, oh;
   if (!sd) return;
   evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
   if ((ow == w) && (oh == h)) return;
   evas_object_smart_changed(obj);
   evas_object_resize(sd->clip, ow, oh);
}

static Eina_Bool
_unsmooth_timeout(void *data)
{
   Media *sd = evas_object_smart_data_get(data);
   Evas_Coord ox, oy, ow, oh;

   if (!sd) return EINA_FALSE;
   evas_object_geometry_get(data, &ox, &oy, &ow, &oh);
   sd->smooth_timer = NULL;
   sd->nosmooth = EINA_FALSE;
   if ((sd->type == MEDIA_TYPE_IMG) || (sd->type == MEDIA_TYPE_SCALE))
     {
        evas_object_image_smooth_scale_set(sd->o_img, !sd->nosmooth);
        if (sd->o_tmp)
          evas_object_image_smooth_scale_set(sd->o_tmp, !sd->nosmooth);
        if (sd->type == MEDIA_TYPE_SCALE) _type_scale_calc(data, ox, oy, ow, oh);
     }
   else if (sd->type == MEDIA_TYPE_MOV)
     emotion_object_smooth_scale_set(sd->o_img, !sd->nosmooth);
   return EINA_FALSE;
}

static void
_smooth_handler(Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   double interval;

   if (!sd) return;
   interval = ecore_animator_frametime_get();
   if (interval <= 0.0) interval = 1.0/60.0;
   if (!sd->nosmooth)
     {
        if (sd->resizes >= 2)
          {
             sd->nosmooth = EINA_TRUE;
             sd->resizes = 0;
             if ((sd->type == MEDIA_TYPE_IMG) || (sd->type == MEDIA_TYPE_SCALE))
               {
                  evas_object_image_smooth_scale_set(sd->o_img, !sd->nosmooth);
                  if (sd->o_tmp)
                    evas_object_image_smooth_scale_set(sd->o_tmp, !sd->nosmooth);
               }
             else if (sd->type == MEDIA_TYPE_MOV)
               emotion_object_smooth_scale_set(sd->o_img, !sd->nosmooth);
             if (sd->smooth_timer)
               sd->smooth_timer = ecore_timer_del(sd->smooth_timer);
             sd->smooth_timer = ecore_timer_add(interval * 10,
                                                _unsmooth_timeout, obj);
          }
     }
   else
     {
        if (sd->smooth_timer)
          sd->smooth_timer = ecore_timer_del(sd->smooth_timer);
        sd->smooth_timer = ecore_timer_add(interval * 10,
                                           _unsmooth_timeout, obj);
     }
}

static void
_smart_calculate(Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   Evas_Coord ox, oy, ow, oh;

   if (!sd) return;
   evas_object_geometry_get(obj, &ox, &oy, &ow, &oh);
   if ((ow != sd->w) || (oh != sd->h)) sd->resizes++;
   else sd->resizes = 0;
   _smooth_handler(obj);
   sd->w = ow;
   sd->h = oh;
   switch (sd->type)
     {
      case MEDIA_TYPE_RECT: return; /* NOT HANDLED HERE */
      case MEDIA_TYPE_IMG: _type_img_calc(obj, ox, oy, ow, oh); break;
      case MEDIA_TYPE_SCALE: _type_scale_calc(obj, ox, oy, ow, oh); break;
      case MEDIA_TYPE_EDJE: _type_edje_calc(obj, ox, oy, ow, oh); break;
      case MEDIA_TYPE_MOV: _type_mov_calc(obj, ox, oy, ow, oh); break;
      case MEDIA_TYPE_THUMB: _type_thumb_calc(obj, ox, oy, ow, oh); break;
      case MEDIA_TYPE_UNKNOWN: return;
     }
   evas_object_move(sd->clip, ox, oy);
   evas_object_resize(sd->clip, ow, oh);
   if (sd->o_busy)
     {
        evas_object_move(sd->o_busy, ox, oy);
        evas_object_resize(sd->o_busy, ow, oh);
     }
   if (sd->o_event)
     {
        evas_object_move(sd->o_event, ox, oy);
        evas_object_resize(sd->o_event, ow, oh);
     }
}

static void
_smart_move(Evas_Object *obj,
            Evas_Coord _x EINA_UNUSED,
            Evas_Coord _y EINA_UNUSED)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return;
   evas_object_smart_changed(obj);
}

static Eina_Bool
_url_prog_cb(void *data,
             int _type EINA_UNUSED,
             void *event_info)
{
   Ecore_Con_Event_Url_Progress *ev = event_info;
   Evas_Object *obj = data;
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return EINA_TRUE;
   if (ev->url_con != sd->url) return EINA_TRUE;
   if (ev->down.total > 0.0)
     {
        double perc;

        if (!sd->downloading)
          edje_object_signal_emit(sd->o_busy, "downloading", "terminology");
        sd->downloading = EINA_TRUE;
        perc = ev->down.now / ev->down.total;
        if (perc != sd->download_perc)
          {
             Edje_Message_Float msg;

             sd->download_perc = perc;
             msg.val = perc;
             edje_object_message_send(sd->o_busy, EDJE_MESSAGE_FLOAT, 1, &msg);
          }
     }
   return EINA_FALSE;
}

static Eina_Bool
_url_compl_cb(void *data,
              int _type EINA_UNUSED,
              void *event_info)
{
   Ecore_Con_Event_Url_Complete *ev = event_info;
   Evas_Object *obj = data;
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return EINA_TRUE;
   if (ev->url_con != sd->url) return EINA_TRUE;


   edje_object_signal_emit(sd->o_busy, "done", "terminology");
   ecore_event_handler_del(sd->url_prog_hand);
   ecore_event_handler_del(sd->url_compl_hand);
   ecore_con_url_free(sd->url);
   sd->url = NULL;
   sd->url_prog_hand = NULL;
   sd->url_compl_hand = NULL;

   switch (sd->type)
     {
      case MEDIA_TYPE_IMG:
         _type_img_init(obj);
         break;
      case MEDIA_TYPE_SCALE:
         _type_scale_init(obj);
         break;
      case MEDIA_TYPE_EDJE:
         _type_edje_init(obj);
         break;
      case MEDIA_TYPE_MOV:
         _type_mov_init(obj);
         break;
      default:
         break;
     }
   evas_object_raise(sd->o_busy);
   evas_object_raise(sd->o_event);
   _smart_calculate(obj);
   return EINA_FALSE;
}

static void
_mouse_down_cb(void *data,
               Evas *_e EINA_UNUSED,
               Evas_Object *_obj EINA_UNUSED,
               void *event)
{
   Evas_Event_Mouse_Down *ev = event;
   Media *sd = evas_object_smart_data_get(data);
   if (!sd) return;

   if (sd->down.down) return;
   if (ev->button != 1) return;
   sd->down.x = ev->canvas.x;
   sd->down.y = ev->canvas.y;
   sd->down.down = EINA_TRUE;
}

static void
_mouse_up_cb(void *data,
             Evas *_e EINA_UNUSED,
             Evas_Object *_obj EINA_UNUSED,
             void *event)
{
   Evas_Event_Mouse_Up *ev = event;
   Media *sd = evas_object_smart_data_get(data);
   Evas_Coord dx, dy;
   if (!sd) return;

   if (!sd->down.down) return;
   sd->down.down = EINA_FALSE;
   dx = abs(ev->canvas.x - sd->down.x);
   dy = abs(ev->canvas.y - sd->down.y);
   if ((dx <= elm_config_finger_size_get()) &&
       (dy <= elm_config_finger_size_get()))
     evas_object_smart_callback_call(data, "clicked", NULL);
}

static void
_smart_init(void)
{
   static Evas_Smart_Class sc;

   evas_object_smart_clipped_smart_set(&_parent_sc);
   sc           = _parent_sc;
   sc.name      = "media";
   sc.version   = EVAS_SMART_CLASS_VERSION;
   sc.add       = _smart_add;
   sc.del       = _smart_del;
   sc.resize    = _smart_resize;
   sc.move      = _smart_move;
   sc.calculate = _smart_calculate;
   _smart = evas_smart_class_new(&sc);
}

Evas_Object *
media_add(Evas_Object *parent, const char *src, const Config *config, int mode,
          Media_Type type)
{
   Evas *e;
   Evas_Object *obj = NULL;
   Media *sd = NULL;

   EINA_SAFETY_ON_NULL_RETURN_VAL(parent, NULL);
   e = evas_object_evas_get(parent);
   if (!e)
     {
        ERR("can not get evas");
        return NULL;
     }

   if (!_smart) _smart_init();
   obj = evas_object_smart_add(e, _smart);
   sd = evas_object_smart_data_get(obj);

   sd->src = eina_stringshare_add(src);
   sd->config = config;
   sd->mode = mode;
   sd->tmpfd = -1;
   sd->type = type;

#if HAVE_MKSTEMPS
   if (link_is_url(sd->src) && (type != MEDIA_TYPE_MOV))
     {
        const char *ext = NULL;
        char *tbuf;

        if (!strncasecmp(sd->src, "www.", 4))
          {
             tbuf = alloca(strlen(sd->src) + 10);
             strcpy(tbuf, "http://");
             strcat(tbuf, sd->src);
          }
        else if (!strncasecmp(sd->src, "ftp.", 4))
          {
             tbuf = alloca(strlen(sd->src) + 10);
             strcpy(tbuf, "ftp://");
             strcat(tbuf, sd->src);
          }
        else
          tbuf = (char *)sd->src;
        if      ((ext = _is_fmt(src, extn_img)))
          sd->ext = ext;
        else if ((ext = _is_fmt(src, extn_scale)))
          sd->ext = ext;
        else if ((ext = _is_fmt(src, extn_edj)))
          sd->ext = ext;
        else if ((ext = _is_fmt(src, extn_mov)))
          sd->ext = ext;
        else
          {
             switch (type)
               {
                case MEDIA_TYPE_IMG:
                   sd->ext = ".png";
                   break;
                case MEDIA_TYPE_SCALE:
                   sd->ext = ".svg";
                   break;
                case MEDIA_TYPE_EDJE:
                   sd->ext = ".edj";
                   break;
                case MEDIA_TYPE_RECT:
                case MEDIA_TYPE_MOV:
                case MEDIA_TYPE_UNKNOWN:
                case MEDIA_TYPE_THUMB:
                   break;
               }
          }
        if (sd->ext)
          {
             char buf[4096];

             snprintf(buf, sizeof(buf), "/tmp/tmngyXXXXXX%s", sd->ext);
             sd->tmpfd = mkstemps(buf, strlen(sd->ext));
             if (sd->tmpfd >= 0)
               {
                  sd->url = ecore_con_url_new(tbuf);
                  if (!sd->url)
                    {
                       unlink(buf);
                       close(sd->tmpfd);
                    }
                  else
                    {
                       ecore_con_url_fd_set(sd->url, sd->tmpfd);
                       if (!ecore_con_url_get(sd->url))
                         {
                            unlink(buf);
                            close(sd->tmpfd);
                            sd->url = NULL;
                         }
                       else
                         {
                            Evas_Object *o;

                            o = sd->o_busy = edje_object_add(evas_object_evas_get(obj));
                            evas_object_smart_member_add(o, obj);
                            theme_apply(o, sd->config, "terminology/mediabusy",
                                        NULL, NULL, EINA_FALSE);
                            evas_object_show(o);
                            edje_object_signal_emit(o, "busy", "terminology");

                            sd->realf = eina_stringshare_add(buf);
                            sd->url_prog_hand = ecore_event_handler_add
                              (ECORE_CON_EVENT_URL_PROGRESS, _url_prog_cb, obj);
                            sd->url_compl_hand = ecore_event_handler_add
                              (ECORE_CON_EVENT_URL_COMPLETE, _url_compl_cb, obj);
                         }
                    }
               }
             else
               {
                  ERR(_("Function %s failed: %s"), "mkstemps()", strerror(errno));
               }
          }
     }
#endif

   if (!sd->url)
     sd->realf = eina_stringshare_add(sd->src);

   if ((mode & MEDIA_SIZE_MASK) == MEDIA_THUMB)
     {
        // XXX: handle sd->url being true?
        if (_type_thumb_init(obj) < 0)
          {
             ERR("failed to init '%s'", src);
             goto err;
          }
     }
   else
     {
        switch (type)
          {
           case MEDIA_TYPE_IMG:
             if (!sd->url && (_type_img_init(obj) < 0))
               {
                  ERR("failed to init '%s'", src);
                  goto err;
               }
             break;
           case MEDIA_TYPE_SCALE:
             if (!sd->url && (_type_scale_init(obj) < 0))
               {
                  ERR("failed to init '%s'", src);
                  goto err;
               }
             break;
           case MEDIA_TYPE_EDJE:
             if (!sd->url && (_type_edje_init(obj) < 0))
               {
                  ERR("failed to init '%s'", src);
                  goto err;
               }
             break;
           case MEDIA_TYPE_MOV:
             if (!sd->url && (_type_mov_init(obj) < 0))
               {
                  ERR("failed to init '%s'", src);
                  goto err;
               }
             break;
           default:
             break;
          }
     }

   sd->o_event = evas_object_rectangle_add(e);
   evas_object_color_set(sd->o_event, 0, 0, 0, 0);
   evas_object_repeat_events_set(sd->o_event, EINA_TRUE);
   evas_object_smart_member_add(sd->o_event, obj);
   evas_object_clip_set(sd->o_event, sd->clip);
   evas_object_show(sd->o_event);
   evas_object_event_callback_add(sd->o_event, EVAS_CALLBACK_MOUSE_DOWN,
                                  _mouse_down_cb, obj);
   evas_object_event_callback_add(sd->o_event, EVAS_CALLBACK_MOUSE_UP,
                                  _mouse_up_cb, obj);

   return obj;

err:
   if (obj)
     evas_object_del(obj);
   return NULL;
}

void
media_mute_set(Evas_Object *obj, Eina_Bool mute)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   emotion_object_audio_mute_set(sd->o_img, mute);
   if (mute)
      edje_object_signal_emit(sd->o_ctrl, "mute,set", "terminology");
   else
      edje_object_signal_emit(sd->o_ctrl, "mute,unset", "terminology");
}

void
media_visualize_set(Evas_Object *obj, Eina_Bool visualize)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   if (visualize)
     {
        /*
         * FIXME: configure visualizing type, not hard coded one
         */
        if (!emotion_object_vis_supported(sd->o_img, EMOTION_VIS_LIBVISUAL_INFINITE))
          ERR(_("Media visualizing is not supported"));
        else
          emotion_object_vis_set(sd->o_img, EMOTION_VIS_LIBVISUAL_INFINITE);
     }
   else
     emotion_object_vis_set(sd->o_img, EMOTION_VIS_NONE);
}

void
media_play_set(Evas_Object *obj, Eina_Bool play)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   emotion_object_play_set(sd->o_img, play);
   if (play)
     {
        evas_object_smart_callback_call(obj, "play", NULL);
        edje_object_signal_emit(sd->o_ctrl, "play,set", "terminology");
     }
   else
     {
        evas_object_smart_callback_call(obj, "pause", NULL);
        edje_object_signal_emit(sd->o_ctrl, "pause,set", "terminology");
     }
}

Eina_Bool
media_play_get(const Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return EINA_FALSE;
   return emotion_object_play_get(sd->o_img);
}

void
media_stop(Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   evas_object_smart_callback_call(obj, "stop", NULL);
   evas_object_del(obj);
}

void
media_position_set(Evas_Object *obj, double pos)
{
   double len;
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   len = emotion_object_play_length_get(sd->o_img);
   emotion_object_position_set(sd->o_img, len * pos);
}

void
media_volume_set(Evas_Object *obj, double vol)
{
   Media *sd = evas_object_smart_data_get(obj);
   if ((!sd) || (sd->type != MEDIA_TYPE_MOV)) return;
   emotion_object_audio_volume_set(sd->o_img, vol);
   edje_object_part_drag_value_set(sd->o_ctrl, "terminology.voldrag", vol, vol);
}

const char *
media_get(const Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return sd->realf;
}

Media_Type
media_src_type_get(const char *src, size_t len)
{
   Media_Type type = MEDIA_TYPE_UNKNOWN;

   if      (extn_matches(src, len, extn_img))   type = MEDIA_TYPE_IMG;
   else if (extn_matches(src, len, extn_scale)) type = MEDIA_TYPE_SCALE;
   else if (extn_matches(src, len, extn_edj))   type = MEDIA_TYPE_EDJE;
   else if (extn_matches(src, len, extn_mov))   type = MEDIA_TYPE_MOV;
   return type;
}

Evas_Object *
media_control_get(const Evas_Object *obj)
{
   Media *sd = evas_object_smart_data_get(obj);
   if (!sd) return NULL;
   return sd->o_ctrl;
}

void
media_unknown_handle(const char *handler, const char *src)
{
   const char *cmd;
   char buf[PATH_MAX];
   char *escaped;
   cmd = "xdg-open";
   escaped = ecore_file_escape_name(src);
   if (!escaped)
     return;
   if (handler && *handler)
     cmd = handler;
   snprintf(buf, sizeof(buf), "%s %s", cmd, escaped);
   free(escaped);
   ecore_exe_run(buf, NULL);
}
