/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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 <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

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

#include "libgimpmath/gimpmath.h"
#include "libgimpwidgets/gimpwidgets.h"

#include "tools-types.h"

#include "config/gimpguiconfig.h" /* playground */

#include "core/gimp.h" /* playground */

#include "widgets/gimphelp-ids.h"
#include "widgets/gimpwidgets-utils.h"

#include "display/gimpcanvasitem.h"
#include "display/gimpdisplay.h"
#include "display/gimptoolgui.h"

#include "gimphandletransformoptions.h"
#include "gimphandletransformtool.h"
#include "gimptoolcontrol.h"

#include "gimp-intl.h"


/* the transformation is defined by 8 points:
 *
 * 4 points on the original image and 4 corresponding points on the
 * transformed image. The first N_HANDLES points on the transformed
 * image are visible as handles.
 *
 * For these handles, the constants TRANSFORM_HANDLE_N,
 * TRANSFORM_HANDLE_S, TRANSFORM_HANDLE_E and TRANSFORM_HANDLE_W are
 * used. Actually, it makes no sense to name the handles with north,
 * south, east, and west.  But this way, we don't need to define even
 * more enum constants.
 */

/*  index into trans_info array  */
enum
{
  X0,
  Y0,
  X1,
  Y1,
  X2,
  Y2,
  X3,
  Y3,
  OX0,
  OY0,
  OX1,
  OY1,
  OX2,
  OY2,
  OX3,
  OY3,
  N_HANDLES
};


/*  local function prototypes  */

static void   gimp_handle_transform_tool_button_press  (GimpTool              *tool,
                                                        const GimpCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        GimpButtonPressType    press_type,
                                                        GimpDisplay           *display);
static void   gimp_handle_transform_tool_button_release (GimpTool              *tool,
                                                         const GimpCoords      *coords,
                                                         guint32                time,
                                                         GdkModifierType        state,
                                                         GimpButtonReleaseType    release_type,
                                                         GimpDisplay           *display);
static void   gimp_handle_transform_tool_modifier_key   (GimpTool              *tool,
                                                         GdkModifierType        key,
                                                         gboolean               press,
                                                         GdkModifierType        state,
                                                         GimpDisplay           *display);

static void   gimp_handle_transform_tool_dialog         (GimpTransformTool  *tr_tool);
static void   gimp_handle_transform_tool_dialog_update  (GimpTransformTool  *tr_tool);
static void   gimp_handle_transform_tool_prepare        (GimpTransformTool  *tr_tool);
static void   gimp_handle_transform_tool_motion         (GimpTransformTool  *tr_tool);
static void   gimp_handle_transform_tool_recalc_matrix  (GimpTransformTool  *tr_tool);
static gchar *gimp_handle_transform_tool_get_undo_desc  (GimpTransformTool  *tr_tool);
static TransformAction
              gimp_handle_transform_tool_pick_function  (GimpTransformTool  *tr_tool,
                                                         const GimpCoords   *coords,
                                                         GdkModifierType     state,
                                                         GimpDisplay        *display);
static void   gimp_handle_transform_tool_cursor_update  (GimpTransformTool  *tr_tool,
                                                         GimpCursorType     *cursor,
                                                         GimpCursorModifier *modifier);
static void   gimp_handle_transform_tool_draw_gui       (GimpTransformTool  *tr_tool,
                                                         gint                handle_w,
                                                         gint                handle_h);

static gboolean       is_handle_position_valid          (GimpTransformTool  *tr_tool,
                                                         gint                active_handle);
static void           handle_micro_move                 (GimpTransformTool *tr_tool,
                                                         gint               active_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);
static gboolean       mod_gauss                         (gdouble  matrix[],
                                                         gdouble  solution[],
                                                         gint     s);


G_DEFINE_TYPE (GimpHandleTransformTool, gimp_handle_transform_tool,
               GIMP_TYPE_TRANSFORM_TOOL)

#define parent_class gimp_handle_transform_tool_parent_class


