/*
* 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-iconview.h"

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

#include "clutter-focus-group.h"

#include "launcher-app.h"
#include "launcher-config.h"
#include "launcher-defines.h"
#include "launcher-icon.h"
#include "launcher-util.h"

#include "tidy-texture-frame.h"

static void clutter_focus_group_iface_init (ClutterFocusGroupIface *iface);

G_DEFINE_TYPE_WITH_CODE (LauncherIconview,
                       launcher_iconview, 
                       TIDY_TYPE_VIEWPORT,
                       G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_FOCUS_GROUP,
                                            clutter_focus_group_iface_init));


#define LAUNCHER_ICONVIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
LAUNCHER_TYPE_ICONVIEW, \
LauncherIconviewPrivate))

struct _LauncherIconviewPrivate
{
ClutterActor *grid;

GList *icons;

ClutterActor *scroll;
ClutterActor *focused;

TidyAdjustment *adjust;
};


gboolean
launcher_iconview_scroll_event (ClutterActor       *actor,
                              ClutterScrollEvent *event,
                              LauncherIconview   *view)
{
TidyAdjustment *adjust;
LauncherConfig *cfg = launcher_config_get_default ();
gdouble diff = cfg->icon_height;
gdouble lower, upper;

if (!LAUNCHER_IS_ICONVIEW (view)) return FALSE;
adjust = view->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 gboolean
on_trough_clicked (ClutterActor *scroll, 
                   ClutterButtonEvent *event, 
                   LauncherIconview *view)
{
  TidyAdjustment *adjust;
  LauncherConfig *cfg = launcher_config_get_default ();
  gdouble diff = cfg->icon_height;
  gdouble lower, upper;
 
  if (!LAUNCHER_IS_ICONVIEW (view)) return FALSE;
  adjust = view->priv->adjust;

  if (!TIDY_IS_ADJUSTMENT (adjust))
    return TRUE;

  diff = event->y - clutter_actor_get_y (scroll);

  tidy_adjustment_get_values (adjust, NULL, &lower, &upper, NULL, NULL, NULL);
  diff = upper * (diff/clutter_actor_get_width (scroll));

  tidy_adjustment_set_value (adjust, diff);

  return TRUE;
}

void           
launcher_iconview_set_category (LauncherIconview     *view,
                                LauncherMenuCategory *cat)
{
  LauncherIconviewPrivate *priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  ClutterActor *scroll, *tex, *frame;
  TidyAdjustment *adjustment;
  GList *l;
  gint xoffset, yoffset, i = 0, rows = 0;
  gint view_width = cfg->iconview_width;
  gint view_height = cfg->iconview_height;
  gint view_padding = cfg->iconview_padding;
  gint icon_width = cfg->icon_width;
  gint icon_height = cfg->icon_height;
  gint app_cols = cfg->iconview_cols;
  gint app_rows = cfg->iconview_rows;
  gint app_count = 0;

  g_return_if_fail (LAUNCHER_IS_ICONVIEW (view));
  priv = view->priv;

  priv->grid = tidy_viewport_new ();
  clutter_container_add_actor (CLUTTER_CONTAINER (view), priv->grid);
  clutter_actor_set_position (priv->grid, 0, 0);
  clutter_actor_show (priv->grid);
  clutter_actor_set_reactive (priv->grid, TRUE);

  app_count = app_cols *app_rows;

  xoffset = -view_padding *2;
  yoffset = view_padding;

  for (l = launcher_menu_category_get_applications (cat); l; l = l->next)
  {
    LauncherMenuApplication *app = l->data;
    ClutterActor *actor;
    
    actor = launcher_icon_new ();
    launcher_icon_set_application (LAUNCHER_ICON (actor), app);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), actor);
    clutter_actor_set_position (actor, xoffset, yoffset);
    clutter_actor_show (actor);

    priv->icons = g_list_append (priv->icons, actor);
        
    if (i == (app_cols -1))
    {
      i = 0;
      xoffset = - view_padding;
      yoffset += icon_height + view_padding;
      rows++;
    }
    else
    {
      xoffset += icon_width + view_padding;
      i++;
    }
  }
  clutter_actor_set_height (CLUTTER_ACTOR (priv->grid), 
                            (rows + 1) * icon_height);
  clutter_actor_set_clip (CLUTTER_ACTOR (view), 0, 3,
                          view_width,
                          view_height-3);

  /* Add the scroll bar */
  if (g_list_length (launcher_menu_category_get_applications (cat)) > app_count)
  {
    tidy_scrollable_get_adjustments (TIDY_SCROLLABLE (priv->grid), 
                                     NULL, &adjustment);
    priv->adjust = adjustment;
    tidy_adjustment_set_values (adjustment, 
                                0,
                                0, (rows+1) * (icon_height + view_padding),
                                icon_height + view_padding,
                                app_cols * icon_width,
                                app_rows * icon_height);

    tex = launcher_util_texture_new_from_file (PKGDATADIR"/scrollbar.svg");
    clutter_actor_set_opacity (tex, 0);
    clutter_container_add_actor (CLUTTER_CONTAINER (priv->grid), tex);
    
    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_iconview_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_iconview_scroll_event), adjustment);
    clutter_actor_set_reactive (CLUTTER_ACTOR (scroll), TRUE);
    g_signal_connect (scroll, "scroll-event",
                      G_CALLBACK (launcher_iconview_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 (view), scroll);
    clutter_actor_set_size (scroll, view_height*0.97, 
                            cfg->tablet_mode ? 16 :10);
    clutter_actor_set_anchor_point_from_gravity (scroll, 
                                                 CLUTTER_GRAVITY_CENTER);
    clutter_actor_set_position (scroll, 
                                view_width-(cfg->tablet_mode ? 10 : 5 ),
                                view_height/2);
  
    clutter_actor_set_rotation (scroll, CLUTTER_Z_AXIS, 90, 0, 0, 0);
    clutter_actor_show (scroll);

    clutter_actor_set_reactive (CLUTTER_ACTOR (view), TRUE);
    g_signal_connect (view, "scroll-event",
                      G_CALLBACK (launcher_iconview_scroll_event), view);
    g_signal_connect (priv->grid, "scroll-event",
                      G_CALLBACK (launcher_iconview_scroll_event), view);
    g_signal_connect (priv->scroll, "button-press-event", 
                      G_CALLBACK (on_trough_clicked), view);
  }
}

