/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * Copyright 2009 Intel Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <cogl/cogl.h>
#include <cogl/cogl-pango.h>
#include <clutter/clutter.h>
#include <mx/mx.h>

#define DRAGGABLE_TYPE_RECTANGLE        (draggable_rectangle_get_type ())
#define DRAGGABLE_RECTANGLE(obj)        (G_TYPE_CHECK_INSTANCE_CAST ((obj), DRAGGABLE_TYPE_RECTANGLE, DraggableRectangle))
#define DRAGGABLE_IS_RECTANGLE(obj)     (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DRAGGABLE_TYPE_RECTANGLE))

typedef struct _DraggableRectangle      DraggableRectangle;
typedef struct _DraggableRectangleClass DraggableRectangleClass;

struct _DraggableRectangle
{
  ClutterRectangle parent_instance;

  /* Draggable properties */
  guint threshold;

  MxDragAxis axis;

#if 0
  MxDragContainment containment;
#endif
  ClutterActorBox area;
  ClutterActor *actor;

  guint is_enabled : 1;

  gboolean drag_clone;
};

struct _DraggableRectangleClass
{
  ClutterRectangleClass parent_class;
};

enum
{
  PROP_0,

  PROP_DRAG_THRESHOLD,
  PROP_AXIS,
#if 0
  PROP_CONTAINMENT_TYPE,
  PROP_CONTAINMENT_AREA,
#endif
  PROP_ENABLED,
  PROP_ACTOR,
};

static void mx_draggable_iface_init (MxDraggableIface *iface);
static GType draggable_rectangle_get_type ();
G_DEFINE_TYPE_WITH_CODE (DraggableRectangle,
                         draggable_rectangle,
                         CLUTTER_TYPE_RECTANGLE,
                         G_IMPLEMENT_INTERFACE (MX_TYPE_DRAGGABLE,
                                                mx_draggable_iface_init));

static void
draggable_rectangle_drag_begin (MxDraggable       *draggable,
                                gfloat               event_x,
                                gfloat               event_y,
                                gint                 event_button,
                                ClutterModifierType  modifiers)
{
  gfloat x, y;
  ClutterActor *self = CLUTTER_ACTOR (draggable);
  ClutterActor *actor = mx_draggable_get_drag_actor (draggable);
  ClutterActor *stage = clutter_actor_get_stage (self);

  g_debug ("%s: drag of '%s' begin at %.2f, %.2f",
           G_STRLOC,
           clutter_actor_get_name (self),
           event_x,
           event_y);

  if (actor)
    {
      clutter_actor_get_position (self, &x, &y);
      clutter_actor_set_position (actor, x, y);

      clutter_container_add_actor (CLUTTER_CONTAINER (stage), actor);
    }
  else
    actor = self;

  clutter_actor_set_opacity (actor, 224);
  clutter_actor_set_rotation (actor, CLUTTER_Y_AXIS,
                              30.0,
                              clutter_actor_get_width (actor) / 2.0,
                              0,
                              clutter_actor_get_height (actor) / 2.0);
}

static void
draggable_rectangle_drag_motion (MxDraggable *draggable,
                                 gfloat         delta_x,
                                 gfloat         delta_y)
{
  ClutterActor *actor = mx_draggable_get_drag_actor (draggable);

  g_debug ("%s: drag motion of '%s' (dx: %.2f, dy: %.2f)",
           G_STRLOC,
           clutter_actor_get_name (CLUTTER_ACTOR (draggable)),
           delta_x,
           delta_y);

  if (!actor)
    actor = CLUTTER_ACTOR (draggable);

  clutter_actor_move_by (actor, delta_x, delta_y);
}

