/*
 * 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 "clutter-focus-manager.h"

G_DEFINE_TYPE (ClutterFocusManager, clutter_focus_manager, CLUTTER_TYPE_ACTOR);

#define CLUTTER_FOCUS_MANAGER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CLUTTER_TYPE_FOCUS_MANAGER, \
  ClutterFocusManagerPrivate))

struct _ClutterFocusManagerPrivate
{
  GList *groups;

  ClutterFocusGroup *active;
};

enum
{
  TYPE_VOID = 0,
  TYPE_DIRECTION,
  TYPE_ACTIVATE,
  TYPE_STRING
};


static gboolean 
on_key_press (ClutterActor        *actor,
              ClutterKeyEvent     *event, 
              ClutterFocusManager *manager)
{
  ClutterFocusManagerPrivate *priv;
  gint keyval = clutter_key_event_symbol (event);
  static gint prevkey = 0;
  gint type = 0;
  ClutterFocusDirection dir = CLUTTER_DIRECTION_NEXT;

  g_return_val_if_fail (CLUTTER_IS_FOCUS_MANAGER (manager), FALSE);
  priv = manager->priv;

  if (g_list_length (priv->groups) == 0)
    return FALSE;

  switch (keyval)
    {
      case CLUTTER_Return:
      case CLUTTER_KP_Enter:
      case CLUTTER_ISO_Enter:
      case CLUTTER_KP_Space:
      case CLUTTER_space:
        type = TYPE_ACTIVATE;
        break;
      case CLUTTER_BackSpace:
      case CLUTTER_Escape:
        break;
      case CLUTTER_Up:
      case CLUTTER_KP_Up:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_UP;
        break;
      case CLUTTER_Down:
      case CLUTTER_KP_Down:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_DOWN;
        break;
      case CLUTTER_Left:
      case CLUTTER_KP_Left:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_LEFT;
        break;
      case CLUTTER_Right:
      case CLUTTER_KP_Right:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_RIGHT;
        break;
      case CLUTTER_Page_Up:
      case CLUTTER_KP_Page_Up:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_PAGE_UP;
        break;
      case CLUTTER_Page_Down:
      case CLUTTER_KP_Page_Down:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_PAGE_DOWN;
        break;
      case CLUTTER_Tab:
      case CLUTTER_KP_Tab:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_NEXT;
        if (prevkey == CLUTTER_Shift_L || prevkey == CLUTTER_Shift_R)
          dir = CLUTTER_DIRECTION_PREV;
        break;
      case CLUTTER_ISO_Left_Tab:
        type = TYPE_DIRECTION;
        dir = CLUTTER_DIRECTION_PREV;
        break;
      default:
        type = TYPE_STRING;
        break;
  }

  prevkey = keyval;

  /* Check we have a valid group */
  if (!CLUTTER_IS_FOCUS_GROUP (priv->active))
  {
    if (type == TYPE_VOID)
      return FALSE;

    priv->active = priv->groups->data;
    clutter_focus_group_set_focus (priv->active, TRUE);
  }

  if (type == TYPE_VOID)
  {
    /* Undo all focuses */
    clutter_focus_group_set_focus (priv->active, FALSE);
    return FALSE;
  }
  else if (type == TYPE_DIRECTION)
  {
    if (dir == CLUTTER_DIRECTION_NEXT)
    {
      gint current, next, length;

      length = g_list_length (priv->groups);
      current = next = g_list_index (priv->groups, priv->active);
      next += 1;
      next = CLAMP (next, 0, length-1);

      if (next == current && next == length-1)
        next = 0;

      clutter_focus_group_set_focus (priv->active, FALSE);
      priv->active = g_list_nth_data (priv->groups, next);
      clutter_focus_group_set_focus (priv->active, TRUE);
    }
    else if (dir == CLUTTER_DIRECTION_PREV)
    {
      gint current, next, length;

      length = g_list_length (priv->groups);
      current = next = g_list_index (priv->groups, priv->active);
      next -= 1;
      next = CLAMP (next, 0, length-1);

      clutter_focus_group_set_focus (priv->active, FALSE);
      priv->active = g_list_nth_data (priv->groups, next);
      clutter_focus_group_set_focus (priv->active, TRUE);
    }
    else
    {
      clutter_focus_group_direction_event (priv->active, dir);
    }
  }
  else if (type == TYPE_ACTIVATE)
  {
    /* Send the activation event */
    clutter_focus_group_action_event (priv->active);
  }
  else
  {
    /* Send a keyboard event */
    //clutter_focus_group_key_event (priv->active, " ");
  }

  return TRUE;
}


/* Public funcs */
static void
notify_destroy (ClutterFocusManager *manager, GObject *dead_pointer)
{
  ClutterFocusManagerPrivate *priv;

  g_return_if_fail (CLUTTER_IS_FOCUS_MANAGER (manager));
  priv = manager->priv;

  priv->groups = g_list_remove (priv->groups, dead_pointer);  
}

void     
clutter_focus_manager_insert_group (ClutterFocusManager *manager,
                                    ClutterFocusGroup   *group,
                                    gint                 i)
{
  ClutterFocusManagerPrivate *priv;

  g_return_if_fail (CLUTTER_IS_FOCUS_MANAGER (manager));
  g_return_if_fail (CLUTTER_IS_FOCUS_GROUP (group));
  g_return_if_fail (i >= 0);
  priv = manager->priv;

  priv->groups = g_list_insert (priv->groups, group, i);

  g_object_weak_ref (G_OBJECT (group), (GWeakNotify)notify_destroy, manager);
}


/* GObject stuff */
static void
clutter_focus_manager_finalize (GObject *object)
{
  ClutterFocusManagerPrivate *priv;

  priv = CLUTTER_FOCUS_MANAGER_GET_PRIVATE (object);

  g_list_free (priv->groups);

  G_OBJECT_CLASS (clutter_focus_manager_parent_class)->finalize (object);
}

static void
clutter_focus_manager_class_init (ClutterFocusManagerClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);

  obj_class->finalize = clutter_focus_manager_finalize;

  g_type_class_add_private (obj_class, sizeof (ClutterFocusManagerPrivate));
}

static void
clutter_focus_manager_init (ClutterFocusManager *server)
{
  ClutterFocusManagerPrivate *priv;
	
  priv = server->priv = CLUTTER_FOCUS_MANAGER_GET_PRIVATE (server);

  priv->groups = NULL;
  priv->active = NULL;

  clutter_grab_keyboard (CLUTTER_ACTOR (server));

  g_signal_connect (server, "key-press-event",  
                    G_CALLBACK (on_key_press), server);
}

ClutterActor *
clutter_focus_manager_get_default (void)

{
  static ClutterActor *focus_manager = NULL;

  if (!focus_manager)
    focus_manager = g_object_new (CLUTTER_TYPE_FOCUS_MANAGER, 
                       NULL);

  return focus_manager;
}
