/* -*- Mode: C; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * Copyright (C) 2009 Canonical Ltd.
 * Authors:
 *  Gustavo Sverzut Barbieri <gustavo.barbieri@canonical.com>
 *
 * This file is part of Netbook Launcher EFL.
 *
 * Netbook Launcher EFL is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * Netbook Launcher EFL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Netbook Launcher EFL.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * DESIGN NOTE:
 *
 * Icons are loaded in a separate thread by
 * evas_object_image_preload(), this should avoid sluggish animations
 * while big images or slow loaders (SVG!) do their work.
 *
 * To help animations be smooth, images will be loaded in a queue,
 * only one per time (image must finish preload before new is
 * requested). If icons are hidden they will be removed from preload
 * queue (preload is suspended), if they are made visible again they
 * will be readded to queue (preload is resumed). This is done with an
 * animator (ecore_frametime seconds delay) to be even nicer with UI.
 *
 * Theme will get signals gui,action,loading and gui,action,loaded
 * telling the preload has started and finished in order to show
 * special state or "loading" throbber.
 */

#include "netbook-launcher.h"
#include <Elementary.h>

static const char _EDJE_GROUP_LAYOUT[] = "e/netbook/launcher/main/icon_async";
static const char _EDJE_PART_ICON[] = "e.swallow.icon";

static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_EMPTY[] = "gui,action,empty";
static const char _EDJE_SIG_LOADING[] = "gui,action,loading";
static const char _EDJE_SIG_LOADED[] = "gui,action,loaded";

static const char _icon_key[] = "_nl_icon_async";

struct icon_async_context
{
  Evas_Object *layout;
  Evas_Object *edje;
  Evas_Object *icon;
  const char *file;
  Evas_Coord w, h;
  Eina_Bool pending;
  Eina_Bool pending_suspended;
};

static void
_icon_async_context_set(Evas_Object *obj, struct icon_async_context *ctxt)
{
  evas_object_data_set(obj, _icon_key, ctxt);
}

static struct icon_async_context *
_icon_async_context_get(const Evas_Object *obj)
{
  return evas_object_data_get(obj, _icon_key);
}

static Eina_List *_queue = NULL;
static Ecore_Animator *_queue_animator = NULL;

static int
_icon_async_queue_delayed_preload(void *data __UNUSED__)
{
  struct icon_async_context *ctxt;

  if (!_queue)
    goto end;

  ctxt = _queue->data;
  DBG("request preload of %p '%s'\n", ctxt->layout, ctxt->file);
  evas_object_image_preload(ctxt->icon, 0);

end:
#ifdef EVAS_PRELOAD_BUG_FIXED
  _queue_animator = NULL;
  return 0;
#else
  return 1;
#endif
}

static void
_icon_async_queue_preloader_stop(void)
{
  if (!_queue_animator)
    return;
  ecore_animator_del(_queue_animator);
  _queue_animator = NULL;
}

static void
_icon_async_queue_preloader_start(void)
{
  if (_queue_animator)
    return;
  _queue_animator = ecore_animator_add(_icon_async_queue_delayed_preload, NULL);
}

static void
_icon_async_queue_del(const struct icon_async_context *ctxt)
{
  _queue = eina_list_remove(_queue, ctxt);
  if (!_queue)
  {
    DBG("icon async queue is now empty!\n");
    _icon_async_queue_preloader_stop();
    return;
  }

  _icon_async_queue_preloader_start();
}

static void
_icon_async_queue_add(const struct icon_async_context *ctxt)
{
  _queue = eina_list_append(_queue, ctxt);
  _icon_async_queue_preloader_start();
}

static Eina_Bool
_icon_async_reload(struct icon_async_context *ctxt)
{
  if (ctxt->w < 1)
  {
    DBG("no icon width set.\n");
    goto error;
  }
  if (ctxt->h < 1)
  {
    DBG("no icon height set.\n");
    goto error;
  }
  if (!ctxt->file)
  {
    DBG("no file set.\n");
    goto error;
  }

  if (ctxt->pending)
  {
    _icon_async_queue_del(ctxt);
    evas_object_image_preload(ctxt->icon, 1);
  }

  ctxt->pending = 1;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_LOADING, _EDJE_SIG_SRC);

  evas_object_image_load_size_set(ctxt->icon, ctxt->w, ctxt->h);
  evas_object_image_file_set(ctxt->icon, ctxt->file, NULL);
  if (evas_object_image_load_error_get(ctxt->icon))
  {
    ERR("could not load image: %s, error #%d\n", ctxt->file,
        evas_object_image_load_error_get(ctxt->icon));
    goto error;
  }

  _icon_async_queue_add(ctxt);

  return 1;

