/* -*- 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/>.
 */

#include "netbook-launcher.h"

#include <Elementary.h>
#include <glib/gi18n.h>
#include <launcher/launcher.h>

static const char _EDJE_GROUP_LAYOUT[] = "e/netbook/launcher/main/preferences/layout";
static const char _EDJE_GROUP_SECTION[] = "e/netbook/launcher/main/preferences/section";
static const char _EDJE_GROUP_GRID[] = "e/netbook/launcher/main/preferences/grid";
static const char _EDJE_PART_CONTENTS[] = "e.box.contents";
static const char _EDJE_PART_SCROLLER[] = "e.swallow.scroller";
static const char _EDJE_PART_LABEL[] = "e.text.label";
static const char _EDJE_PART_SIZE[] = "e.size";

static const char _EDJE_SIG_SRC[] = "e";
static const char _EDJE_SIG_FOCUS_IN[] = "gui,action,focus,in";

static const char _context_key[] = "_nl_preferences";

struct preferences_context
{
  Evas_Object *preferences;
  Evas_Object *edje;
  Evas_Object *scroller;
  Evas_Object *grid;
  Evas_Object *box;
  Eina_List *to_delete;
  struct app_item_common item;
  struct
  {
    Ecore_Event_Handler *apps_changed;
  } handlers;
  struct
  {
    Ecore_Job *resize;
  } job;
};

static inline void
_preferences_context_set(Evas_Object *preferences, const struct preferences_context *ctxt)
{
  evas_object_data_set(preferences, _context_key, ctxt);
}

static inline struct preferences_context *
_preferences_context_get(const Evas_Object *preferences)
{
  return evas_object_data_get(preferences, _context_key);
}

static void
_preferences_section_resized(void *data, Evas *e __UNUSED__, Evas_Object *size, void *ev __UNUSED__)
{
  Evas_Object *section = data;
  Evas_Coord w, h, ow, oh;
  evas_object_geometry_get(size, NULL, NULL, &w, &h);
  evas_object_geometry_get(section, NULL, NULL, &ow, &oh);
  evas_object_resize(section, w, h);
  evas_object_size_hint_min_set(section, w, h);
}

static Evas_Object *
_preferences_section_add(Evas_Object *parent, const char *label)
{
  Evas_Object *section, *ed, *size;
  Evas_Coord w, h;

  section = elm_layout_add(parent);
  if (!section)
  {
    ERR("could not create layout for secion '%s'\n", label);
    return NULL;
  }

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

  edje_object_part_text_set(ed, _EDJE_PART_LABEL, label);

  edje_object_size_min_get(ed, &w, &h);
  if ((w <= 0) && (h <= 0))
    edje_object_size_min_calc(ed, &w, &h);

  evas_object_resize(section, w, h);

  size = (Evas_Object *)edje_object_part_object_get(ed, _EDJE_PART_SIZE);
  evas_object_geometry_get(size, NULL, NULL, &w, &h);
  evas_object_event_callback_add
    (size, EVAS_CALLBACK_RESIZE, _preferences_section_resized, section);

  evas_object_size_hint_align_set(section, -1.0, -1.0);
  evas_object_size_hint_min_set(section, w, h);
  evas_object_resize(section, w, h);
  evas_object_show(section);

  return section;
}

struct preferences_todo_message_category_item
{
  const char *name;
  const char *comment;
  const char *icon_name;
  const char *match_name;
  const char *uid;
};

struct preferences_todo_message_category
{
  char *name;
  unsigned long items_len;
  struct preferences_todo_message_category_item *items;
};

struct preferences_todo_message
{
  struct func_ctxt func_ctxt;
  struct preferences_context *ctxt;
  unsigned long categories_len;
  struct preferences_todo_message_category categories[];
};

static void
_preferences_todo_message_category_item_data_free(struct preferences_todo_message_category_item *item)
{
  free((char *)item->name);
}