void
gimp_handle_transform_tool_register (GimpToolRegisterCallback  callback,
                                     gpointer                  data)
{
  /* we should not know that "data" is a Gimp*, but what the heck this
   * is experimental playground stuff
   */
  if (GIMP_GUI_CONFIG (GIMP (data)->config)->playground_handle_transform_tool)
    (* callback) (GIMP_TYPE_HANDLE_TRANSFORM_TOOL,
                  GIMP_TYPE_HANDLE_TRANSFORM_OPTIONS,
                  gimp_handle_transform_options_gui,
                  GIMP_CONTEXT_PROP_MASK_BACKGROUND,
                  "gimp-handle-transform-tool",
                  _("Handle Transform"),
                  _("Handle Transform Tool: "
                    "Deform the layer, selection or path with handles"),
                  N_("_Handle Transform"), "<ctrl><shift>H",
                  NULL, GIMP_HELP_TOOL_HANDLE_TRANSFORM,
                  GIMP_STOCK_TOOL_HANDLE_TRANSFORM,
                  data);
}

static void
gimp_handle_transform_tool_class_init (GimpHandleTransformToolClass *klass)
{
  GimpToolClass          *tool_class  = GIMP_TOOL_CLASS (klass);
  GimpTransformToolClass *trans_class = GIMP_TRANSFORM_TOOL_CLASS (klass);

  tool_class->button_press   = gimp_handle_transform_tool_button_press;
  tool_class->button_release = gimp_handle_transform_tool_button_release;
  tool_class->modifier_key   = gimp_handle_transform_tool_modifier_key;

  trans_class->dialog        = gimp_handle_transform_tool_dialog;
  trans_class->dialog_update = gimp_handle_transform_tool_dialog_update;
  trans_class->prepare       = gimp_handle_transform_tool_prepare;
  trans_class->motion        = gimp_handle_transform_tool_motion;
  trans_class->recalc_matrix = gimp_handle_transform_tool_recalc_matrix;
  trans_class->get_undo_desc = gimp_handle_transform_tool_get_undo_desc;
  trans_class->pick_function = gimp_handle_transform_tool_pick_function;
  trans_class->cursor_update = gimp_handle_transform_tool_cursor_update;
  trans_class->draw_gui      = gimp_handle_transform_tool_draw_gui;
}

static void
gimp_handle_transform_tool_init (GimpHandleTransformTool *ht_tool)
{
  GimpTransformTool *tr_tool = GIMP_TRANSFORM_TOOL (ht_tool);

  tr_tool->progress_text    = _("Handle transformation");
  tr_tool->use_grid         = TRUE;

  tr_tool->does_perspective = TRUE;

  ht_tool->saved_handle_mode = GIMP_HANDLE_MODE_ADD_TRANSFORM;
}