static void
draggable_rectangle_drag_end (MxDraggable *draggable,
                              gfloat         event_x,
                              gfloat         event_y)
{
  gfloat x, y;
  ClutterActor *self = CLUTTER_ACTOR (draggable);
  ClutterActor *actor = mx_draggable_get_drag_actor (draggable);
  ClutterActor *stage = clutter_actor_get_stage (self);

  g_debug ("%s: drag of '%s' end at %.2f, %.2f",
           G_STRLOC,
           clutter_actor_get_name (CLUTTER_ACTOR (draggable)),
           event_x,
           event_y);

  if (!actor)
    {
      clutter_actor_set_rotation (self, CLUTTER_Y_AXIS,
                                  0.0, 0, 0, 0);
      clutter_actor_set_opacity (self, 255);
      return;
    }

  clutter_actor_get_position (actor, &x, &y);
  clutter_actor_animate (self, CLUTTER_EASE_OUT_QUAD, 150,
                         "x", x,
                         "y", y,
                         NULL);

  clutter_container_remove_actor (CLUTTER_CONTAINER (stage), actor);
}

static void
mx_draggable_iface_init (MxDraggableIface *iface)
{
  iface->drag_begin = draggable_rectangle_drag_begin;
  iface->drag_motion = draggable_rectangle_drag_motion;
  iface->drag_end = draggable_rectangle_drag_end;
}

