/*
 * 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 Gordon Allott <gord.allott@canonical.com>
 *
 */
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include "ctk-dnd.h"
#include "ctk-actor.h"

/* we hold two lists of actors that have registered as sources or dests for
 * dnd, which means we need to loop though them in order to find which actor
 * we want to fire a signal off to. sloooooow. 
 *
 * if this needs to be faster just use a quadtree, super quick!
 */
GSList *dest_actors;
GSList *source_actors;

gfloat cached_cursor_x = 0;
gfloat cached_cursor_y = 0;
GtkWidget *drag_widget = NULL;

/* goes though the given pool of CtkActor's and returns the actors who's 
 * actorbox overlaps the x/y position given
 */
GSList *
ctk_dnd_find_actors (GSList *pool, gint srcx, gint srcy)
{
  GSList *found = NULL;
  GSList *a;

  for (a = pool; a; a=a->next)
    {
      CtkActor *actor = a->data;
      gfloat x, y, w, h;
      ClutterActorBox actorbox;
      
      clutter_actor_get_transformed_position (CLUTTER_ACTOR (actor), 
                                              &x, &y);
      ctk_actor_get_stored_allocation (actor, &actorbox);
      w = clutter_actor_box_get_width (&actorbox);
      h = clutter_actor_box_get_height (&actorbox);

      if ((srcx < x + w && srcx > x) && (srcy < y + h && srcy > y))
        {
          found = g_slist_append (found, actor);
        }
    }  

  return found;
}

void 
ctk_dnd_on_motion_event (ClutterActor *stage, ClutterEvent *event)
{
  ClutterMotionEvent *motion = (ClutterMotionEvent*)event;
  
  cached_cursor_x = motion->x;
  cached_cursor_y = motion->y;
} 

/* intended to be a more abstract proxy to handle the motion events
 * for drag and drop. we can't just "use" the drag-leave events and such
 * because they only trigger on leaving the stage, so we have to fake our
 * own ones
 */
GSList *cached_dragged_actors = NULL;
void ctk_dnd_handle_motion_events (GdkDragContext *context, 
                                   gint x, gint y, guint dtime)
{
  GSList *target_actors;
  GSList *a;
  guint sid_leave, sid_motion;
  gboolean *retval = NULL;

  sid_leave = g_signal_lookup ("drag-leave", CTK_TYPE_ACTOR);
  sid_motion = g_signal_lookup ("drag-motion", CTK_TYPE_ACTOR);

  target_actors = ctk_dnd_find_actors (dest_actors, x, y);

  if (!target_actors)
    {
      // we are not touching any actors. check if there are cached actors
      // if there are, emit the drag-leave signal and then quit asap
    if (cached_dragged_actors)
      {
        for (a = cached_dragged_actors; a; a=a->next)
          {
            CtkActor *actor = a->data;
            if (!CTK_IS_ACTOR (actor))
              {
                dest_actors = g_slist_remove (dest_actors, actor);
                continue;
              }
            g_signal_emit (actor, sid_leave, 0, context, dtime);
          }
        g_slist_free (cached_dragged_actors);
        cached_dragged_actors = NULL;
        g_free (retval);
        return;
      }
    }

  
  // first go though the cached list of actors, if actor is not in the new
  // list, then the mouse cursor has left its position
  if (cached_dragged_actors) 
    {
    for (a = cached_dragged_actors; a; a=a->next)
      {
        CtkActor *actor = a->data;
        if (!CTK_IS_ACTOR (actor))
          {
            dest_actors = g_slist_remove (dest_actors, actor);
            continue;
          }

        if (!g_slist_find (dest_actors, actor))
          {
            g_signal_emit (actor, sid_leave, 0, context, dtime);
          }
      }
    }

  // now go though each of the new list of actors and call motion events
  // on them
  
  retval = g_malloc (sizeof (gboolean));
  for (a = target_actors; a; a=a->next) 
    {
      CtkActor *actor = a->data;
      if (!CTK_IS_ACTOR (actor))
        {
          dest_actors = g_slist_remove (dest_actors, actor);
          continue;
        }
 
      g_signal_emit (actor, sid_motion, 0, 
                     context, x, y, dtime,
                     retval);
    }
  g_free (retval);

  // now cache the new list
  g_slist_free (cached_dragged_actors);
  cached_dragged_actors = target_actors;
}

gboolean 
ctk_dnd_drag_motion (GtkWidget *widget,
                     GdkDragContext *context,
                     gint x,
                     gint y,
                     guint dtime)
{
  ctk_dnd_handle_motion_events (context, x, y, dtime);
  return FALSE;
}

