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

#include "clutter-drag-dest.h"

#include "launcher-defines.h"

G_DEFINE_TYPE (ClutterDragServer, clutter_drag_server, G_TYPE_OBJECT);

#define CLUTTER_DRAG_SERVER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CLUTTER_TYPE_DRAG_SERVER, \
  ClutterDragServerPrivate))

struct _ClutterDragServerPrivate
{
  GList *dests;

  ClutterActor *context;
  gpointer data;
  gint startx;
  gint starty;

  ClutterTimeline *time;
  ClutterEffectTemplate *temp;

  guint drag_tag;
  guint drop_tag;
};

enum
{
  DRAG_STARTED,
  DRAG_FINISHED,

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

void
clutter_drag_server_add_drag_dest (ClutterDragServer *server,
                                   ClutterDragDest   *dest)
{
  ClutterDragServerPrivate *priv;

  g_return_if_fail (CLUTTER_IS_DRAG_SERVER (server));
  g_return_if_fail (CLUTTER_IS_DRAG_DEST (dest));
  priv = server->priv;

  priv->dests = g_list_append (priv->dests, dest);
}

static gboolean
on_drag (ClutterActor       *stage, 
         ClutterMotionEvent *event, 
         ClutterDragServer  *server)
{
  ClutterDragServerPrivate *priv;

  g_return_val_if_fail (CLUTTER_IS_DRAG_SERVER (server), FALSE);
  priv = server->priv;

  if (!priv->context)
  {
    return FALSE;
  }

  if (event->modifier_state & CLUTTER_BUTTON1_MASK)
  {
   clutter_actor_set_position (priv->context, event->x, event->y);
  }
  else
  {
    clutter_actor_destroy (priv->context);
    priv->context = NULL;
    clutter_ungrab_pointer ();
    g_signal_handler_disconnect (stage, priv->drag_tag);
    g_signal_handler_disconnect (stage, priv->drop_tag);
  
    g_signal_emit (server, _server_signals[DRAG_FINISHED], 0);
   }

  return TRUE;
}


void 
clutter_drag_server_remove_drag_dest (ClutterDragServer *server,
                                      ClutterDragDest   *dest)
{
  ClutterDragServerPrivate *priv;

  g_return_if_fail (CLUTTER_IS_DRAG_SERVER (server));
  g_return_if_fail (CLUTTER_IS_DRAG_DEST (dest));
  priv = server->priv;

  priv->dests = g_list_remove (priv->dests, dest);
}


static void
finish_drop (ClutterActor *context, ClutterEffectTemplate *temp)
{
  clutter_effect_scale (temp, context, 0.3, 0.3, NULL, NULL);
  clutter_effect_fade (temp, context, 0, 
                       (ClutterEffectCompleteFunc)clutter_actor_destroy, 
                       context);
}

static gboolean
on_drop (ClutterActor       *stage, 
         ClutterButtonEvent *event, 
         ClutterDragServer  *server)
{
  ClutterDragServerPrivate *priv;
  ClutterActor *dest = NULL;
  GList *d;

  g_return_val_if_fail (CLUTTER_IS_DRAG_SERVER (server), FALSE);
  priv = server->priv;

  if (!priv->context)
  {
    g_debug (G_STRLOC " - No context");
    return FALSE;
  }

  g_debug ("Dropped : %d %d\n", event->x, event->y);

  clutter_actor_set_position (priv->context, event->x, event->y);

  dest = clutter_stage_get_actor_at_pos (CLUTTER_STAGE (stage), 
                                         event->x, event->y);
  
  for (d = priv->dests; d; d = d->next)
  {
    ClutterActor *actor = d->data;
    gint x = 0, y = 0; guint width = 0, height = 0;

    if (!CLUTTER_IS_ACTOR (actor))
      continue;

    clutter_actor_get_transformed_position (actor, &x, &y);
    clutter_actor_get_transformed_size (actor, &width, &height);
    
    if (event->x > x && event->x < (x + width))
    {
      if (event->y > y && event->y < (y + height))
      {
        dest = actor;
        break;
      } 
    }
  }
  
  if (!CLUTTER_IS_DRAG_DEST (dest))
  {
    clutter_effect_fade (priv->temp, priv->context, 0, NULL, NULL);
    clutter_effect_move (priv->temp, priv->context, 
                         priv->startx, priv->starty, 
                         (ClutterEffectCompleteFunc)clutter_actor_destroy,
                         priv->context);
  }
  else if (!clutter_drag_dest_drop (CLUTTER_DRAG_DEST (dest), priv->data))
  {
    /* Drop was unsuccessful */
    clutter_effect_fade (priv->temp, priv->context, 0, NULL, NULL);
    clutter_effect_move (priv->temp, priv->context, 
                         priv->startx, priv->starty, 
                         (ClutterEffectCompleteFunc)clutter_actor_destroy,
                         priv->context); 
  }
  else
  {
    /* Drop animation */
    clutter_effect_scale (priv->temp, priv->context, 1.1, 1.1,
                          (ClutterEffectCompleteFunc)finish_drop,
                          priv->temp);
  }
  priv->context = NULL;
  clutter_ungrab_pointer ();
  g_signal_handler_disconnect (stage, priv->drag_tag);
  g_signal_handler_disconnect (stage, priv->drop_tag);
  
  g_signal_emit (server, _server_signals[DRAG_FINISHED], 0);
  return TRUE;
}
/*
 * @context is the visual representaion of the source, it will be destroyed
 * by the server. Must be added to the stage before passing to this function.
 * @udata is whats passed to a ClutterDragDest if the drop is successful.
 */
void 
clutter_drag_server_begin_drag (ClutterDragServer *server,
                                ClutterActor      *context,
                                gpointer           udata)
{
  ClutterDragServerPrivate *priv;
  ClutterActor *stage = clutter_stage_get_default ();

  g_return_if_fail (CLUTTER_IS_DRAG_SERVER (server));
  g_return_if_fail (CLUTTER_IS_ACTOR (context));
  priv = server->priv;

  g_debug ("Begin drag");

  if (CLUTTER_IS_ACTOR (priv->context))
  {
    g_debug (G_STRLOC " - No context");
    clutter_drag_server_finish_drag (server);
  }

  priv->context = context;
  priv->data = udata;

  priv->startx = clutter_actor_get_x (context);
  priv->starty = clutter_actor_get_y (context);

  clutter_grab_pointer (stage);
  priv->drag_tag = g_signal_connect (stage, "motion-event",
                                     G_CALLBACK (on_drag), server);
  priv->drop_tag =g_signal_connect (stage, "button-release-event", 
                                    G_CALLBACK (on_drop), server);
  g_signal_emit (server, _server_signals[DRAG_STARTED], 0);
}

void    
clutter_drag_server_finish_drag (ClutterDragServer *server)
{
  ClutterDragServerPrivate *priv;
  ClutterActor *stage = clutter_stage_get_default ();

  g_return_if_fail (CLUTTER_IS_DRAG_SERVER (server));
  priv = server->priv;

  clutter_actor_destroy (priv->context);
  priv->context = NULL;
 
  clutter_ungrab_pointer ();
  g_signal_handler_disconnect (stage, priv->drag_tag);
  g_signal_handler_disconnect (stage, priv->drop_tag);
  
  g_signal_emit (server, _server_signals[DRAG_FINISHED], 0);
}


/* GObject stuff */
static void
clutter_drag_server_finalize (GObject *object)
{
  ClutterDragServerPrivate *priv;

  priv = CLUTTER_DRAG_SERVER_GET_PRIVATE (object);

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

static void
clutter_drag_server_class_init (ClutterDragServerClass *klass)
{
  GObjectClass        *obj_class = G_OBJECT_CLASS (klass);

  obj_class->finalize = clutter_drag_server_finalize;

	_server_signals[DRAG_STARTED] =
		g_signal_new ("drag-started",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ClutterDragServerClass, drag_started),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, 
			      G_TYPE_NONE, 0);

	_server_signals[DRAG_FINISHED] =
		g_signal_new ("drag-finished",
			      G_OBJECT_CLASS_TYPE (obj_class),
			      G_SIGNAL_RUN_LAST,
			      G_STRUCT_OFFSET (ClutterDragServerClass, drag_finished),
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID, 
			      G_TYPE_NONE, 0);

  g_type_class_add_private (obj_class, sizeof (ClutterDragServerPrivate));
}

static void
clutter_drag_server_init (ClutterDragServer *server)
{
  ClutterDragServerPrivate *priv;
  	
  priv = server->priv = CLUTTER_DRAG_SERVER_GET_PRIVATE (server);

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

ClutterDragServer *
clutter_drag_server_get_default (void)

{
  static ClutterDragServer *drag_server = NULL;

  if (!drag_server)
    drag_server = g_object_new (CLUTTER_TYPE_DRAG_SERVER, 
                       NULL);

  return drag_server;
}