static void
gimp_handle_transform_tool_button_press (GimpTool            *tool,
                                         const GimpCoords    *coords,
                                         guint32              time,
                                         GdkModifierType      state,
                                         GimpButtonPressType  press_type,
                                         GimpDisplay         *display)
{
  GimpHandleTransformTool    *ht      = GIMP_HANDLE_TRANSFORM_TOOL (tool);
  GimpTransformTool          *tr_tool = GIMP_TRANSFORM_TOOL (tool);
  GimpHandleTransformOptions *options;
  gint                        n_handles;
  gint                        active_handle;

  options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);

  GIMP_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
                                                press_type, display);

  n_handles     = (gint) tr_tool->trans_info[N_HANDLES];
  active_handle = tr_tool->function - TRANSFORM_HANDLE_N;

  switch (options->handle_mode)
    {
    case GIMP_HANDLE_MODE_ADD_TRANSFORM:
      if (n_handles < 4 && tr_tool->function == TRANSFORM_HANDLE_NONE)
        {
          /* add handle */

          GimpMatrix3 matrix;

          gimp_draw_tool_pause (GIMP_DRAW_TOOL (tool));

          active_handle = n_handles;

          tr_tool->trans_info[X0 + 2 * active_handle] = coords->x;
          tr_tool->trans_info[Y0 + 2 * active_handle] = coords->y;
          tr_tool->trans_info[N_HANDLES]++;

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

          /* handle was added, calculate new original position */
          matrix = tr_tool->transform;
          gimp_matrix3_invert (&matrix);
          gimp_matrix3_transform_point (&matrix,
                                        tr_tool->trans_info[X0 + 2 * active_handle],
                                        tr_tool->trans_info[Y0 + 2 * active_handle],
                                        &tr_tool->trans_info[OX0 + 2 * active_handle],
                                        &tr_tool->trans_info[OY0 + 2 * active_handle]);

          /*  this is disgusting: we put the new handle's coordinates
           *  into the prev_trans_info array, because our motion
           *  handler needs them for doing the actual transform; we
           *  can only do this because the values will be ignored by
           *  anything but our motion handler because we don't
           *  increase the N_HANDLES value in prev_trans_info.
           */
          (*tr_tool->prev_trans_info)[X0 + 2 * active_handle] = tr_tool->trans_info[X0 + 2 * active_handle];
          (*tr_tool->prev_trans_info)[Y0 + 2 * active_handle] = tr_tool->trans_info[Y0 + 2 * active_handle];

          (*tr_tool->prev_trans_info)[OX0 + 2 * active_handle] = tr_tool->trans_info[OX0 + 2 * active_handle];
          (*tr_tool->prev_trans_info)[OY0 + 2 * active_handle] = tr_tool->trans_info[OY0 + 2 * active_handle];

          gimp_draw_tool_resume (GIMP_DRAW_TOOL (tool));

          tr_tool->function = TRANSFORM_HANDLE_N + active_handle;
        }
      break;

    case GIMP_HANDLE_MODE_MOVE:
      /* check for valid position and calculating of OX0...OY3 is
       * done on button release
       */

      /* move handles without changing the transformation matrix */
      ht->matrix_recalculation = FALSE;
      break;

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

          gdouble tempx  = tr_tool->trans_info[X0  + 2 * active_handle];
          gdouble tempy  = tr_tool->trans_info[Y0  + 2 * active_handle];
          gdouble tempox = tr_tool->trans_info[OX0 + 2 * active_handle];
          gdouble tempoy = tr_tool->trans_info[OY0 + 2 * active_handle];
          gint    i;

          n_handles--;
          tr_tool->trans_info[N_HANDLES]--;

          for (i = active_handle; i < n_handles; i++)
            {
              tr_tool->trans_info[X0  + 2 * i] = tr_tool->trans_info[X1  + 2 * i];
              tr_tool->trans_info[Y0  + 2 * i] = tr_tool->trans_info[Y1  + 2 * i];
              tr_tool->trans_info[OX0 + 2 * i] = tr_tool->trans_info[OX1 + 2 * i];
              tr_tool->trans_info[OY0 + 2 * i] = tr_tool->trans_info[OY1 + 2 * i];
            }

          tr_tool->trans_info[X0  + 2 * n_handles] = tempx;
          tr_tool->trans_info[Y0  + 2 * n_handles] = tempy;
          tr_tool->trans_info[OX0 + 2 * n_handles] = tempox;
          tr_tool->trans_info[OY0 + 2 * n_handles] = tempoy;
        }
      break;
    }
}

static void
gimp_handle_transform_tool_button_release (GimpTool              *tool,
                                           const GimpCoords      *coords,
                                           guint32                time,
                                           GdkModifierType        state,
                                           GimpButtonReleaseType  release_type,
                                           GimpDisplay           *display)
{
  GimpHandleTransformTool    *ht      = GIMP_HANDLE_TRANSFORM_TOOL (tool);
  GimpTransformTool          *tr_tool = GIMP_TRANSFORM_TOOL (tool);
  GimpHandleTransformOptions *options;
  gint                        active_handle;

  options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);

  active_handle = tr_tool->function - TRANSFORM_HANDLE_N;

  if (options->handle_mode == GIMP_HANDLE_MODE_MOVE &&
      active_handle >= 0 &&
      active_handle < 4)
    {
      GimpMatrix3 matrix;

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

      /* handle was moved, calculate new original position */
      matrix = tr_tool->transform;
      gimp_matrix3_invert (&matrix);
      gimp_matrix3_transform_point (&matrix,
                                    tr_tool->trans_info[X0 + 2 * active_handle],
                                    tr_tool->trans_info[Y0 + 2 * active_handle],
                                    &tr_tool->trans_info[OX0 + 2 * active_handle],
                                    &tr_tool->trans_info[OY0 + 2 * active_handle]);
    }

  ht->matrix_recalculation = TRUE;

  GIMP_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
                                                  state, release_type, display);
}

