/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * pikatoolhandlegrid.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * Based on PikaHandleTransformTool
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"

#include "display-types.h"

#include "core/pika-transform-utils.h"
#include "core/pika-utils.h"

#include "widgets/pikawidgets-utils.h"

#include "pikacanvashandle.h"
#include "pikadisplayshell.h"
#include "pikatoolhandlegrid.h"

#include "pika-intl.h"


enum
{
  PROP_0,
  PROP_HANDLE_MODE,
  PROP_N_HANDLES,
  PROP_ORIG_X1,
  PROP_ORIG_Y1,
  PROP_ORIG_X2,
  PROP_ORIG_Y2,
  PROP_ORIG_X3,
  PROP_ORIG_Y3,
  PROP_ORIG_X4,
  PROP_ORIG_Y4,
  PROP_TRANS_X1,
  PROP_TRANS_Y1,
  PROP_TRANS_X2,
  PROP_TRANS_Y2,
  PROP_TRANS_X3,
  PROP_TRANS_Y3,
  PROP_TRANS_X4,
  PROP_TRANS_Y4
};


struct _PikaToolHandleGridPrivate
{
  PikaTransformHandleMode  handle_mode; /* enum to be renamed */

  gint        n_handles;
  PikaVector2 orig[4];
  PikaVector2 trans[4];

  gint     handle;
  gdouble  last_x;
  gdouble  last_y;

  gboolean hover;
  gdouble  mouse_x;
  gdouble  mouse_y;

  PikaCanvasItem *handles[5];
};


/*  local function prototypes  */

static void   pika_tool_handle_grid_constructed    (GObject               *object);
static void   pika_tool_handle_grid_set_property   (GObject               *object,
                                                    guint                  property_id,
                                                    const GValue          *value,
                                                    GParamSpec            *pspec);
static void   pika_tool_handle_grid_get_property   (GObject               *object,
                                                    guint                  property_id,
                                                    GValue                *value,
                                                    GParamSpec            *pspec);

static void   pika_tool_handle_grid_changed        (PikaToolWidget        *widget);
static gint   pika_tool_handle_grid_button_press   (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    guint32                time,
                                                    GdkModifierType        state,
                                                    PikaButtonPressType    press_type);
static void   pika_tool_handle_grid_button_release (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    guint32                time,
                                                    GdkModifierType        state,
                                                    PikaButtonReleaseType  release_type);
static void   pika_tool_handle_grid_motion         (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    guint32                time,
                                                    GdkModifierType        state);
static PikaHit  pika_tool_handle_grid_hit          (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    GdkModifierType        state,
                                                    gboolean               proximity);
static void     pika_tool_handle_grid_hover        (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    GdkModifierType        state,
                                                    gboolean               proximity);
static void     pika_tool_handle_grid_leave_notify (PikaToolWidget        *widget);
static gboolean pika_tool_handle_grid_get_cursor   (PikaToolWidget        *widget,
                                                    const PikaCoords      *coords,
                                                    GdkModifierType        state,
                                                    PikaCursorType        *cursor,
                                                    PikaToolCursorType    *tool_cursor,
                                                    PikaCursorModifier    *modifier);

static gint     pika_tool_handle_grid_get_handle     (PikaToolHandleGrid  *grid,
                                                      const PikaCoords    *coords);
static void     pika_tool_handle_grid_update_hilight (PikaToolHandleGrid  *grid);
static void     pika_tool_handle_grid_update_matrix  (PikaToolHandleGrid  *grid);

static gboolean is_handle_position_valid           (PikaToolHandleGrid    *grid,
                                                    gint                   handle);
static void     handle_micro_move                  (PikaToolHandleGrid    *grid,
                                                    gint                   handle);

static inline gdouble calc_angle               (gdouble ax,
                                                gdouble ay,
                                                gdouble bx,
                                                gdouble by);
static inline gdouble calc_len                 (gdouble a,
                                                gdouble b);