static void
draggable_rectangle_set_property (GObject      *gobject,
                                  guint         prop_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  DraggableRectangle *rect = DRAGGABLE_RECTANGLE (gobject);

  switch (prop_id)
    {
    case PROP_DRAG_THRESHOLD:
      rect->threshold = g_value_get_uint (value);
      break;

    case PROP_AXIS:
      rect->axis = g_value_get_enum (value);
      break;

#if 0
    case PROP_CONTAINMENT_TYPE:
      rect->containment = g_value_get_enum (value);
      break;

    case PROP_CONTAINMENT_AREA:
      {
        ClutterActorBox *box = g_value_get_boxed (value);

        if (box)
          rect->area = *box;
        else
          memset (&rect->area, 0, sizeof (ClutterActorBox));
      }
      break;
#endif

    case PROP_ENABLED:
      rect->is_enabled = g_value_get_boolean (value);
      if (rect->is_enabled)
        mx_draggable_enable (MX_DRAGGABLE (gobject));
      else
        mx_draggable_disable (MX_DRAGGABLE (gobject));
      break;

    case PROP_ACTOR:
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
draggable_rectangle_get_property (GObject    *gobject,
                                  guint       prop_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  DraggableRectangle *rect = DRAGGABLE_RECTANGLE (gobject);

  switch (prop_id)
    {
    case PROP_DRAG_THRESHOLD:
      g_value_set_uint (value, rect->threshold);
      break;

    case PROP_AXIS:
      g_value_set_enum (value, rect->axis);
      break;

#if 0
    case PROP_CONTAINMENT_TYPE:
      g_value_set_enum (value, rect->containment);
      break;

    case PROP_CONTAINMENT_AREA:
      g_value_set_boxed (value, &rect->area);
      break;
#endif

    case PROP_ENABLED:
      g_value_set_boolean (value, rect->is_enabled);
      break;

    case PROP_ACTOR:
      if (rect->drag_clone)
        g_value_set_object (value, rect->actor);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
draggable_rectangle_paint (ClutterActor *actor)
{
  gfloat text_x, text_y;
  ClutterActorBox box = { 0, };
  gint layout_width, layout_height;
  CoglColor color = { 0, };
  PangoLayout *layout;
  const gchar *name;

  CLUTTER_ACTOR_CLASS (draggable_rectangle_parent_class)->paint (actor);

  name = clutter_actor_get_name (actor);

  clutter_actor_get_allocation_box (actor, &box);

  layout = clutter_actor_create_pango_layout (actor, name);
  pango_layout_get_size (layout, &layout_width, &layout_height);

  text_x = ((box.x2 - box.x1) - (layout_width / 1024)) / 2;
  text_y = ((box.y2 - box.y1) - (layout_height / 1024)) / 2;

  cogl_color_set_from_4ub (&color, 0, 0, 0, 255);
  cogl_pango_render_layout (layout,
                            (int) text_x,
                            (int) text_y,
                            &color, 0);

  g_object_unref (layout);
}

static void
draggable_rectangle_dispose (GObject *object)
{
  DraggableRectangle *rect = DRAGGABLE_RECTANGLE (object);

  if (rect->actor)
    {
      g_object_unref (rect->actor);
      rect->actor = NULL;
    }

  G_OBJECT_CLASS (draggable_rectangle_parent_class)->dispose (object);
}

static void
draggable_rectangle_class_init (DraggableRectangleClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);

  gobject_class->set_property = draggable_rectangle_set_property;
  gobject_class->get_property = draggable_rectangle_get_property;
  gobject_class->dispose = draggable_rectangle_dispose;

  actor_class->paint = draggable_rectangle_paint;

  g_object_class_override_property (gobject_class,
                                    PROP_DRAG_THRESHOLD,
                                    "drag-threshold");
  g_object_class_override_property (gobject_class,
                                    PROP_AXIS,
                                    "axis");
#if 0
  g_object_class_override_property (gobject_class,
                                    PROP_CONTAINMENT_TYPE,
                                    "containment-type");
  g_object_class_override_property (gobject_class,
                                    PROP_CONTAINMENT_AREA,
                                    "containment-area");
#endif
  g_object_class_override_property (gobject_class,
                                    PROP_ENABLED,
                                    "drag-enabled");
  g_object_class_override_property (gobject_class,
                                    PROP_ACTOR,
                                    "drag-actor");
}

static void
draggable_rectangle_init (DraggableRectangle *self)
{
  self->threshold = 0;
  self->axis = 0;
#if 0
  self->containment = MX_DISABLE_CONTAINMENT;
#endif
  self->is_enabled = FALSE;
  self->actor = g_object_ref_sink (clutter_clone_new (CLUTTER_ACTOR (self)));
}

int
main (int argc, char *argv[])
{
  ClutterActor *stage;
  ClutterActor *draggable;
  ClutterColor rect_color = { 204, 204, 204, 255 };

  clutter_init (&argc, &argv);

  stage = clutter_stage_get_default ();
  clutter_stage_set_title (CLUTTER_STAGE (stage), "Draggable Example");
  clutter_actor_set_size (stage, 800, 600);

  draggable = g_object_new (DRAGGABLE_TYPE_RECTANGLE, NULL);
  ((DraggableRectangle*)draggable)->drag_clone = TRUE;
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), draggable);
  clutter_rectangle_set_color (CLUTTER_RECTANGLE (draggable), &rect_color);
  clutter_actor_set_size (draggable, 100, 100);
  clutter_actor_set_position (draggable, 350, 100);
  clutter_actor_set_reactive (draggable, TRUE);
  clutter_actor_set_name (draggable, "h-handle");
  mx_draggable_set_axis (MX_DRAGGABLE (draggable), MX_DRAG_AXIS_X);
  mx_draggable_enable (MX_DRAGGABLE (draggable));

  draggable = g_object_new (DRAGGABLE_TYPE_RECTANGLE, NULL);
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), draggable);
  clutter_rectangle_set_color (CLUTTER_RECTANGLE (draggable), &rect_color);
  clutter_actor_set_size (draggable, 100, 100);
  clutter_actor_set_position (draggable, 350, 300);
  clutter_actor_set_reactive (draggable, TRUE);
  clutter_actor_set_name (draggable, "v-handle");
  mx_draggable_set_axis (MX_DRAGGABLE (draggable), MX_DRAG_AXIS_Y);
  mx_draggable_enable (MX_DRAGGABLE (draggable));

  clutter_actor_show_all (stage);

  clutter_main ();

  return EXIT_SUCCESS;
}