static void
gimp_handle_transform_tool_modifier_key (GimpTool        *tool,
                                         GdkModifierType  key,
                                         gboolean         press,
                                         GdkModifierType  state,
                                         GimpDisplay     *display)
{
  GimpHandleTransformTool    *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tool);
  GimpHandleTransformOptions *options;
  GdkModifierType             shift   = gimp_get_extend_selection_mask ();
  GdkModifierType             ctrl    = gimp_get_constrain_behavior_mask ();
  GimpTransformHandleMode     handle_mode;

  options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tool);

  handle_mode = options->handle_mode;

  if (press)
    {
      if (key == (state & (shift | ctrl)))
        {
          /*  first modifier pressed  */
          ht_tool->saved_handle_mode = options->handle_mode;
        }
    }
  else
    {
      if (! (state & (shift | ctrl)))
        {
          /*  last modifier released  */
          handle_mode = ht_tool->saved_handle_mode;
        }
    }

  if (state & shift)
    {
      handle_mode = GIMP_HANDLE_MODE_MOVE;
    }
  else if (state & ctrl)
    {
      handle_mode = GIMP_HANDLE_MODE_REMOVE;
    }

  if (handle_mode != options->handle_mode)
    {
      g_object_set (options, "handle-mode", handle_mode, NULL);
    }

  GIMP_TOOL_CLASS (parent_class)->modifier_key (tool, key, press,
                                                state, display);
}

static void
gimp_handle_transform_tool_dialog (GimpTransformTool *tr_tool)
{
  GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
  GtkWidget               *frame;
  GtkWidget               *table;
  gint                     x, y;

  frame = gimp_frame_new (_("Transformation Matrix"));
  gtk_box_pack_start (GTK_BOX (gimp_tool_gui_get_vbox (tr_tool->gui)), frame,
                      FALSE, FALSE, 0);
  gtk_widget_show (frame);

  table = gtk_table_new (3, 3, FALSE);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_table_set_col_spacings (GTK_TABLE (table), 2);
  gtk_container_add (GTK_CONTAINER (frame), table);
  gtk_widget_show (table);

  for (y = 0; y < 3; y++)
    {
      for (x = 0; x < 3; x++)
        {
          GtkWidget *label = gtk_label_new (" ");

          gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.0);
          gtk_label_set_width_chars (GTK_LABEL (label), 12);
          gtk_table_attach (GTK_TABLE (table), label,
                            x, x + 1, y, y + 1, GTK_EXPAND, GTK_FILL, 0, 0);
          gtk_widget_show (label);

          ht_tool->label[y][x] = label;
        }
    }
}

static void
gimp_handle_transform_tool_dialog_update (GimpTransformTool *tr_tool)
{
  GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
  gint                     x, y;

  for (y = 0; y < 3; y++)
    {
      for (x = 0; x < 3; x++)
        {
          gchar buf[32];

          g_snprintf (buf, sizeof (buf),
                      "%10.5f", tr_tool->transform.coeff[y][x]);

          gtk_label_set_text (GTK_LABEL (ht_tool->label[y][x]), buf);
        }
    }
}

static void
gimp_handle_transform_tool_prepare (GimpTransformTool  *tr_tool)
{
  GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);

  tr_tool->trans_info[X0]        = (gdouble) tr_tool->x1;
  tr_tool->trans_info[Y0]        = (gdouble) tr_tool->y1;
  tr_tool->trans_info[X1]        = (gdouble) tr_tool->x2;
  tr_tool->trans_info[Y1]        = (gdouble) tr_tool->y1;
  tr_tool->trans_info[X2]        = (gdouble) tr_tool->x1;
  tr_tool->trans_info[Y2]        = (gdouble) tr_tool->y2;
  tr_tool->trans_info[X3]        = (gdouble) tr_tool->x2;
  tr_tool->trans_info[Y3]        = (gdouble) tr_tool->y2;
  tr_tool->trans_info[OX0]       = (gdouble) tr_tool->x1;
  tr_tool->trans_info[OY0]       = (gdouble) tr_tool->y1;
  tr_tool->trans_info[OX1]       = (gdouble) tr_tool->x2;
  tr_tool->trans_info[OY1]       = (gdouble) tr_tool->y1;
  tr_tool->trans_info[OX2]       = (gdouble) tr_tool->x1;
  tr_tool->trans_info[OY2]       = (gdouble) tr_tool->y2;
  tr_tool->trans_info[OX3]       = (gdouble) tr_tool->x2;
  tr_tool->trans_info[OY3]       = (gdouble) tr_tool->y2;
  tr_tool->trans_info[N_HANDLES] = 0;

  ht_tool->matrix_recalculation = TRUE;
}