static inline gdouble calc_lineintersect_ratio (gdouble p1x,
                                                gdouble p1y,
                                                gdouble p2x,
                                                gdouble p2y,
                                                gdouble q1x,
                                                gdouble q1y,
                                                gdouble q2x,
                                                gdouble q2y);


G_DEFINE_TYPE_WITH_PRIVATE (PikaToolHandleGrid, pika_tool_handle_grid,
                            PIKA_TYPE_TOOL_TRANSFORM_GRID)

#define parent_class pika_tool_handle_grid_parent_class


static void
pika_tool_handle_grid_class_init (PikaToolHandleGridClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);

  object_class->constructed     = pika_tool_handle_grid_constructed;
  object_class->set_property    = pika_tool_handle_grid_set_property;
  object_class->get_property    = pika_tool_handle_grid_get_property;

  widget_class->changed         = pika_tool_handle_grid_changed;
  widget_class->button_press    = pika_tool_handle_grid_button_press;
  widget_class->button_release  = pika_tool_handle_grid_button_release;
  widget_class->motion          = pika_tool_handle_grid_motion;
  widget_class->hit             = pika_tool_handle_grid_hit;
  widget_class->hover           = pika_tool_handle_grid_hover;
  widget_class->leave_notify    = pika_tool_handle_grid_leave_notify;
  widget_class->get_cursor      = pika_tool_handle_grid_get_cursor;

  g_object_class_install_property (object_class, PROP_HANDLE_MODE,
                                   g_param_spec_enum ("handle-mode",
                                                      NULL, NULL,
                                                      PIKA_TYPE_TRANSFORM_HANDLE_MODE,
                                                      PIKA_HANDLE_MODE_ADD_TRANSFORM,
                                                      PIKA_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_N_HANDLES,
                                   g_param_spec_int ("n-handles",
                                                     NULL, NULL,
                                                     0, 4, 0,
                                                     PIKA_PARAM_READWRITE |
                                                     G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_X1,
                                   g_param_spec_double ("orig-x1",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_Y1,
                                   g_param_spec_double ("orig-y1",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_X2,
                                   g_param_spec_double ("orig-x2",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_Y2,
                                   g_param_spec_double ("orig-y2",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_X3,
                                   g_param_spec_double ("orig-x3",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_Y3,
                                   g_param_spec_double ("orig-y3",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_X4,
                                   g_param_spec_double ("orig-x4",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ORIG_Y4,
                                   g_param_spec_double ("orig-y4",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_X1,
                                   g_param_spec_double ("trans-x1",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_Y1,
                                   g_param_spec_double ("trans-y1",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_X2,
                                   g_param_spec_double ("trans-x2",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_Y2,
                                   g_param_spec_double ("trans-y2",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_X3,
                                   g_param_spec_double ("trans-x3",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_Y3,
                                   g_param_spec_double ("trans-y3",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_X4,
                                   g_param_spec_double ("trans-x4",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_TRANS_Y4,
                                   g_param_spec_double ("trans-y4",
                                                        NULL, NULL,
                                                        -PIKA_MAX_IMAGE_SIZE,
                                                        PIKA_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));
}

static void
pika_tool_handle_grid_init (PikaToolHandleGrid *grid)
{
  grid->private = pika_tool_handle_grid_get_instance_private (grid);
}

static void
pika_tool_handle_grid_constructed (GObject *object)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (object);
  PikaToolWidget            *widget  = PIKA_TOOL_WIDGET (object);
  PikaToolHandleGridPrivate *private = grid->private;
  gint                       i;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  for (i = 0; i < 4; i++)
    {
      private->handles[i + 1] =
        pika_tool_widget_add_handle (widget,
                                     PIKA_HANDLE_CIRCLE,
                                     0, 0,
                                     PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                     PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                                     PIKA_HANDLE_ANCHOR_CENTER);
    }

  pika_tool_handle_grid_changed (widget);
}

static void
pika_tool_handle_grid_set_property (GObject      *object,
                                    guint         property_id,
                                    const GValue *value,
                                    GParamSpec   *pspec)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (object);
  PikaToolHandleGridPrivate *private = grid->private;

  switch (property_id)
    {
    case PROP_HANDLE_MODE:
      private->handle_mode = g_value_get_enum (value);
      break;

    case PROP_N_HANDLES:
      private->n_handles = g_value_get_int (value);
      break;

    case PROP_ORIG_X1:
      private->orig[0].x = g_value_get_double (value);
      break;
    case PROP_ORIG_Y1:
      private->orig[0].y = g_value_get_double (value);
      break;
    case PROP_ORIG_X2:
      private->orig[1].x = g_value_get_double (value);
      break;
    case PROP_ORIG_Y2:
      private->orig[1].y = g_value_get_double (value);
      break;
    case PROP_ORIG_X3:
      private->orig[2].x = g_value_get_double (value);
      break;
    case PROP_ORIG_Y3:
      private->orig[2].y = g_value_get_double (value);
      break;
    case PROP_ORIG_X4:
      private->orig[3].x = g_value_get_double (value);
      break;
    case PROP_ORIG_Y4:
      private->orig[3].y = g_value_get_double (value);
      break;

    case PROP_TRANS_X1:
      private->trans[0].x = g_value_get_double (value);
      break;
    case PROP_TRANS_Y1:
      private->trans[0].y = g_value_get_double (value);
      break;
    case PROP_TRANS_X2:
      private->trans[1].x = g_value_get_double (value);
      break;
    case PROP_TRANS_Y2:
      private->trans[1].y = g_value_get_double (value);
      break;
    case PROP_TRANS_X3:
      private->trans[2].x = g_value_get_double (value);
      break;
    case PROP_TRANS_Y3:
      private->trans[2].y = g_value_get_double (value);
      break;
    case PROP_TRANS_X4:
      private->trans[3].x = g_value_get_double (value);
      break;
    case PROP_TRANS_Y4:
      private->trans[3].y = g_value_get_double (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_tool_handle_grid_get_property (GObject    *object,
                                    guint       property_id,
                                    GValue     *value,
                                    GParamSpec *pspec)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (object);
  PikaToolHandleGridPrivate *private = grid->private;

  switch (property_id)
    {
    case PROP_N_HANDLES:
      g_value_set_int (value, private->n_handles);
      break;

    case PROP_HANDLE_MODE:
      g_value_set_enum (value, private->handle_mode);
      break;

    case PROP_ORIG_X1:
      g_value_set_double (value, private->orig[0].x);
      break;
    case PROP_ORIG_Y1:
      g_value_set_double (value, private->orig[0].y);
      break;
    case PROP_ORIG_X2:
      g_value_set_double (value, private->orig[1].x);
      break;
    case PROP_ORIG_Y2:
      g_value_set_double (value, private->orig[1].y);
      break;
    case PROP_ORIG_X3:
      g_value_set_double (value, private->orig[2].x);
      break;
    case PROP_ORIG_Y3:
      g_value_set_double (value, private->orig[2].y);
      break;
    case PROP_ORIG_X4:
      g_value_set_double (value, private->orig[3].x);
      break;
    case PROP_ORIG_Y4:
      g_value_set_double (value, private->orig[3].y);
      break;

    case PROP_TRANS_X1:
      g_value_set_double (value, private->trans[0].x);
      break;
    case PROP_TRANS_Y1:
      g_value_set_double (value, private->trans[0].y);
      break;
    case PROP_TRANS_X2:
      g_value_set_double (value, private->trans[1].x);
      break;
    case PROP_TRANS_Y2:
      g_value_set_double (value, private->trans[1].y);
      break;
    case PROP_TRANS_X3:
      g_value_set_double (value, private->trans[2].x);
      break;
    case PROP_TRANS_Y3:
      g_value_set_double (value, private->trans[2].y);
      break;
    case PROP_TRANS_X4:
      g_value_set_double (value, private->trans[3].x);
      break;
    case PROP_TRANS_Y4:
      g_value_set_double (value, private->trans[3].y);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_tool_handle_grid_changed (PikaToolWidget *widget)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private = grid->private;
  gint                       i;

  PIKA_TOOL_WIDGET_CLASS (parent_class)->changed (widget);

  for (i = 0; i < 4; i++)
    {
      pika_canvas_handle_set_position (private->handles[i + 1],
                                       private->trans[i].x,
                                       private->trans[i].y);
      pika_canvas_item_set_visible (private->handles[i + 1],
                                    i < private->n_handles);
    }

  pika_tool_handle_grid_update_hilight (grid);
}

static gint
pika_tool_handle_grid_button_press (PikaToolWidget      *widget,
                                    const PikaCoords    *coords,
                                    guint32              time,
                                    GdkModifierType      state,
                                    PikaButtonPressType  press_type)
{
  PikaToolHandleGrid        *grid           = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private        = grid->private;
  gint                       n_handles      = private->n_handles;
  gint                       active_handle  = private->handle - 1;
  PikaCanvasItem            *dragged_handle = NULL;

  switch (private->handle_mode)
    {
    case PIKA_HANDLE_MODE_ADD_TRANSFORM:
      if (n_handles < 4 && active_handle == -1)
        {
          /* add handle */

          PikaMatrix3 *matrix;

          active_handle = n_handles;

          private->trans[active_handle].x = coords->x;
          private->trans[active_handle].y = coords->y;
          private->n_handles++;

          if (! is_handle_position_valid (grid, active_handle))
            {
              handle_micro_move (grid, active_handle);
            }

          /* handle was added, calculate new original position */
          g_object_get (grid,
                        "transform", &matrix,
                        NULL);

          pika_matrix3_invert (matrix);
          pika_matrix3_transform_point (matrix,
                                        private->trans[active_handle].x,
                                        private->trans[active_handle].y,
                                        &private->orig[active_handle].x,
                                        &private->orig[active_handle].y);

          g_free (matrix);

          private->handle = active_handle + 1;

          g_object_notify (G_OBJECT (grid), "n-handles");
        }
      else if (active_handle >= 0 &&
               active_handle < 4)
        {
          /* existing handle is being dragged. don't set dragged_handle for
           * newly-created handles, otherwise their snap offset will be wrong
           */
          dragged_handle = private->handles[private->handle];
        }
      break;

    case PIKA_HANDLE_MODE_MOVE:
      if (active_handle >= 0 &&
          active_handle < 4)
        {
          /* existing handle is being dragged */
          dragged_handle = private->handles[private->handle];
        }
      /* check for valid position and calculating of OX0...OY3 is
       * done on button release
       */
      break;

    case PIKA_HANDLE_MODE_REMOVE:
      if (n_handles > 0      &&
          active_handle >= 0 &&
          active_handle < 4)
        {
          /* remove handle */

          PikaVector2 temp  = private->trans[active_handle];
          PikaVector2 tempo = private->orig[active_handle];
          gint        i;

          n_handles--;
          private->n_handles--;

          for (i = active_handle; i < n_handles; i++)
            {
              private->trans[i] = private->trans[i + 1];
              private->orig[i]  = private->orig[i + 1];
            }

          private->trans[n_handles] = temp;
          private->orig[n_handles]  = tempo;

          g_object_notify (G_OBJECT (grid), "n-handles");
        }
      break;
    }

  /* ensure dragged handles snap to guides based on the handle center, not where
   * the cursor grabbed them
   */
  if (dragged_handle)
    {
      gdouble x, y;

      pika_canvas_handle_get_position (dragged_handle,
                                       &x,
                                       &y);
      pika_tool_widget_set_snap_offsets (widget,
                                         SIGNED_ROUND (x - coords->x),
                                         SIGNED_ROUND (y - coords->y),
                                         0, 0);
    }

  private->last_x = coords->x;
  private->last_y = coords->y;

  return private->handle;
}

static void
pika_tool_handle_grid_button_release (PikaToolWidget        *widget,
                                      const PikaCoords      *coords,
                                      guint32                time,
                                      GdkModifierType        state,
                                      PikaButtonReleaseType  release_type)
{
  PikaToolHandleGrid        *grid          = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private       = grid->private;
  gint                       active_handle = private->handle - 1;

  if (private->handle_mode == PIKA_HANDLE_MODE_MOVE &&
      active_handle >= 0 &&
      active_handle < 4)
    {
      PikaMatrix3 *matrix;

      if (! is_handle_position_valid (grid, active_handle))
        {
          handle_micro_move (grid, active_handle);
        }

      /* handle was moved, calculate new original position */
      g_object_get (grid,
                    "transform", &matrix,
                    NULL);

      pika_matrix3_invert (matrix);
      pika_matrix3_transform_point (matrix,
                                    private->trans[active_handle].x,
                                    private->trans[active_handle].y,
                                    &private->orig[active_handle].x,
                                    &private->orig[active_handle].y);

      g_free (matrix);

      pika_tool_handle_grid_update_matrix (grid);
    }
}

void
pika_tool_handle_grid_motion (PikaToolWidget   *widget,
                              const PikaCoords *coords,
                              guint32           time,
                              GdkModifierType   state)
{
  PikaToolHandleGrid        *grid          = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private       = grid->private;
  gint                       n_handles     = private->n_handles;
  gint                       active_handle = private->handle - 1;
  gdouble                    diff_x        = coords->x - private->last_x;
  gdouble                    diff_y        = coords->y - private->last_y;

  private->mouse_x = coords->x;
  private->mouse_y = coords->y;

  if (active_handle >= 0 && active_handle < 4)
    {
      if (private->handle_mode == PIKA_HANDLE_MODE_MOVE)
        {
          private->trans[active_handle].x += diff_x;
          private->trans[active_handle].y += diff_y;

          /* check for valid position and calculating of OX0...OY3 is
           * done on button release hopefully this makes the code run
           * faster Moving could be even faster if there was caching
           * for the image preview
           */
          pika_canvas_handle_set_position (private->handles[active_handle + 1],
                                           private->trans[active_handle].x,
                                           private->trans[active_handle].y);
        }
      else if (private->handle_mode == PIKA_HANDLE_MODE_ADD_TRANSFORM)
        {
          gdouble angle, angle_sin, angle_cos, scale;
          PikaVector2 fixed_handles[3];
          PikaVector2 oldpos[4];
          PikaVector2 newpos[4];
          gint    i, j;

          for (i = 0, j = 0; i < 4; i++)
            {
              /* Find all visible handles that are not being moved */
              if (i < n_handles && i != active_handle)
                {
                  fixed_handles[j] = private->trans[i];
                  j++;
                }

              newpos[i] = oldpos[i] = private->trans[i];
            }

          newpos[active_handle].x = oldpos[active_handle].x + diff_x;
          newpos[active_handle].y = oldpos[active_handle].y + diff_y;

          switch (n_handles)
            {
            case 1:
              /* move */
              for (i = 0; i < 4; i++)
                {
                  newpos[i].x = oldpos[i].x + diff_x;
                  newpos[i].y = oldpos[i].y + diff_y;
                }
              break;

            case 2:
              /* rotate and keep-aspect-scale */
              scale =
                calc_len (newpos[active_handle].x - fixed_handles[0].x,
                          newpos[active_handle].y - fixed_handles[0].y) /
                calc_len (oldpos[active_handle].x - fixed_handles[0].x,
                          oldpos[active_handle].y - fixed_handles[0].y);

              angle = calc_angle (oldpos[active_handle].x - fixed_handles[0].x,
                                  oldpos[active_handle].y - fixed_handles[0].y,
                                  newpos[active_handle].x - fixed_handles[0].x,
                                  newpos[active_handle].y - fixed_handles[0].y);

              angle_sin = sin (angle);
              angle_cos = cos (angle);

              for (i = 2; i < 4; i++)
                {
                  newpos[i].x =
                    fixed_handles[0].x +
                    scale * (angle_cos * (oldpos[i].x - fixed_handles[0].x) +
                             angle_sin * (oldpos[i].y - fixed_handles[0].y));

                  newpos[i].y =
                    fixed_handles[0].y +
                    scale * (-angle_sin * (oldpos[i].x - fixed_handles[0].x) +
                              angle_cos * (oldpos[i].y - fixed_handles[0].y));
                }
              break;

            case 3:
              /* shear and non-aspect-scale */
              scale = calc_lineintersect_ratio (oldpos[3].x,
                                                oldpos[3].y,
                                                oldpos[active_handle].x,
                                                oldpos[active_handle].y,
                                                fixed_handles[0].x,
                                                fixed_handles[0].y,
                                                fixed_handles[1].x,
                                                fixed_handles[1].y);

              newpos[3].x = oldpos[3].x + scale * diff_x;
              newpos[3].y = oldpos[3].y + scale * diff_y;
              break;
            }

          for (i = 0; i < 4; i++)
            {
              private->trans[i] = newpos[i];
            }

          pika_tool_handle_grid_update_matrix (grid);
        }
    }

  private->last_x = coords->x;
  private->last_y = coords->y;
}

static PikaHit
pika_tool_handle_grid_hit (PikaToolWidget   *widget,
                           const PikaCoords *coords,
                           GdkModifierType   state,
                           gboolean          proximity)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private = grid->private;

  if (proximity)
    {
      gint handle = pika_tool_handle_grid_get_handle (grid, coords);

      switch (private->handle_mode)
        {
        case PIKA_HANDLE_MODE_ADD_TRANSFORM:
          if (handle > 0)
            return PIKA_HIT_DIRECT;
          else
            return PIKA_HIT_INDIRECT;
          break;

        case PIKA_HANDLE_MODE_MOVE:
        case PIKA_HANDLE_MODE_REMOVE:
          if (private->handle > 0)
            return PIKA_HIT_DIRECT;
          break;
        }
    }

  return PIKA_HIT_NONE;
}

static void
pika_tool_handle_grid_hover (PikaToolWidget   *widget,
                             const PikaCoords *coords,
                             GdkModifierType   state,
                             gboolean          proximity)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private = grid->private;
  gchar                     *status  = NULL;

  private->hover   = TRUE;
  private->mouse_x = coords->x;
  private->mouse_y = coords->y;

  private->handle = pika_tool_handle_grid_get_handle (grid, coords);

  if (proximity)
    {
      GdkModifierType extend_mask = pika_get_extend_selection_mask ();
      GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();

      switch (private->handle_mode)
        {
        case PIKA_HANDLE_MODE_ADD_TRANSFORM:
          if (private->handle > 0)
            {
              const gchar *s = NULL;

              switch (private->n_handles)
                {
                case 1:
                  s = _("Click-Drag to move");
                  break;
                case 2:
                  s = _("Click-Drag to rotate and scale");
                  break;
                case 3:
                  s = _("Click-Drag to shear and scale");
                  break;
                case 4:
                  s = _("Click-Drag to change perspective");
                  break;
                }

              status = pika_suggest_modifiers (s,
                                               extend_mask | toggle_mask,
                                               NULL, NULL, NULL);
            }
          else
            {
              if (private->n_handles < 4)
                status = g_strdup (_("Click to add a handle"));
            }
          break;

        case PIKA_HANDLE_MODE_MOVE:
          if (private->handle > 0)
            status = g_strdup (_("Click-Drag to move this handle"));
          break;

        case PIKA_HANDLE_MODE_REMOVE:
          if (private->handle > 0)
            status = g_strdup (_("Click-Drag to remove this handle"));
          break;
        }
    }

  pika_tool_widget_set_status (widget, status);
  g_free (status);

  pika_tool_handle_grid_update_hilight (grid);
}

static void
pika_tool_handle_grid_leave_notify (PikaToolWidget *widget)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private = grid->private;

  private->hover  = FALSE;
  private->handle = 0;

  pika_tool_handle_grid_update_hilight (grid);

  PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
}

static gboolean
pika_tool_handle_grid_get_cursor (PikaToolWidget     *widget,
                                  const PikaCoords   *coords,
                                  GdkModifierType     state,
                                  PikaCursorType     *cursor,
                                  PikaToolCursorType *tool_cursor,
                                  PikaCursorModifier *modifier)
{
  PikaToolHandleGrid        *grid    = PIKA_TOOL_HANDLE_GRID (widget);
  PikaToolHandleGridPrivate *private = grid->private;

  *cursor      = PIKA_CURSOR_CROSSHAIR_SMALL;
  *tool_cursor = PIKA_TOOL_CURSOR_NONE;
  *modifier    = PIKA_CURSOR_MODIFIER_NONE;

  switch (private->handle_mode)
    {
    case PIKA_HANDLE_MODE_ADD_TRANSFORM:
      if (private->handle > 0)
        {
          switch (private->n_handles)
            {
            case 1:
              *tool_cursor = PIKA_TOOL_CURSOR_MOVE;
              break;
            case 2:
              *tool_cursor = PIKA_TOOL_CURSOR_ROTATE;
              break;
            case 3:
              *tool_cursor = PIKA_TOOL_CURSOR_SHEAR;
              break;
            case 4:
              *tool_cursor = PIKA_TOOL_CURSOR_PERSPECTIVE;
              break;
            }
        }
      else
        {
          if (private->n_handles < 4)
            *modifier = PIKA_CURSOR_MODIFIER_PLUS;
          else
            *modifier = PIKA_CURSOR_MODIFIER_BAD;
        }
      break;

    case PIKA_HANDLE_MODE_MOVE:
      if (private->handle > 0)
        *modifier = PIKA_CURSOR_MODIFIER_MOVE;
      else
        *modifier = PIKA_CURSOR_MODIFIER_BAD;
      break;

    case PIKA_HANDLE_MODE_REMOVE:
      if (private->handle > 0)
        *modifier = PIKA_CURSOR_MODIFIER_MINUS;
      else
        *modifier = PIKA_CURSOR_MODIFIER_BAD;
      break;
    }

  return TRUE;
}

static gint
pika_tool_handle_grid_get_handle (PikaToolHandleGrid *grid,
                                  const PikaCoords   *coords)
{
  PikaToolHandleGridPrivate *private = grid->private;
  gint                       i;

  for (i = 0; i < 4; i++)
    {
      if (private->handles[i + 1] &&
          pika_canvas_item_hit (private->handles[i + 1],
                                coords->x, coords->y))
        {
          return i + 1;
        }
    }

  return 0;
}

static void
pika_tool_handle_grid_update_hilight (PikaToolHandleGrid *grid)
{
  PikaToolHandleGridPrivate *private = grid->private;
  gint                       i;

  for (i = 0; i < 4; i++)
    {
      PikaCanvasItem *item = private->handles[i + 1];

      if (item)
        {
          gdouble diameter = PIKA_CANVAS_HANDLE_SIZE_CIRCLE;

          if (private->hover)
            {
              diameter = pika_canvas_handle_calc_size (
                item,
                private->mouse_x,
                private->mouse_y,
                PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
                2 * PIKA_CANVAS_HANDLE_SIZE_CIRCLE);
            }

          pika_canvas_handle_set_size (item, diameter, diameter);
          pika_canvas_item_set_highlight (item, (i + 1) == private->handle);
        }
    }
}

static void
pika_tool_handle_grid_update_matrix (PikaToolHandleGrid *grid)
{
  PikaToolHandleGridPrivate *private = grid->private;
  PikaMatrix3                transform;
  gboolean                   transform_valid;

  pika_matrix3_identity (&transform);
  transform_valid = pika_transform_matrix_generic (&transform,
                                                   private->orig,
                                                   private->trans);

  g_object_set (grid,
                "transform",   &transform,
                "show-guides", transform_valid,
                NULL);
}

/* check if a handle is not on the connection line of two other handles */
static gboolean
is_handle_position_valid (PikaToolHandleGrid *grid,
                          gint                handle)
{
  PikaToolHandleGridPrivate *private = grid->private;
  gint                       i, j, k;

  for (i = 0; i < 2; i++)
    {
      for (j = i + 1; j < 3; j++)
        {
          for (k = j + 1; i < 4; i++)
            {
              if (handle == i ||
                  handle == j ||
                  handle == k)
                {
                  if ((private->trans[i].x -
                       private->trans[j].x) *
                      (private->trans[j].y -
                       private->trans[k].y) ==

                      (private->trans[j].x -
                       private->trans[k].x) *
                      (private->trans[i].y -
                       private->trans[j].y))
                    {
                      return FALSE;
                    }
                }
            }
        }
    }

  return TRUE;
}

/* three handles on a line causes problems.
 * Let's move the new handle around a bit to find a better position */
static void
handle_micro_move (PikaToolHandleGrid *grid,
                   gint                handle)
{
  PikaToolHandleGridPrivate *private = grid->private;
  gdouble                    posx    = private->trans[handle].x;
  gdouble                    posy    = private->trans[handle].y;
  gdouble                    dx, dy;

  for (dx = -0.1; dx < 0.11; dx += 0.1)
    {
      private->trans[handle].x = posx + dx;

      for (dy = -0.1; dy < 0.11; dy += 0.1)
        {
          private->trans[handle].y = posy + dy;

          if (is_handle_position_valid (grid, handle))
            {
              return;
            }
        }
    }
}

/* finds the clockwise angle between the vectors given, 0-2π */
static inline gdouble
calc_angle (gdouble ax,
            gdouble ay,
            gdouble bx,
            gdouble by)
{
  gdouble angle;
  gdouble direction;
  gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by));

  angle = acos ((ax * bx + ay * by) / length);
  direction = ax * by - ay * bx;

  return ((direction < 0) ? angle : 2 * G_PI - angle);
}

static inline gdouble
calc_len  (gdouble a,
           gdouble b)
{
  return sqrt (a * a + b * b);
}

/* imagine two lines, one through the points p1 and p2, the other one
 * through the points q1 and q2. Find the intersection point r.
 * Calculate (distance p1 to r)/(distance p2 to r)
 */
static inline gdouble
calc_lineintersect_ratio (gdouble p1x, gdouble p1y,
                          gdouble p2x, gdouble p2y,
                          gdouble q1x, gdouble q1y,
                          gdouble q2x, gdouble q2y)
{
  gdouble denom, u;

  denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y);
  if (denom == 0.0)
    {
      /* u is infinite, so u/(u-1) is 1 */
      return 1.0;
    }

  u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x);
  u /= denom;

  return u / (u - 1);
}


/*  public functions  */

PikaToolWidget *
pika_tool_handle_grid_new (PikaDisplayShell *shell,
                           gdouble           x1,
                           gdouble           y1,
                           gdouble           x2,
                           gdouble           y2)
{
  g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);

  return g_object_new (PIKA_TYPE_TOOL_HANDLE_GRID,
                       "shell",       shell,
                       "x1",          x1,
                       "y1",          y1,
                       "x2",          x2,
                       "y2",          y2,
                       "clip-guides", TRUE,
                       NULL);
}
