/*
 * Copyright (C) 2008 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as 
 * published by the Free Software Foundation.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */

#include "launcher-catbar.h"

#include <string.h>
#include <tidy/tidy.h>

#include "clutter-drag-server.h"
#include "clutter-drag-dest.h"
#include "clutter-focus-group.h"

#include "launcher-config.h"
#include "launcher-defines.h"
#include "launcher-util.h"
#include "launcher-shortcut.h"
#include "launcher-sidebar.h"
#include "launcher-wm.h"

static void clutter_focus_group_iface_init (ClutterFocusGroupIface *iface);

G_DEFINE_TYPE_WITH_CODE (LauncherCatbar, 
                         launcher_catbar, 
                         CLUTTER_TYPE_GROUP,
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_FOCUS_GROUP,
                                              clutter_focus_group_iface_init));

#define LAUNCHER_CATBAR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  LAUNCHER_TYPE_CATBAR, \
  LauncherCatbarPrivate))

struct _LauncherCatbarPrivate
{
  LauncherSidebar *sidebar;
  ClutterActor   *grid;
  ClutterActor   *scroll;
  TidyAdjustment *adjust;

  LauncherMenuCategory *fav;
  LauncherMenuCategory *active;

  ClutterTimeline *time;
  ClutterEffectTemplate *temp;

  ClutterActor *favourite;
  
  ClutterActor *active_shortcut;
  ClutterActor *focused;
};

enum
{
  CAT_SELECTED,

  LAST_SIGNAL
};
static guint _catbar_signals[LAST_SIGNAL] = { 0 };

gint  
launcher_catbar_get_active_category (LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  GList *cats;
  gint ret = 0;

  g_return_val_if_fail (LAUNCHER_IS_CATBAR (bar), 0);
  priv = bar->priv;

  if (priv->active == NULL)
    return 0;

  cats = launcher_menu_get_categories (launcher_menu_get_default ());

  ret = g_list_index (cats, priv->active);

  return ret;
}

void  
launcher_catbar_set_active_category (LauncherCatbar *bar,
                                     gint            n_cat)
{
  LauncherCatbarPrivate *priv;
  GList *cats;
  LauncherShortcut *active;

  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;

  cats = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));

  active = g_list_nth_data (cats, n_cat);

  if (LAUNCHER_IS_SHORTCUT (active))
  {
    launcher_shortcut_clicked (active);
    launcher_shortcut_set_active (active, TRUE);
  }

  g_list_free (cats);
}

void 
launcher_catbar_deactivate (LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  GList *cats, *c;

  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;

  cats = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  for (c = cats; c; c = c->next)
  {
    LauncherShortcut *cut = c->data;
    
    if (LAUNCHER_IS_SHORTCUT (cut))
      launcher_shortcut_set_active (cut, FALSE);
  }
  priv->active = NULL;

  g_list_free (cats);
}

static gboolean
on_category_clicked (LauncherShortcut *hit,
                     LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  LauncherMenuCategory *cat = g_object_get_data (G_OBJECT (hit), "category");
  GList *cats, *c;

  priv = bar->priv;

  if (cat == priv->active)
    return TRUE;

  cats = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  for (c = cats; c; c = c->next)
  {
    LauncherShortcut *cut = c->data;

    if (LAUNCHER_IS_SHORTCUT (cut) && cut != hit)
    {
      launcher_shortcut_set_active (cut, FALSE);
    }
  }

  g_list_free (cats);

  bar->priv->active = cat;
  bar->priv->active_shortcut = CLUTTER_ACTOR (hit);

  if (!cfg->is_portrait)
    launcher_sidebar_show_trash (priv->sidebar,
                               (priv->active == priv->fav) ? TRUE:FALSE);
  
  g_signal_emit (bar, _catbar_signals[CAT_SELECTED], 0, cat);
    
  return TRUE;
}

static void
on_drag_started (ClutterDragServer *server, LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  GList *actors, *a;
  gint i = 0;

  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;

  actors = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  for (a = actors; a; a = a->next)
  {
    ClutterActor *actor = a->data;

    if (i && actor != priv->favourite)
      clutter_effect_fade (priv->temp, actor, 50, NULL, NULL);

    i++;
  }
  g_list_free (actors);
}

static void
on_drag_finished (ClutterDragServer *server, LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  GList *actors, *a;
  gint i = 0;

  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;

  actors = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  for (a = actors; a; a = a->next)
  {
    ClutterActor *actor = a->data;

    clutter_effect_fade (priv->temp, actor, 255, NULL, NULL);

    i++;
  }
  g_list_free (actors);

}