static void
gimp_handle_transform_tool_motion (GimpTransformTool *tr_tool)
{
  GimpHandleTransformOptions *options;
  gint                        n_handles;
  gint                        active_handle;

  options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);

  n_handles     = (gint) tr_tool->trans_info[N_HANDLES];
  active_handle = tr_tool->function - TRANSFORM_HANDLE_N;

  if (active_handle >= 0 && active_handle < 4)
    {
      if (options->handle_mode == GIMP_HANDLE_MODE_MOVE)
        {
          tr_tool->trans_info[X0 + 2 * active_handle] += tr_tool->curx - tr_tool->lastx;
          tr_tool->trans_info[Y0 + 2 * active_handle] += tr_tool->cury - tr_tool->lasty;

          /* 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
           */
        }
      else if (options->handle_mode == GIMP_HANDLE_MODE_ADD_TRANSFORM)
        {
          gdouble angle, angle_sin, angle_cos, scale;
          gdouble fixed_handles_x[3];
          gdouble fixed_handles_y[3];
          gdouble oldpos_x[4], oldpos_y[4];
          gdouble newpos_x[4], newpos_y[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_x[j] = tr_tool->trans_info[X0 + i * 2];
                  fixed_handles_y[j] = tr_tool->trans_info[Y0 + i * 2];
                  j++;
                }

              newpos_x[i] = oldpos_x[i] = (*tr_tool->prev_trans_info)[X0 + i * 2];
              newpos_y[i] = oldpos_y[i] = (*tr_tool->prev_trans_info)[Y0 + i * 2];
            }

          newpos_x[active_handle] = oldpos_x[active_handle] + tr_tool->curx - tr_tool->mousex;
          newpos_y[active_handle] = oldpos_y[active_handle] + tr_tool->cury - tr_tool->mousey;

          switch (n_handles)
            {
            case 1:
              /* move */
              for (i = 1; i < 4; i++)
                {
                  newpos_x[i] = oldpos_x[i] + tr_tool->curx - tr_tool->mousex;
                  newpos_y[i] = oldpos_y[i] + tr_tool->cury - tr_tool->mousey;
                }
              break;

            case 2:
              /* rotate and keep-aspect-scale */
              scale = calc_len (newpos_x[active_handle] - fixed_handles_x[0],
                                newpos_y[active_handle] - fixed_handles_y[0])
                / calc_len (oldpos_x[active_handle] - fixed_handles_x[0],
                            oldpos_y[active_handle] - fixed_handles_y[0]);

              angle = calc_angle (oldpos_x[active_handle] - fixed_handles_x[0],
                                  oldpos_y[active_handle] - fixed_handles_y[0],
                                  newpos_x[active_handle] - fixed_handles_x[0],
                                  newpos_y[active_handle] - fixed_handles_y[0]);

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

              for (i = 2; i < 4; i++)
                {
                  newpos_x[i] = fixed_handles_x[0]
                    + scale * (angle_cos * (oldpos_x[i]-fixed_handles_x[0])
                               + angle_sin * (oldpos_y[i]-fixed_handles_y[0]) );
                  newpos_y[i] = fixed_handles_y[0]
                    + scale * (-angle_sin * (oldpos_x[i]-fixed_handles_x[0])
                               + angle_cos * (oldpos_y[i]-fixed_handles_y[0]) );
                }
              break;

            case 3:
              /* shear and non-aspect-scale */
              scale = calc_lineintersect_ratio (oldpos_x[3], oldpos_y[3],
                                                oldpos_x[active_handle], oldpos_y[active_handle],
                                                fixed_handles_x[0], fixed_handles_y[0],
                                                fixed_handles_x[1], fixed_handles_y[1]);

              newpos_x[3] = oldpos_x[3] + scale * (tr_tool->curx - tr_tool->mousex);
              newpos_y[3] = oldpos_y[3] + scale * (tr_tool->cury - tr_tool->mousey);
              break;
            }

          for (i = 0; i < 4; i++)
            {
              tr_tool->trans_info[X0 + 2 * i] = newpos_x[i];
              tr_tool->trans_info[Y0 + 2 * i] = newpos_y[i];
            }
        }
    }
}