gboolean 
ctk_dnd_drag_drop (GtkWidget      *widget,
                   GdkDragContext *context,
                   gint            x,
                   gint            y,
                   guint           dtime)
{
  /* just need to check to make sure that the drop was on a destination
   * actor, if its not, return false 
   * else, fire off the drag drop signal to the actor
   */
  GSList *target_actors;
  GSList *a;
  guint sid_dragdrop;
  gboolean *retval = NULL;

  target_actors = ctk_dnd_find_actors (dest_actors, x, y);

  if (!target_actors) 
    {
      gdk_drag_status (context, 0, dtime);
      return FALSE;
    }

  sid_dragdrop = g_signal_lookup ("drag-drop", CTK_TYPE_ACTOR);
  retval = g_malloc (sizeof (gboolean));

  for (a = target_actors; a; a=a->next)
    {
      CtkActor *actor = a->data;
      if (!CTK_IS_ACTOR (actor))
        {
          dest_actors = g_slist_remove (dest_actors, actor);
          continue;
        }
      
      g_signal_emit (actor, sid_dragdrop, 0, 
                     context, x, y, dtime,
                     retval);

      if (*retval)
        {
          /* we got a truth value from the retval, which means that the dnd
           * was handled
           */
          break;
        }
    }

  return *retval;
}
/* replaces gtk_drag_dest_find_target */
GdkAtom ctk_drag_dest_find_target  (GdkDragContext *context,
                                    GtkTargetList *target_list)
{
  GdkAtom target;
  target = gtk_drag_dest_find_target (drag_widget, context, target_list);
  return target;
}

void
ctk_dnd_drag_data_received (GtkWidget *widget, GdkDragContext *context, 
                            gint x, gint y,
                            GtkSelectionData *selection_data, 
                            guint target_type, guint dtime,
                            gpointer data)
{
  GSList *target_actors;
  GSList *a;
  guint signalid;

  target_actors = ctk_dnd_find_actors (dest_actors, x, y);

  if (!target_actors)
    {
      // we have no targets under us, so we just fail tjhe dnd
      gdk_drag_status (context, 0, dtime);
      return;
    }

  signalid = g_signal_lookup ("drag-data-received", CTK_TYPE_ACTOR);
 
  for (a = target_actors; a; a=a->next)
    {
      CtkActor *actor = a->data;
      if (!CTK_IS_ACTOR (actor))
        {
          dest_actors = g_slist_remove (dest_actors, actor);
          continue;
        }
      
      g_signal_emit (actor, signalid, 0, 
                     context, x, y, selection_data, target_type, dtime);
    }
}

void
ctk_drag_get_data (CtkActor *actor, GdkDragContext *context, 
                   GdkAtom target, guint32 time_)
{
  gtk_drag_get_data (drag_widget, context, target, time_);
}


void 
ctk_dnd_init (GtkWidget *clutter_embed, 
              const GtkTargetEntry *targets,
              gint n_targets)
{
  drag_widget = clutter_embed;
  gtk_drag_dest_set (clutter_embed,
                     GTK_DEST_DEFAULT_MOTION,
                     targets,           
                     n_targets,        
                     GDK_ACTION_PRIVATE|GDK_ACTION_COPY|GDK_ACTION_MOVE);

  g_signal_connect (clutter_stage_get_default (), "motion-event", 
                    G_CALLBACK (ctk_dnd_on_motion_event), NULL);

  g_signal_connect (clutter_embed, "drag-motion",
                    G_CALLBACK (ctk_dnd_drag_motion), NULL);

  g_signal_connect (clutter_embed, "drag-drop",
                    G_CALLBACK (ctk_dnd_drag_drop), NULL);

  g_signal_connect (clutter_embed, "drag-data-received",
                    G_CALLBACK (ctk_dnd_drag_data_received), NULL);
}

void
ctk_drag_dest_start (CtkActor *widget)
{
  g_return_if_fail (CTK_IS_ACTOR (widget));
  
  if (g_slist_find (dest_actors, widget))
    {
      return;
    }

  dest_actors = g_slist_append (dest_actors, widget);
}

gboolean
ctk_drag_dest_is_dest (CtkActor *widget)
{
  g_return_val_if_fail (CTK_IS_ACTOR (widget), FALSE);
  
  GSList *found = NULL;
  g_slist_find (dest_actors, widget);
  
  if (!found)
    {
      return TRUE;
    }
  return FALSE;
}
  