/* GObject stuff */
static void
launcher_catbar_class_init (LauncherCatbarClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);

	_catbar_signals[CAT_SELECTED] =
		g_signal_new ("category-selected",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (LauncherCatbarClass, category_selected),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__POINTER, 
			      G_TYPE_NONE,1, G_TYPE_POINTER);

  g_type_class_add_private (obj_class, sizeof (LauncherCatbarPrivate));
}

static ClutterActor *
_append_category (LauncherCatbar       *bar, 
                  LauncherMenuCategory *category,
                  gint                  width,
                  gint                  height,
                  gint                  offset)
{
  ClutterActor *cat;

  cat = launcher_shortcut_new (launcher_menu_category_get_icon_name (category),
                               launcher_menu_category_get_name (category),
                               FALSE);
  clutter_container_add_actor (CLUTTER_CONTAINER (bar->priv->grid), cat);
  clutter_actor_set_position (cat, 0, offset);
  clutter_actor_show_all (cat);

  g_object_set_data (G_OBJECT (cat), "category", category);
  g_signal_connect (cat, "clicked", 
                    G_CALLBACK (on_category_clicked), bar);

  return cat;
}

static gboolean
on_drag_drop (ClutterDragDest *dest, gpointer data)
{
  LauncherMenuApplication *app = data;
  LauncherMenuCategory *fav;
  GList *cats, *favs, *f;
  const gchar *exec;

  if (!app)
    return FALSE;

  exec = launcher_menu_application_get_exec (app);

  cats = launcher_menu_get_categories (launcher_menu_get_default ());
  fav = cats->data;

  if (!fav)
    return FALSE;

  favs = launcher_menu_category_get_applications (fav);
  for (f = favs; f; f = f->next)
  {
    LauncherMenuApplication *favapp = f->data;
    const gchar *e = launcher_menu_application_get_exec (favapp);

    if (exec && e && strcmp (exec, e) == 0)
    {
      g_debug ("Favourite already exists");
      return FALSE;
    }
  }

  launcher_util_create_favorite (app);
  clutter_drag_dest_finish (dest, TRUE);

  return TRUE;
}

static void
on_desktop_shown (LauncherWm *wm, LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;

  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;
}

gboolean
launcher_catbar_scroll_event (ClutterActor       *actor,
                              ClutterScrollEvent *event,
                              LauncherCatbar     *bar)
{
  TidyAdjustment *adjust;
  LauncherConfig *cfg = launcher_config_get_default ();
  gdouble diff = cfg->shortcut_height;
  gdouble lower, upper;

  if (!LAUNCHER_IS_CATBAR (bar)) return TRUE;
  adjust = bar->priv->adjust;

  if (!TIDY_IS_ADJUSTMENT (adjust))
    return TRUE;

  if (event->direction == CLUTTER_SCROLL_UP)
    diff = -cfg->icon_height;

  diff += tidy_adjustment_get_value (adjust);

  tidy_adjustment_get_values (adjust, NULL, &lower, &upper, NULL, NULL, NULL);

  diff = CLAMP (diff, lower, upper);

  tidy_adjustment_set_value (adjust, diff);

  return TRUE;
}