error:
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_EMPTY, _EDJE_SIG_SRC);
  return 0;
}

static void
_icon_async_preloaded(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  struct icon_async_context *ctxt = data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_LOADED, _EDJE_SIG_SRC);
  ctxt->pending = 0;
  _icon_async_queue_del(ctxt);
}

static void
_icon_async_resize(void *data __UNUSED__, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  // TODO: if icon is SVG, reload using new size hint.
  //////// do this using a timer to prevent reloading icon during animation
}

static void
_icon_async_del(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  struct icon_async_context *ctxt = data;
  if (ctxt->pending && !ctxt->pending_suspended)
    _icon_async_queue_del(ctxt);
  free(ctxt);
}

static void
_icon_async_hide(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  struct icon_async_context *ctxt = data;
  if (!ctxt->pending)
    return;
  DBG("icon was hidden before it was preloaded, suspend preload: %p '%s'.\n",
      ctxt->layout, ctxt->file);
  ctxt->pending_suspended = 1;
  _icon_async_queue_del(ctxt);
}

static void
_icon_async_show(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
{
  struct icon_async_context *ctxt = data;
  if (!ctxt->pending)
    return;
  if (!ctxt->pending_suspended) /* already in queue */
    return;
  DBG("icon was show but its preload was suspended, queue preload: %p '%s'.\n",
      ctxt->layout, ctxt->file);
  ctxt->pending_suspended = 0;
  _icon_async_queue_add(ctxt);
}

Evas_Object *
icon_async_add(Evas_Object *parent)
{
  Evas_Object *layout, *ed, *icon;
  struct icon_async_context *ctxt;

  layout = elm_layout_add(parent);
  if (!layout)
  {
    ERR("cannot not create async icon layout.\n");
    return NULL;
  }

  ed = elm_layout_edje_get(layout);
  if (!elm_layout_file_set(layout, _nl_prefs.theme, _EDJE_GROUP_LAYOUT))
  {
    int err = edje_object_load_error_get(ed);
    const char *errmsg = edje_load_error_str(err);
    ERR("cannot load theme '%s', group '%s': %s\n",
        _nl_prefs.theme, _EDJE_GROUP_LAYOUT, errmsg);
    evas_object_del(layout);
    return NULL;
  }

  icon = evas_object_image_filled_add(evas_object_evas_get(layout));
  if (!icon)
  {
    ERR("cannot create icon object.\n");
    evas_object_del(layout);
    return NULL;
  }
  elm_layout_content_set(layout, _EDJE_PART_ICON, icon);
  evas_object_image_smooth_scale_set(icon, 1);

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("cannot allocate async_icon_context.\n");
    evas_object_del(layout);
    return NULL;
  }

  ctxt->layout = layout;
  ctxt->edje = ed;
  ctxt->icon = icon;

  _icon_async_context_set(layout, ctxt);
  evas_object_event_callback_add
    (layout, EVAS_CALLBACK_FREE, _icon_async_del, ctxt);
  evas_object_event_callback_add
    (layout, EVAS_CALLBACK_SHOW, _icon_async_show, ctxt);
  evas_object_event_callback_add
    (layout, EVAS_CALLBACK_HIDE, _icon_async_hide, ctxt);
  evas_object_event_callback_add
    (icon, EVAS_CALLBACK_RESIZE, _icon_async_resize, ctxt);
  evas_object_event_callback_add
    (icon, EVAS_CALLBACK_IMAGE_PRELOADED, _icon_async_preloaded, ctxt);

  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_EMPTY, _EDJE_SIG_SRC);

  return layout;
}

Eina_Bool
icon_async_size_set(Evas_Object *icon, Evas_Coord w, Evas_Coord h)
{
  struct icon_async_context *ctxt = _icon_async_context_get(icon);
  if (!ctxt)
  {
    ERR("no icon_async_context in object %p\n", icon);
    return 0;
  }
  if ((ctxt->w == w) && (ctxt->h == h))
    return 1;
  ctxt->w = w;
  ctxt->h = h;
  return _icon_async_reload(ctxt);
}

Eina_Bool
icon_async_file_set(Evas_Object *icon, const char *file)
{
  struct icon_async_context *ctxt = _icon_async_context_get(icon);
  if (!ctxt)
  {
    ERR("no icon_async_context in object %p\n", icon);
    return 0;
  }

  if (!eina_stringshare_replace(&ctxt->file, file))
    return 1;
  return _icon_async_reload(ctxt);
}