static void
gimp_handle_transform_tool_recalc_matrix (GimpTransformTool *tr_tool)
{
  GimpHandleTransformTool *ht_tool = GIMP_HANDLE_TRANSFORM_TOOL (tr_tool);
  gdouble                  coeff[8 * 9];
  gdouble                  sol[8];
  gdouble                  opos_x[4];
  gdouble                  opos_y[4];
  gdouble                  pos_x[4];
  gdouble                  pos_y[4];
  gint                     i;

  if (ht_tool->matrix_recalculation)
    {
      for (i = 0; i < 4; i++)
        {
          pos_x[i]  = tr_tool->trans_info[X0 + i * 2];
          pos_y[i]  = tr_tool->trans_info[Y0 + i * 2];
          opos_x[i] = tr_tool->trans_info[OX0 + i * 2];
          opos_y[i] = tr_tool->trans_info[OY0 + i * 2];
        }

      for (i = 0; i < 4; i++)
        {
          coeff[i * 9 + 0] = opos_x[i];
          coeff[i * 9 + 1] = opos_y[i];
          coeff[i * 9 + 2] = 1;
          coeff[i * 9 + 3] = 0;
          coeff[i * 9 + 4] = 0;
          coeff[i * 9 + 5] = 0;
          coeff[i * 9 + 6] = -opos_x[i] * pos_x[i];
          coeff[i * 9 + 7] = -opos_y[i] * pos_x[i];
          coeff[i * 9 + 8] = pos_x[i];

          coeff[(i + 4) * 9 + 0] = 0;
          coeff[(i + 4) * 9 + 1] = 0;
          coeff[(i + 4) * 9 + 2] = 0;
          coeff[(i + 4) * 9 + 3] = opos_x[i];
          coeff[(i + 4) * 9 + 4] = opos_y[i];
          coeff[(i + 4) * 9 + 5] = 1;
          coeff[(i + 4) * 9 + 6] = -opos_x[i] * pos_y[i];
          coeff[(i + 4) * 9 + 7] = -opos_y[i] * pos_y[i];
          coeff[(i + 4) * 9 + 8] = pos_y[i];
        }

      if (mod_gauss (coeff, sol, 8))
        {
          tr_tool->transform.coeff[0][0] = sol[0];
          tr_tool->transform.coeff[0][1] = sol[1];
          tr_tool->transform.coeff[0][2] = sol[2];
          tr_tool->transform.coeff[1][0] = sol[3];
          tr_tool->transform.coeff[1][1] = sol[4];
          tr_tool->transform.coeff[1][2] = sol[5];
          tr_tool->transform.coeff[2][0] = sol[6];
          tr_tool->transform.coeff[2][1] = sol[7];
          tr_tool->transform.coeff[2][2] = 1;
        }
      else
        {
          /* this should not happen reset the matrix so the user sees
           * that something went wrong
           */
          gimp_matrix3_identity (&tr_tool->transform);
        }
    }
}

static gchar *
gimp_handle_transform_tool_get_undo_desc (GimpTransformTool *tr_tool)
{
  return g_strdup (C_("undo-type", "Handle transform"));
}

static TransformAction
gimp_handle_transform_tool_pick_function (GimpTransformTool *tr_tool,
                                          const GimpCoords  *coords,
                                          GdkModifierType    state,
                                          GimpDisplay       *display)
{
  TransformAction i;

  for (i = TRANSFORM_HANDLE_N; i < TRANSFORM_HANDLE_N + 4; i++)
    {
      if (tr_tool->handles[i] &&
          gimp_canvas_item_hit (tr_tool->handles[i], coords->x, coords->y))
        {
          return i;
        }
    }

  return TRANSFORM_HANDLE_NONE;
}