/*
 * FOCUS STUFF
 */
static void
launcher_iconview_set_focus (ClutterFocusGroup *group, gboolean has_focus)
{
  LauncherIconviewPrivate *priv = LAUNCHER_ICONVIEW (group)->priv;

  if (has_focus)
  {
    priv->focused = priv->icons->data;
    launcher_icon_set_focus (LAUNCHER_ICON (priv->focused), TRUE);
  }
  else
  {
    launcher_icon_set_focus (LAUNCHER_ICON (priv->focused), FALSE);
  }
}

static gboolean
launcher_iconview_direction_event (ClutterFocusGroup     *group,
                                 ClutterFocusDirection  dir)
{
  LauncherIconviewPrivate *priv = LAUNCHER_ICONVIEW (group)->priv;
  LauncherConfig *cfg = launcher_config_get_default ();
  LauncherIcon *new;
  GList *children, *c;
  gint current;
  gint next;

  /* 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 = priv->icons;
  current = next = g_list_index (children, priv->focused);
 
  switch (dir)
  {
    case CLUTTER_DIRECTION_LEFT:
      next -= 1;
      break;
    case CLUTTER_DIRECTION_RIGHT:
      next += 1;
      break;
    case CLUTTER_DIRECTION_UP:
      next -= cfg->iconview_rows;
      break;
    case CLUTTER_DIRECTION_DOWN:
      next += cfg->iconview_rows;
      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_icon_set_focus (new, TRUE);

  for (c = children; c; c= c->next)
  {
    LauncherIcon *icon = c->data;

    if (icon != new)
      launcher_icon_set_focus (icon, FALSE);
  }

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

    y = tidy_adjustment_get_value (priv->adjust);
    pos = cfg->icon_height * (next/cfg->iconview_rows);
    page = cfg->iconview_rows * cfg->icon_height;

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

  return TRUE;
}

static gboolean
launcher_iconview_action_event (ClutterFocusGroup *group)
{
  LauncherIconviewPrivate *priv = LAUNCHER_ICONVIEW (group)->priv;

  launcher_icon_launch (LAUNCHER_ICON (priv->focused));

  return TRUE;
}

static gboolean
launcher_iconview_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_iconview_set_focus;
  iface->direction_event = launcher_iconview_direction_event;
  iface->key_event       = launcher_iconview_key_event;
  iface->action_event    = launcher_iconview_action_event;
}

/*
 * /FOCUS STUFF
 */
 
/* GObject stuff */
static void
launcher_iconview_finalize (GObject *object)
{
  LauncherIconviewPrivate *priv = LAUNCHER_ICONVIEW (object)->priv;

  g_list_free (priv->icons);
  
  G_OBJECT_CLASS (launcher_iconview_parent_class)->finalize (object);
}

static void
launcher_iconview_class_init (LauncherIconviewClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);

  obj_class->finalize = launcher_iconview_finalize;

  g_type_class_add_private (obj_class, sizeof (LauncherIconviewPrivate));
}

      
static void
launcher_iconview_init (LauncherIconview *iconview)
{
  LauncherIconviewPrivate *priv;
  priv = iconview->priv = LAUNCHER_ICONVIEW_GET_PRIVATE (iconview);
}

ClutterActor *
launcher_iconview_new (void)

{
  ClutterActor *iconview = NULL;

  iconview = g_object_new (LAUNCHER_TYPE_ICONVIEW, 
                           "sync-adjustments", FALSE,  
                           NULL);

  return iconview;
}