static void
on_menu_changed (LauncherMenu *menu, LauncherCatbar *bar)
{
  LauncherCatbarPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  LauncherMenuCategory *old_cat = NULL;
  TidyAdjustment *adjustment;
  ClutterActor *scroll, *frame, *tex;
  gint bar_width = cfg->bar_width;
  gint bar_height = cfg->shortcut_height;
	GList *l, *childs, *c;
  gint i = 0;
  
  g_return_if_fail (LAUNCHER_IS_CATBAR (bar));
  priv = bar->priv;

  childs = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  for (c = childs; c; c = c->next)
  {
    clutter_actor_destroy (c->data);
  }
  g_list_free (childs);
  
  for (l = launcher_menu_get_categories (menu); l; l = l->next)
  {
    LauncherMenuCategory *cat = l->data;
    ClutterActor *shortcut;
    const gchar *icon_name;

    shortcut = _append_category (bar, cat, 
                                 bar_width, bar_height,
                                 (bar_height * i));
    if (cat == priv->active)
    {
      old_cat = cat;
      launcher_shortcut_set_active (LAUNCHER_SHORTCUT (shortcut), TRUE);
      priv->active_shortcut = shortcut;
    }
    
    icon_name = launcher_menu_category_get_icon_name (cat);
    if (icon_name && FAVCATNAME && strcmp (icon_name, FAVCATNAME)==0)
    {
      clutter_drag_dest_enable (CLUTTER_DRAG_DEST (shortcut));
      g_signal_connect (shortcut, "drop", 
                      G_CALLBACK (on_drag_drop), NULL);      
      priv->fav = cat;
      priv->favourite = shortcut;
    }
    i++;
  }
  clutter_actor_show_all (priv->grid);
    
  clutter_actor_set_height (priv->grid, i * bar_height);

  if (i > N_CATS-2)
  {
    tidy_scrollable_get_adjustments (TIDY_SCROLLABLE (priv->grid), 
                                     NULL, &adjustment);
    priv->adjust = adjustment;
    tidy_adjustment_set_values (adjustment, 
                                0,
                                0, i * bar_height,
                                bar_height,
                                1 * bar_width,
                                (N_CATS-2) * bar_height);

    if (CLUTTER_IS_ACTOR (priv->scroll))
    {
      clutter_actor_destroy (priv->scroll);
    }

    tex = launcher_util_texture_new_from_file (PKGDATADIR"/scrollbar.svg");
    clutter_actor_set_opacity (tex, 0);
        
    frame = tidy_texture_frame_new (CLUTTER_TEXTURE (tex), 5, 5, 5, 5);
    clutter_actor_set_reactive (CLUTTER_ACTOR (frame), TRUE);
    g_signal_connect (frame, "scroll-event",
                      G_CALLBACK (launcher_catbar_scroll_event), adjustment); 
        
    scroll = priv->scroll = tidy_scroll_bar_new_with_handle (adjustment, frame);
    
    frame = tidy_texture_frame_new (CLUTTER_TEXTURE (tex), 5, 5, 5, 5);
    clutter_actor_set_reactive (CLUTTER_ACTOR (frame), TRUE);
    g_signal_connect (frame, "scroll-event",
                      G_CALLBACK (launcher_catbar_scroll_event), adjustment);
    clutter_actor_set_reactive (CLUTTER_ACTOR (scroll), TRUE);
    g_signal_connect (scroll, "scroll-event",
                      G_CALLBACK (launcher_catbar_scroll_event), adjustment);
    clutter_actor_set_opacity (frame, 100);
    tidy_scroll_bar_set_texture (TIDY_SCROLL_BAR (scroll), frame);

    clutter_container_add_actor (CLUTTER_CONTAINER (bar), scroll);
    clutter_actor_set_size (scroll, bar_height * (N_CATS-2), 5);
    clutter_actor_set_anchor_point_from_gravity (scroll, 
                                                 CLUTTER_GRAVITY_CENTER);
    clutter_actor_set_position (scroll, 
                                bar_width- 10/2,
                                (bar_height*(N_CATS-2))/2);
    clutter_actor_set_opacity (scroll, 100);
    clutter_actor_set_rotation (scroll, CLUTTER_Z_AXIS, 90, 0, 0, 0);
    clutter_actor_show (scroll);
  }
  else if (priv->scroll)
  {
    clutter_actor_set_opacity (priv->scroll, 0);
  }

  if (!old_cat)
  {
    priv->active = priv->fav;
    priv->active_shortcut = priv->favourite;
    launcher_shortcut_set_active (LAUNCHER_SHORTCUT (priv->favourite), TRUE);
    launcher_sidebar_show_trash (priv->sidebar, TRUE);
  }

  g_signal_emit (bar, _catbar_signals[CAT_SELECTED], 0, bar->priv->active);
  clutter_actor_queue_redraw (CLUTTER_ACTOR (bar));
}

/*
 * FOCUS STUFF
 */
static void
launcher_catbar_set_focus (ClutterFocusGroup *group, gboolean has_focus)
{
  LauncherCatbarPrivate *priv = LAUNCHER_CATBAR (group)->priv;

  if (has_focus)
  {
    priv->focused = priv->active_shortcut;
  }
  else
  {
    if (priv->focused != priv->active_shortcut)
      launcher_shortcut_set_active (LAUNCHER_SHORTCUT (priv->focused), FALSE);
  }
}