static void
gimp_handle_transform_tool_cursor_update (GimpTransformTool  *tr_tool,
                                          GimpCursorType     *cursor,
                                          GimpCursorModifier *modifier)
{
  GimpHandleTransformOptions *options;
  GimpToolCursorType          tool_cursor = GIMP_TOOL_CURSOR_NONE;

  options = GIMP_HANDLE_TRANSFORM_TOOL_GET_OPTIONS (tr_tool);

  *cursor   = GIMP_CURSOR_CROSSHAIR_SMALL;
  *modifier = GIMP_CURSOR_MODIFIER_NONE;

  /* do not show modifiers when the tool isn't active */
  if (! gimp_draw_tool_is_active (GIMP_DRAW_TOOL (tr_tool)))
    return;

  switch (options->handle_mode)
    {
    case GIMP_HANDLE_MODE_ADD_TRANSFORM:
      if (tr_tool->function > TRANSFORM_HANDLE_NONE)
        {
          switch ((gint) tr_tool->trans_info[N_HANDLES])
            {
            case 1:
              tool_cursor = GIMP_TOOL_CURSOR_MOVE;
              break;
            case 2:
              tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
              break;
            case 3:
              tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
              break;
            case 4:
              tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
              break;
            }
        }
      else
        {
          if ((gint) tr_tool->trans_info[N_HANDLES] < 4)
            *modifier = GIMP_CURSOR_MODIFIER_PLUS;
          else
            *modifier = GIMP_CURSOR_MODIFIER_BAD;
        }
      break;

    case GIMP_HANDLE_MODE_MOVE:
      if (tr_tool->function > TRANSFORM_HANDLE_NONE)
        *modifier = GIMP_CURSOR_MODIFIER_MOVE;
      else
        *modifier = GIMP_CURSOR_MODIFIER_BAD;
      break;

    case GIMP_HANDLE_MODE_REMOVE:
      if (tr_tool->function > TRANSFORM_HANDLE_NONE)
        *modifier = GIMP_CURSOR_MODIFIER_MINUS;
      else
        *modifier = GIMP_CURSOR_MODIFIER_BAD;
      break;
    }

  gimp_tool_control_set_tool_cursor (GIMP_TOOL (tr_tool)->control,
                                     tool_cursor);
}

static void
gimp_handle_transform_tool_draw_gui (GimpTransformTool *tr_tool,
                                     gint               handle_w,
                                     gint               handle_h)
{
  GimpDrawTool *draw_tool = GIMP_DRAW_TOOL (tr_tool);
  gint          i;

#if 0
  /* show additional points for debugging */
  for (i = tr_tool->trans_info[N_HANDLES]; i < 4; i++)
    {
      gimp_draw_tool_add_handle (draw_tool,
                                 GIMP_HANDLE_FILLED_CIRCLE,
                                 tr_tool->trans_info[X0 + 2 * i],
                                 tr_tool->trans_info[Y0 + 2 * i],
                                 GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                 GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                 GIMP_HANDLE_ANCHOR_CENTER);
      gimp_draw_tool_add_handle (draw_tool,
                                 GIMP_HANDLE_FILLED_DIAMOND,
                                 tr_tool->trans_info[OX0 + 2 * i],
                                 tr_tool->trans_info[OY0 + 2 * i],
                                 GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                 GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                 GIMP_HANDLE_ANCHOR_CENTER);
      }

  for (i = 0; i < tr_tool->trans_info[N_HANDLES]; i++)
    {
      tr_tool->handles[TRANSFORM_HANDLE_N + i] =
        gimp_draw_tool_add_handle (draw_tool,
                                   GIMP_HANDLE_DIAMOND,
                                   tr_tool->trans_info[OX0 + 2 * i],
                                   tr_tool->trans_info[OY0 + 2 * i],
                                   GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                   GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                   GIMP_HANDLE_ANCHOR_CENTER);
    }
#endif

  for (i = 0; i < tr_tool->trans_info[N_HANDLES]; i++)
    {
      tr_tool->handles[TRANSFORM_HANDLE_N + i] =
        gimp_draw_tool_add_handle (draw_tool,
                                   GIMP_HANDLE_CIRCLE,
                                   tr_tool->trans_info[X0 + 2 * i],
                                   tr_tool->trans_info[Y0 + 2 * i],
                                   GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                   GIMP_TOOL_HANDLE_SIZE_CIRCLE,
                                   GIMP_HANDLE_ANCHOR_CENTER);
    }
}