/* all allocated in a single memory blob, avoid fragmentation */
static Eina_Bool
_preferences_todo_message_category_item_data_set(struct preferences_todo_message_category_item *item, LauncherApplication *app, unsigned int icon_size)
{
  unsigned int name_len, comment_len, icon_name_len, match_name_len, uid_len;
  const char *name, *comment, *icon_name, *match_name, *uid, *p;

#define DO(x, n)                                        \
  x = launcher_application_get_ ## n(app); if (!x) x = ""
  DO(name, name);
  DO(comment, comment);
  DO(icon_name, icon_name);
  DO(match_name, unique_string);
  DO(uid, desktop_file);
#undef DO

  icon_name = icon_path_get(icon_name, icon_size);
  if (!icon_name)
    icon_name = "";

#define DO(x)                                   \
  x ## _len = strlen(x) + 1
  DO(name);
  DO(comment);
  DO(icon_name);
  DO(match_name);
  DO(uid);
#undef DO

  p = malloc(name_len + comment_len + icon_name_len + match_name_len + uid_len);
  if (!p)
    return 0;

#define DO(x)                                   \
  item->x = p; p += x ## _len
  DO(name);
  DO(comment);
  DO(icon_name);
  DO(match_name);
  DO(uid);
#undef DO

#define DO(x)                                   \
  memcpy((char *)item->x, x, x ## _len)
  DO(name);
  DO(comment);
  DO(icon_name);
  DO(match_name);
  DO(uid);
#undef DO

  return 1;
}

static void
_preferences_todo_message_free(struct preferences_todo_message *msg)
{
  unsigned int i = msg->categories_len;

  if (i == 0)
  {
    free(msg);
    return;
  }

  do
  {
    struct preferences_todo_message_category *cat;

    i--;
    cat = msg->categories + i;

    free(cat->name);
    if (cat->items_len == 0)
      continue;

    do
    {
      cat->items_len--;
      _preferences_todo_message_category_item_data_free
        (cat->items + cat->items_len);
    } while (cat->items_len > 0);
  }
  while (i > 0);

  free(msg);
}

static void
_preferences_clean_to_delete(struct preferences_context *ctxt)
{
  Evas_Object *obj;
  EINA_LIST_FREE(ctxt->to_delete, obj)
    evas_object_del(obj);
}

static void
_preferences_ecore_load(void *data)
{
  struct preferences_todo_message *msg = data;
  struct preferences_context *ctxt = msg->ctxt;
  Evas_Object *ed;
  unsigned int i;

  _preferences_clean_to_delete(ctxt);

  ed = elm_layout_edje_get(ctxt->grid);
  if (!edje_object_part_box_remove_all(ed, _EDJE_PART_CONTENTS, 1))
  {
    ERR("could not clear box\n");
    return;
  }
  evas_object_size_hint_min_set(ctxt->grid, 0, 0);

  for (i = 0; i < msg->categories_len; i++)
  {
    struct preferences_todo_message_category *cat = msg->categories + i;
    Evas_Object *section, *section_ed;
    unsigned int j;

    section = _preferences_section_add(ctxt->grid, cat->name);
    if (!section)
      continue;

    section_ed = elm_layout_edje_get(section);
    ctxt->to_delete = eina_list_prepend(ctxt->to_delete, section);
    edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, section);
    evas_object_smart_calculate(ed); /* XXX fix this bug in Edje BOX */

    for (j = 0; j < cat->items_len; j++)
    {
      struct preferences_todo_message_category_item *item = cat->items + j;
      Evas_Object *it;

      it = app_item_add
        (&ctxt->item, cat->name,
         item->name, item->comment, item->icon_name, item->match_name,
         item->uid, 0);
      if (!it)
        continue;

      ctxt->to_delete = eina_list_prepend(ctxt->to_delete, it);
      edje_object_part_box_append(section_ed, _EDJE_PART_CONTENTS, it);
    }
  }
}

static gint
_preferences_sort(gconstpointer pa, gconstpointer pb)
{
  LauncherCategory *a = (void *)pa, *b = (void *)pb;
  return strcmp(launcher_category_get_name(a), launcher_category_get_name(b));
}

static void
_preferences_glib_load(void *data)
{
  struct preferences_context *ctxt = data;
  struct preferences_todo_message *msg;
  struct preferences_todo_message_category_item *ci;
  GSList *lst, *used, *n;
  unsigned int i, j;

  lst = launcher_menu_get_categories(ctxt->item.menu);
  used = NULL;
  i = 0;
  j = 0;
  for (n = lst; n != NULL; n = n->next)
  {
    LauncherCategory *cat = n->data;
    const char *icon_name;
    unsigned int child_count;

    icon_name = launcher_category_get_icon_name(cat);
    if (!icon_name || !strstr(icon_name, PREFS_MATCH_STR))
      continue;

    child_count = g_slist_length(launcher_category_get_applications(cat));
    if (child_count < 1)
    {
      g_debug("category '%s' is empty, ignored.",
              launcher_category_get_name(cat));
      continue;
    }

    g_debug("using category '%s' (%s) as it matches '%s'.",
            launcher_category_get_name(cat), icon_name, PREFS_MATCH_STR);
    used = g_slist_prepend(used, cat);
    i++;
    j += child_count;
  }

  msg = malloc(sizeof(struct preferences_todo_message) +
               i * sizeof(struct preferences_todo_message_category) +
               j * sizeof(struct preferences_todo_message_category_item));
  if (!msg)
  {
    g_critical("could not allocate preferences_todo_message.");
    return;
  }
  msg->func_ctxt.func = _preferences_ecore_load;
  msg->func_ctxt.free = (void (*)(void*))_preferences_todo_message_free;
  msg->func_ctxt.data = msg;
  msg->ctxt = ctxt;
  msg->categories_len = i;
  ci = (void *)(msg->categories + i);

  used = g_slist_sort(used, _preferences_sort);

  for (i = 0, n = used; n != NULL; n = n->next, i++)
  {
    LauncherCategory *cat = n->data;
    GSList *n2;

    msg->categories[i].name = strdup(launcher_category_get_name(cat));
    msg->categories[i].items_len = 0;
    if (!msg->categories[i].name)
      goto error;
    msg->categories[i].items = ci;

    n2 = launcher_category_get_applications(cat);
    for (;n2 != NULL; n2 = n2->next, ci++, msg->categories[i].items_len++)
    {
      LauncherApplication *app = n2->data;
      if (!_preferences_todo_message_category_item_data_set
          (ci, app, ctxt->item.item.icon_size))
        goto error;
    }
  }

  gstuff_ecore_run(&msg->func_ctxt);
  printf("assemble message with %u categories, %u items total.\n", i, j);
  return;

error:
  g_critical("could not create preferences_todo_message, out of memory?");
  _preferences_todo_message_free(msg);
}

