/* -*- 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/places/layout";
static const char _EDJE_GROUP_SECTION[] = "e/netbook/launcher/main/places/section";
static const char _EDJE_GROUP_GRID[] = "e/netbook/launcher/main/places/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 _places_key[] = "_nl_places";

struct places_context
{
  Evas_Object *places;
  Evas_Object *edje;
  Evas_Object *scroller;
  Evas_Object *grid;
  Evas_Object *box;
  Evas_Object *storage;
  Evas_Object *folders;
  Evas_Object *recent;
  Ecore_Job *resize_job;
  struct kbdnav_pool *nav_pool;
};

static void
_places_context_set(Evas_Object *places, const struct places_context *ctxt)
{
  evas_object_data_set(places, _places_key, ctxt);
}

static struct places_context *
_places_context_get(const Evas_Object *places)
{
  return evas_object_data_get(places, _places_key);
}

static void
_places_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);
}

Evas_Object *
places_section_add(Evas_Object *places, const char *label)
{
  Evas_Object *section, *ed, *size;
  Evas_Coord w, h;

  section = elm_layout_add(places);
  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, _places_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;
}

static void
_places_resize_job(void *data)
{
  struct places_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->resize_job = NULL;
}

static void
_places_box_size_hints_changed(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct places_context *ctxt = data;
  if (ctxt->resize_job)
    return;
  ctxt->resize_job = ecore_job_add(_places_resize_job, ctxt);
}

static void
_places_del(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct places_context *ctxt = data;
  if (ctxt->nav_pool)
    kbdnav_pool_free(ctxt->nav_pool);
  if (ctxt->resize_job)
    ecore_job_del(ctxt->resize_job);
  free(ctxt);
}

static Evas_Object *
_places_item_common_object_get(void *user_data __UNUSED__, void *item_data)
{
  const struct places_item_common *common = item_data;
  return common->obj;
}

static const char *
_places_item_common_id_get(void *user_data __UNUSED__, void *item_data)
{
  const struct places_item_common *common = item_data;
  return common->id;
}

static void
_places_item_common_select(void *user_data, void *item_data)
{
  const struct places_context *ctxt = user_data;
  struct places_item_common *common = item_data;
  places_item_common_select(ctxt->places, common);
}

static void
_places_item_common_unselect(void *user_data, void *item_data)
{
  const struct places_context *ctxt = user_data;
  struct places_item_common *common = item_data;
  places_item_common_unselect(ctxt->places, common);
}

static void
_places_focus_in(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct places_context *ctxt = data;
  kbdnav_pool_current_select(ctxt->nav_pool);
}

static void
_places_focus_out(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info __UNUSED__)
{
  struct places_context *ctxt = data;
  kbdnav_pool_current_unselect(ctxt->nav_pool);
}

static void
_places_key_down(void *data, Evas *e __UNUSED__, Evas_Object *o __UNUSED__, void *event_info)
{
  Evas_Event_Key_Down *ev = event_info;
  struct places_context *ctxt = data;

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

  if (strcmp(ev->keyname, "Up") == 0)
    kbdnav_pool_navigate_up(ctxt->nav_pool);
  else if (strcmp(ev->keyname, "Down") == 0)
    kbdnav_pool_navigate_down(ctxt->nav_pool);
  else if (strcmp(ev->keyname, "Left") == 0)
    kbdnav_pool_navigate_left(ctxt->nav_pool);
  else if (strcmp(ev->keyname, "Right") == 0)
    kbdnav_pool_navigate_right(ctxt->nav_pool);
  else
  {
    struct places_item_common *common;
    common= kbdnav_pool_current_item_get(ctxt->nav_pool);
    if (common)
      common->key_down(common, ev);
  }
}

Evas_Object *
places_add(Evas_Object *parent)
{
  struct places_context *ctxt;
  Evas_Object *places, *scroller, *grid, *ed, *box;
  const char *s;
  Eina_Bool w = 1, h = 1;

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

  ed = elm_layout_edje_get(places);
  if (!elm_layout_file_set(places, _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(places);
  if (!scroller)
  {
    ERR("could not createplaces scroller.\n");
    goto error;
  }
  elm_object_style_set(scroller, "places");
  elm_layout_content_set(places, _EDJE_PART_SCROLLER, scroller);

  grid = elm_layout_add(places);
  if (!grid)
  {
    ERR("could not create places 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 places context.\n");
    goto error;
  }
  ctxt->places = places;
  ctxt->edje = elm_layout_edje_get(places);
  ctxt->scroller = scroller;
  ctxt->grid = grid;
  ctxt->box = box;

  _places_context_set(places, ctxt);
  evas_object_event_callback_add(places, EVAS_CALLBACK_FREE, _places_del, ctxt);

  ctxt->storage = places_storage_add(places);
  if (!ctxt->storage) goto error;
  ctxt->folders = places_folders_add(places);
  if (!ctxt->folders) goto error;
  ctxt->recent = places_recent_add(places);
  if (!ctxt->recent) goto error;

  edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, ctxt->storage);
  edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, ctxt->folders);
  edje_object_part_box_append(ed, _EDJE_PART_CONTENTS, ctxt->recent);

  ctxt->nav_pool = kbdnav_pool_new
    (_places_item_common_object_get, _places_item_common_id_get,
     _places_item_common_select, _places_item_common_unselect, ctxt);
  if (!ctxt->nav_pool)
  {
    ERR("could not create nav_pool\n");
    goto error;
  }
  evas_object_event_callback_add
    (places, EVAS_CALLBACK_FOCUS_IN, _places_focus_in, ctxt);
  evas_object_event_callback_add
    (places, EVAS_CALLBACK_FOCUS_OUT, _places_focus_out, ctxt);
  evas_object_event_callback_add
    (places, EVAS_CALLBACK_KEY_DOWN, _places_key_down, ctxt);

  evas_object_event_callback_add
    (box, EVAS_CALLBACK_CHANGED_SIZE_HINTS,
     _places_box_size_hints_changed, 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);

  return places;

error:
  evas_object_del(places);
  return NULL;
}

Eina_Bool
places_load(Evas_Object *places __UNUSED__)
{
  return 1;
}

Eina_Bool
places_navigation_item_add(Evas_Object *places, struct places_item_common *item)
{
  struct places_context *ctxt = _places_context_get(places);
  if (!ctxt)
  {
    ERR("could not get places %p context.\n", places);
    return 0;
  }

  return kbdnav_pool_item_add(ctxt->nav_pool, item);
}

Eina_Bool
places_navigation_item_del(Evas_Object *places, struct places_item_common *item)
{
  struct places_context *ctxt = _places_context_get(places);
  if (!ctxt)
  {
    ERR("could not get places %p context.\n", places);
    return 0;
  }

  return kbdnav_pool_item_del(ctxt->nav_pool, item);
}

void
places_item_common_select(Evas_Object *places, struct places_item_common *common)
{
  Evas_Coord x, y, w, h, cx, cy;
  const struct places_context *ctxt = _places_context_get(places);

  if (!ctxt)
  {
    ERR("places (%p) is has no context!\n", places);
    return;
  }

  common->select(common);
  evas_object_geometry_get(common->obj, &x, &y, &w, &h);
  evas_object_geometry_get(ctxt->grid, &cx, &cy, NULL, NULL);
  x -= cx;
  y -= cy;
  elm_scroller_region_show(ctxt->scroller, x, y, w, h);
  sound_play(common->obj, "item-selected");
}

void
places_item_common_unselect(Evas_Object *places __UNUSED__, struct places_item_common *common)
{
  common->unselect(common);
}