/* check if a handle is not on the connection line of two other handles */
static gboolean
is_handle_position_valid (GimpTransformTool *tr_tool,
                          gint               active_handle)
{
  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 (active_handle == i ||
                  active_handle == j ||
                  active_handle == k)
                {
                  if ((tr_tool->trans_info[X0 + 2 * i] -
                       tr_tool->trans_info[X0 + 2 * j]) *
                      (tr_tool->trans_info[Y0 + 2 * j] -
                       tr_tool->trans_info[Y0 + 2 * k]) ==

                      (tr_tool->trans_info[X0 + 2 * j] -
                       tr_tool->trans_info[X0 + 2 * k]) *
                      (tr_tool->trans_info[Y0 + 2 * i] -
                       tr_tool->trans_info[Y0 + 2 * j]))
                    {
                      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 (GimpTransformTool *tr_tool,
                   gint               active_handle)
{
  gdouble posx = tr_tool->trans_info[X0 + 2 * active_handle];
  gdouble posy = tr_tool->trans_info[Y0 + 2 * active_handle];
  gdouble dx, dy;

  for (dx = -0.1; dx < 0.11; dx += 0.1)
    {
      tr_tool->trans_info[X0 + 2 * active_handle] = posx + dx;

      for (dy = -0.1; dy < 0.11; dy += 0.1)
        {
          tr_tool->trans_info[Y0 + 2 * active_handle] = posy + dy;

          if (is_handle_position_valid (tr_tool, active_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);
}


/* modified gaussian algorithm
 * solves a system of linear equations
 *
 * Example:
 * 1x + 2y + 4z = 25
 * 2x + 1y      = 4
 * 3x + 5y + 2z = 23
 * Solution: x=1, y=2, z=5
 *
 * Input:
 * matrix = { 1,2,4,25,2,1,0,4,3,5,2,23 }
 * s = 3 (Number of variables)
 * Output:
 * return value == TRUE (TRUE, if there is a single unique solution)
 * solution == { 1,2,5 } (if the return value is FALSE, the content
 * of solution is of no use)
 */
static gboolean
mod_gauss (gdouble matrix[],
           gdouble solution[],
           gint    s)
{
  gint    p[s]; /* row permutation */
  gint    i, j, r, temp;
  gdouble q;
  gint    t = s + 1;

  for (i = 0; i < s; i++)
    {
      p[i] = i;
    }

  for (r = 0; r < s; r++)
    {
      /* make sure that (r,r) is not 0 */
      if (matrix[p[r] * t + r] == 0.0)
        {
          /* we need to permutate rows */
          for (i = r + 1; i <= s; i++)
            {
              if (i == s)
                {
                  /* if this happens, the linear system has zero or
                   * more than one solutions.
                   */
                  return FALSE;
                }

              if (matrix[p[i] * t + r] != 0.0)
                break;
            }

          temp = p[r];
          p[r] = p[i];
          p[i] = temp;
        }

      /* make (r,r) == 1 */
      q = 1.0 / matrix[p[r] * t + r];
      matrix[p[r] * t + r] = 1.0;

      for (j = r + 1; j < t; j++)
        {
          matrix[p[r] * t + j] *= q;
        }

      /* make that all entries in column r are 0 (except (r,r)) */
      for (i = 0; i < s; i++)
        {
          if (i == r)
            continue;

          for (j = r + 1; j < t ; j++)
            {
              matrix[p[i] * t + j] -= matrix[p[r] * t + j] * matrix[p[i] * t + r];
            }

          /* we don't need to execute the following line
           * since we won't access this element again:
           *
           * matrix[p[i] * t + r] = 0.0;
           */
        }
    }

  for (i = 0; i < s; i++)
    {
      solution[i] = matrix[p[i] * t + s];
    }

  return TRUE;
}