static void
_preferences_resize_job(void *data)
{
  struct preferences_context *ctxt = data;
  Evas_Coord w, h, ow, oh;

  evas_object_size_hint_min_get(ctxt->box, &w, &h);
  evas_object_size_hint_min_get(ctxt->grid, &ow, &oh);
  if ((w != ow) || (h != oh))
    evas_object_size_hint_min_set(ctxt->grid, w, h);
  ctxt->job.resize = NULL;
}

static void
_preferences_box_size_hints_changed(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct preferences_context *ctxt = data;
  if (ctxt->job.resize)
    return;
  ctxt->job.resize = ecore_job_add(_preferences_resize_job, ctxt);
}

static void
_preferences_glib_start(void *data)
{
  struct preferences_context *ctxt = data;
  app_item_common_glib_start(&ctxt->item);
}

static void
_preferences_glib_stop(void *data)
{
  struct preferences_context *ctxt = data;
  app_item_common_glib_stop(&ctxt->item);
  free(ctxt);
}

static void
_preferences_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct preferences_context *ctxt = data;

  _preferences_clean_to_delete(ctxt);

  app_item_common_ecore_stop(&ctxt->item);

  if (ctxt->job.resize)
    ecore_job_del(ctxt->job.resize);

  gstuff_glib_run_simple(_preferences_glib_stop, ctxt);
}

static void
_preferences_focus_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct preferences_context *ctxt = data;
  edje_object_signal_emit(ctxt->edje, _EDJE_SIG_FOCUS_IN, _EDJE_SIG_SRC);
}

Evas_Object *
preferences_add(Evas_Object *parent)
{
  struct preferences_context *ctxt;
  Evas_Object *preferences, *scroller, *grid, *ed, *box;
  const char *s;
  Eina_Bool w = 1, h = 1;

  preferences = elm_layout_add(parent);
  if (!preferences)
  {
    ERR("could not create preferences layout widget.\n");
    return NULL;
  }

  ed = elm_layout_edje_get(preferences);
  if (!elm_layout_file_set(preferences, _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);
    goto error;
  }

  scroller = elm_scroller_add(preferences);
  if (!scroller)
  {
    ERR("could not createpreferences scroller.\n");
    goto error;
  }
  elm_object_style_set(scroller, "preferences");
  elm_layout_content_set(preferences, _EDJE_PART_SCROLLER, scroller);

  grid = elm_layout_add(preferences);
  if (!grid)
  {
    ERR("could not create preferences layout widget.\n");
    goto error;
  }

  ed = elm_layout_edje_get(grid);
  if (!elm_layout_file_set(grid, _nl_prefs.theme, _EDJE_GROUP_GRID))
  {
    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_GRID, errmsg);
    goto error;
  }
  box = (Evas_Object *)edje_object_part_object_get(ed, _EDJE_PART_CONTENTS);
  evas_object_size_hint_weight_set(grid, 1.0, 1.0);
  evas_object_size_hint_align_set(grid, 1.0, 1.0);
  elm_scroller_content_set(scroller, grid);

  ctxt = calloc(1, sizeof(*ctxt));
  if (!ctxt)
  {
    ERR("could not allocate preferences context.\n");
    goto error;
  }
  ctxt->preferences = preferences;
  ctxt->edje = elm_layout_edje_get(preferences);
  ctxt->scroller = scroller;
  ctxt->grid = grid;
  ctxt->box = box;

  _preferences_context_set(preferences, ctxt);

  evas_object_event_callback_add
    (box, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
     _preferences_box_size_hints_changed, ctxt);

  evas_object_event_callback_add
    (preferences, EVAS_CALLBACK_FREE, _preferences_del, ctxt);
  evas_object_event_callback_add
    (preferences, EVAS_CALLBACK_KEY_DOWN, _preferences_focus_key_down, ctxt);

  s = edje_object_data_get(ed, "scroll_horizontal");
  if (s && (strcmp(s, "off") == 0))
      w = 0;
  s = edje_object_data_get(ed, "scroll_vertical");
  if (s && (strcmp(s, "off") == 0))
      h = 0;

  elm_scroller_bounce_set(scroller, w, h);

  app_item_common_ecore_start(&ctxt->item, preferences, scroller, grid);

  gstuff_glib_run_simple(_preferences_glib_start, ctxt);

  return preferences;

error:
  evas_object_del(preferences);
  return NULL;
}

Eina_Bool
preferences_load(Evas_Object *preferences)
{
  struct preferences_context *ctxt = _preferences_context_get(preferences);

  if (!ctxt)
    return 0;

  return gstuff_glib_run_simple(_preferences_glib_load, ctxt);
}