static gboolean
launcher_catbar_direction_event (ClutterFocusGroup     *group,
                                 ClutterFocusDirection  dir)
{
  LauncherCatbarPrivate *priv = LAUNCHER_CATBAR (group)->priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  LauncherShortcut *new;
  GList *children, *c;
  gint bar_height = cfg->shortcut_height;
  gint current;
  gint next;

  switch (dir)
  {
    case CLUTTER_DIRECTION_RIGHT:
    case CLUTTER_DIRECTION_LEFT:
      return TRUE;
    default:
      ;
  }
  /* Move to the next or previous category, 
   *  if category is first or last, return FALSE
   *  page up and page down takes you to top and bottom
   */
  children = clutter_container_get_children (CLUTTER_CONTAINER (priv->grid));
  current = next = g_list_index (children, priv->focused);

  switch (dir)
  {
    case CLUTTER_DIRECTION_UP:
      next -= 1;
      break;
    case CLUTTER_DIRECTION_DOWN:
      next += 1;
      break;
    case CLUTTER_DIRECTION_PAGE_UP:
      next = 0;
      break;
    case CLUTTER_DIRECTION_PAGE_DOWN:
      next = g_list_length (children) -1;
      break;
    default:
      break;
  }
  if (next < 0) next = 0;
  next = CLAMP (next, 0, g_list_length (children)-1);
  new = g_list_nth_data (children, next);
  priv->focused = CLUTTER_ACTOR (new);
  launcher_shortcut_set_active (new, TRUE);

  for (c = children; c; c= c->next)
  {
    LauncherShortcut *shortcut = c->data;

    if (shortcut == LAUNCHER_SHORTCUT (priv->active_shortcut)
        || shortcut == new)
      ;
    else if (LAUNCHER_IS_SHORTCUT (shortcut))
      launcher_shortcut_set_active (shortcut, FALSE);
  }

  if (TIDY_IS_ADJUSTMENT (priv->adjust))
  {
    gint y, pos, page;

    y = tidy_adjustment_get_value (priv->adjust);
    pos = bar_height * (next);
    page = (N_CATS-2) * bar_height;

    if (pos >= (y + page))
    {
      tidy_adjustment_set_value (priv->adjust, pos);
    }
    else if (pos < y)
    {
      tidy_adjustment_set_value (priv->adjust, pos);
    }
  }
  g_list_free (children);

  return TRUE;
}

static gboolean
launcher_catbar_action_event (ClutterFocusGroup *group)
{
  LauncherCatbarPrivate *priv = LAUNCHER_CATBAR (group)->priv;

  on_category_clicked (LAUNCHER_SHORTCUT (priv->focused), 
                       LAUNCHER_CATBAR (group));

  return TRUE;
}

static gboolean
launcher_catbar_key_event (ClutterFocusGroup *group, const gchar *string)
{
  g_debug ("String event: %s", string);

  return TRUE;
}                        

static void
clutter_focus_group_iface_init (ClutterFocusGroupIface *iface)
{
  iface->set_focus       = launcher_catbar_set_focus;
  iface->direction_event = launcher_catbar_direction_event;
  iface->key_event       = launcher_catbar_key_event;
  iface->action_event    = launcher_catbar_action_event;
}

/*
 * /FOCUS STUFF
 */
   
static void
launcher_catbar_init (LauncherCatbar *catbar)
{
  LauncherCatbarPrivate *priv;
  LauncherMenu *menu = launcher_menu_get_default ();
  LauncherConfig *cfg = launcher_config_get_default ();
  ClutterDragServer *server = clutter_drag_server_get_default ();

  priv = catbar->priv = LAUNCHER_CATBAR_GET_PRIVATE (catbar);

  priv->scroll = NULL;
  priv->sidebar = LAUNCHER_SIDEBAR (launcher_sidebar_get_default ());

  priv->grid = tidy_viewport_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (catbar), priv->grid);
  clutter_actor_set_position (priv->grid, 0, 0);
  clutter_actor_show (priv->grid);
  
  on_menu_changed (menu, catbar);
  launcher_sidebar_show_trash (priv->sidebar, TRUE);

  clutter_actor_set_clip (CLUTTER_ACTOR (catbar), 0, 2,
                          cfg->bar_width, cfg->shortcut_height * (N_CATS-2));

  /* Hook up drag server signals */
  g_signal_connect (server, "drag-started",
                    G_CALLBACK (on_drag_started), catbar);
  g_signal_connect (server, "drag-finished",
                    G_CALLBACK (on_drag_finished), catbar);

  /* Hook up menu changes */
  g_signal_connect (menu, "menu-changed",
                    G_CALLBACK (on_menu_changed), catbar);

  /* Hook up scroll events */
  clutter_actor_set_reactive (CLUTTER_ACTOR (catbar), TRUE);
  g_signal_connect (catbar, "scroll-event",
                    G_CALLBACK (launcher_catbar_scroll_event), catbar);
  g_signal_connect (priv->grid, "scroll-event",
                    G_CALLBACK (launcher_catbar_scroll_event), catbar);

  /* Reset when desktop is shown */
  g_signal_connect (launcher_wm_get_default(), "hide-windec",
                    G_CALLBACK (on_desktop_shown), catbar);

  priv->time = clutter_timeline_new_for_duration (MID_TIME);
  priv->temp = clutter_effect_template_new (priv->time, 
                                            clutter_sine_inc_func);
}

ClutterActor *
launcher_catbar_get_default (void)

{
  static ClutterActor *catbar = NULL;

  if (!catbar)
    catbar = g_object_new (LAUNCHER_TYPE_CATBAR, 
                       NULL);

  return catbar;
}
