/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * GimpLinkLayer
 * Copyright (C) 2019 Jehan
 *
 * 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 <string.h>

#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpcolor/gimpcolor.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpmath/gimpmath.h"

#include "core-types.h"

#include "gegl/gimp-gegl-loops.h"
#include "gegl/gimp-gegl-utils.h"

#include "gimp.h"
#include "gimpcontext.h"
#include "gimpimage.h"
#include "gimpimage-color-profile.h"
#include "gimpimage-undo.h"
#include "gimpimage-undo-push.h"
#include "gimplink.h"
#include "gimplinklayer.h"
#include "gimpobjectqueue.h"
#include "gimpprogress.h"
#include "gimprasterizable.h"
#include "gimp-transform-utils.h"

#include "gimp-intl.h"

#define EPSILON 1e-10


enum
{
  LINK_LAYER_XCF_NONE              = 0,
  LINK_LAYER_XCF_DONT_AUTO_RENAME  = 1 << 0,
  LINK_LAYER_XCF_MODIFIED          = 1 << 1
};

enum
{
  PROP_0,
  PROP_LINK,
  PROP_SCALED_ONLY,
  N_PROPS
};

struct _GimpLinkLayerPrivate
{
  GimpLink              *link;
  gboolean               scaled_only;

  GimpMatrix3            matrix;
  gint                   offset_x;
  gint                   offset_y;
  GimpInterpolationType  interpolation;

  /* A transient value only useful to know when to drop monitoring after
   * a buffer update.
   */
  gboolean               keep_monitoring;
};

static void       gimp_link_layer_rasterizable_iface_init
                                                 (GimpRasterizableInterface *iface);

static void       gimp_link_layer_finalize       (GObject           *object);
static void       gimp_link_layer_get_property   (GObject           *object,
                                                  guint              property_id,
                                                  GValue            *value,
                                                  GParamSpec        *pspec);
static void       gimp_link_layer_set_property   (GObject           *object,
                                                  guint              property_id,
                                                  const GValue      *value,
                                                  GParamSpec        *pspec);

static gint64     gimp_link_layer_get_memsize    (GimpObject        *object,
                                                  gint64            *gui_size);

static GimpItem * gimp_link_layer_duplicate      (GimpItem          *item,
                                                  GType              new_type);

static void       gimp_link_layer_translate      (GimpItem              *item,
                                                  gdouble                offset_x,
                                                  gdouble                offset_y,
                                                  gboolean               push_undo);
static void       gimp_link_layer_flip           (GimpItem              *item,
                                                  GimpContext           *context,
                                                  GimpOrientationType    flip_type,
                                                  gdouble                axis,
                                                  gboolean               clip_result);
static void       gimp_link_layer_scale          (GimpItem              *item,
                                                  gint                   new_width,
                                                  gint                   new_height,
                                                  gint                   new_offset_x,
                                                  gint                   new_offset_y,
                                                  GimpInterpolationType  interpolation_type,
                                                  GimpProgress          *progress);
static void       gimp_link_layer_transform      (GimpItem               *item,
                                                  GimpContext            *context,
                                                  const GimpMatrix3      *matrix,
                                                  GimpTransformDirection  direction,
                                                  GimpInterpolationType   interpolation_type,
                                                  GimpTransformResize     clip_result,
                                                  GimpProgress           *progress,
                                                  gboolean                push_undo);

static void       gimp_link_layer_set_buffer     (GimpDrawable      *drawable,
                                                  gboolean           push_undo,
                                                  const gchar       *undo_desc,
                                                  GeglBuffer        *buffer,
                                                  const GeglRectangle *bounds);
static void       gimp_link_layer_push_undo      (GimpDrawable      *drawable,
                                                  const gchar       *undo_desc,
                                                  GeglBuffer        *buffer,
                                                  gint               x,
                                                  gint               y,
                                                  gint               width,
                                                  gint               height);

static void       gimp_link_layer_set_rasterized (GimpRasterizable  *rasterizable,
                                                  gboolean           rasterized);

static void       gimp_link_layer_convert_type   (GimpLayer         *layer,
                                                  GimpImage         *dest_image,
                                                  const Babl        *new_format,
                                                  GimpColorProfile  *src_profile,
                                                  GimpColorProfile  *dest_profile,
                                                  GeglDitherMethod   layer_dither_type,
                                                  GeglDitherMethod   mask_dither_type,
                                                  gboolean           push_undo,
                                                  GimpProgress      *progress);

static void       gimp_link_layer_render_full    (GimpLinkLayer     *layer);
static gboolean   gimp_link_layer_render_link    (GimpLinkLayer     *layer);

static gboolean
               gimp_link_layer_is_scaling_matrix (GimpLinkLayer     *layer,
                                                  const GimpMatrix3 *matrix,
                                                  gint              *new_width,
                                                  gint              *new_height,
                                                  gint              *new_offset_x,
                                                  gint              *new_offset_y);


G_DEFINE_TYPE_WITH_CODE (GimpLinkLayer, gimp_link_layer, GIMP_TYPE_LAYER,
                         G_ADD_PRIVATE (GimpLinkLayer)
                         G_IMPLEMENT_INTERFACE (GIMP_TYPE_RASTERIZABLE,
                                                gimp_link_layer_rasterizable_iface_init))

#define parent_class gimp_link_layer_parent_class

static GParamSpec *link_layer_props[N_PROPS] = { NULL, };


static void
gimp_link_layer_class_init (GimpLinkLayerClass *klass)
{
  GObjectClass      *object_class      = G_OBJECT_CLASS (klass);
  GimpObjectClass   *gimp_object_class = GIMP_OBJECT_CLASS (klass);
  GimpViewableClass *viewable_class    = GIMP_VIEWABLE_CLASS (klass);
  GimpItemClass     *item_class        = GIMP_ITEM_CLASS (klass);
  GimpDrawableClass *drawable_class    = GIMP_DRAWABLE_CLASS (klass);
  GimpLayerClass    *layer_class       = GIMP_LAYER_CLASS (klass);

  object_class->finalize            = gimp_link_layer_finalize;
  object_class->get_property        = gimp_link_layer_get_property;
  object_class->set_property        = gimp_link_layer_set_property;

  gimp_object_class->get_memsize    = gimp_link_layer_get_memsize;

  /* TODO: make a custom icon "gimp-link-layer". */
  viewable_class->default_icon_name = "emblem-symbolic-link";
  viewable_class->default_name      = _("Link Layer");

  item_class->duplicate             = gimp_link_layer_duplicate;
  item_class->rename                = gimp_rasterizable_rename;
  item_class->translate             = gimp_link_layer_translate;
  item_class->flip                  = gimp_link_layer_flip;
  item_class->scale                 = gimp_link_layer_scale;
  item_class->transform             = gimp_link_layer_transform;

  item_class->rename_desc           = _("Rename Link Layer");
  item_class->translate_desc        = _("Move Link Layer");
  item_class->scale_desc            = _("Scale Link Layer");
  item_class->resize_desc           = _("Resize Link Layer");
  item_class->flip_desc             = _("Flip Link Layer");
  item_class->rotate_desc           = _("Rotate Link Layer");
  item_class->transform_desc        = _("Transform Link Layer");

  drawable_class->set_buffer        = gimp_link_layer_set_buffer;
  drawable_class->push_undo         = gimp_link_layer_push_undo;

  layer_class->convert_type         = gimp_link_layer_convert_type;

  link_layer_props[PROP_LINK]        = g_param_spec_object ("link",
                                                            NULL, NULL,
                                                            GIMP_TYPE_LINK,
                                                            GIMP_PARAM_READWRITE |
                                                            GIMP_PARAM_STATIC_STRINGS);

  link_layer_props[PROP_SCALED_ONLY] = g_param_spec_boolean ("scaled-only",
                                                             NULL, NULL,
                                                             FALSE,
                                                             GIMP_PARAM_READWRITE |
                                                             GIMP_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, N_PROPS, link_layer_props);
}

static void
gimp_link_layer_init (GimpLinkLayer *layer)
{
  layer->p         = gimp_link_layer_get_instance_private (layer);
  layer->p->link   = NULL;
  gimp_matrix3_identity (&layer->p->matrix);

  layer->p->scaled_only   = FALSE;
  layer->p->offset_x      = 0;
  layer->p->offset_y      = 0;
  layer->p->interpolation = GIMP_INTERPOLATION_NONE;

  layer->p->keep_monitoring = FALSE;
}

static void
gimp_link_layer_rasterizable_iface_init (GimpRasterizableInterface *iface)
{
  iface->set_rasterized = gimp_link_layer_set_rasterized;
}

static void
gimp_link_layer_finalize (GObject *object)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (object);

  g_clear_object (&layer->p->link);

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

static void
gimp_link_layer_get_property (GObject      *object,
                              guint         property_id,
                              GValue       *value,
                              GParamSpec   *pspec)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (object);

  switch (property_id)
    {
    case PROP_LINK:
      g_value_set_object (value, layer->p->link);
      break;
    case PROP_SCALED_ONLY:
      g_value_set_boolean (value, layer->p->scaled_only);
      break;

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

static void
gimp_link_layer_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (object);

  switch (property_id)
    {
    case PROP_LINK:
      gimp_link_layer_set_link (layer, g_value_get_object (value), FALSE);
      break;
    case PROP_SCALED_ONLY:
      layer->p->scaled_only = g_value_get_boolean (value);
      break;

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

static gint64
gimp_link_layer_get_memsize (GimpObject *object,
                             gint64     *gui_size)
{
  GimpLinkLayer *layer   = GIMP_LINK_LAYER (object);
  gint64         memsize = 0;

  memsize += gimp_object_get_memsize (GIMP_OBJECT (layer->p->link),
                                      gui_size);

  return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

static GimpItem *
gimp_link_layer_duplicate (GimpItem *item,
                           GType     new_type)
{
  GimpItem *new_item;

  g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_DRAWABLE), NULL);

  new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type);

  if (GIMP_IS_LINK_LAYER (new_item))
    {
      GimpLinkLayer *layer     = GIMP_LINK_LAYER (item);
      GimpLinkLayer *new_layer = GIMP_LINK_LAYER (new_item);
      GimpLink      *link      = NULL;
      GeglBuffer    *buffer;
      gint           width;
      gint           height;

      gimp_config_sync (G_OBJECT (layer), G_OBJECT (new_layer), 0);

      if (layer->p->link)
        {
          link = gimp_link_duplicate (layer->p->link);
          gimp_link_layer_set_link (new_layer, link, FALSE);
        }

      buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
      width  = gegl_buffer_get_width (buffer);
      height = gegl_buffer_get_height (buffer);

      if (width  != gimp_item_get_width  (GIMP_ITEM (new_layer)) ||
          height != gimp_item_get_height (GIMP_ITEM (new_layer)))
        {
          GeglBuffer    *new_buffer;
          GeglRectangle  bounds;

          gimp_item_get_offset (GIMP_ITEM (new_layer), &bounds.x, &bounds.y);
          bounds.width  = 0;
          bounds.height = 0;

          new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
                                        gimp_drawable_get_format (GIMP_DRAWABLE (new_layer)));
          GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (GIMP_DRAWABLE (new_layer),
                                                          FALSE, NULL,
                                                          new_buffer, &bounds);
          g_object_unref (new_buffer);
        }

      gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
                             gimp_drawable_get_buffer (GIMP_DRAWABLE (new_layer)), NULL);

      new_layer->p->scaled_only   = layer->p->scaled_only;
      gimp_rasterizable_set_auto_rename (GIMP_RASTERIZABLE (new_layer),
                                         gimp_rasterizable_get_auto_rename (GIMP_RASTERIZABLE (layer)));
      new_layer->p->matrix        = layer->p->matrix;
      new_layer->p->offset_x      = layer->p->offset_x;
      new_layer->p->offset_y      = layer->p->offset_y;
      new_layer->p->interpolation = layer->p->interpolation;

      new_layer->p->keep_monitoring = FALSE;

      if (gimp_rasterizable_is_rasterized (GIMP_RASTERIZABLE (layer)))
        gimp_rasterizable_set_undo_rasterized (GIMP_RASTERIZABLE (new_layer), TRUE);

      g_clear_object (&link);
    }

  return new_item;
}

static void
gimp_link_layer_translate (GimpItem *item,
                           gdouble   offset_x,
                           gdouble   offset_y,
                           gboolean  push_undo)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (item);

  if (! gimp_matrix3_is_identity (&layer->p->matrix))
    gimp_matrix3_translate (&layer->p->matrix, offset_x, offset_y);

  GIMP_ITEM_CLASS (parent_class)->translate (item, offset_x, offset_y, push_undo);
}

static void
gimp_link_layer_flip (GimpItem            *item,
                      GimpContext         *context,
                      GimpOrientationType  flip_type,
                      gdouble              axis,
                      gboolean             clip_result)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (item);

  if (gimp_link_is_monitored (layer->p->link))
    {
      GimpMatrix3 matrix = layer->p->matrix;

      gimp_transform_matrix_flip (&matrix, flip_type, axis);
      gimp_link_layer_set_transform (layer, &matrix, layer->p->interpolation, TRUE);
    }
  else
    {
      GIMP_ITEM_CLASS (parent_class)->flip (item, context, flip_type, axis, clip_result);
    }
}

static void
gimp_link_layer_scale (GimpItem              *item,
                       gint                   new_width,
                       gint                   new_height,
                       gint                   new_offset_x,
                       gint                   new_offset_y,
                       GimpInterpolationType  interpolation_type,
                       GimpProgress          *progress)
{
  GimpLinkLayer   *link_layer = GIMP_LINK_LAYER (item);
  GimpLayer       *layer      = GIMP_LAYER (item);
  GimpObjectQueue *queue      = NULL;

  if (progress && layer->mask)
    {
      GimpLayerMask *mask;

      queue    = gimp_object_queue_new (progress);
      progress = GIMP_PROGRESS (queue);

      /* temporarily set layer->mask to NULL, so that its size won't be counted
       * when pushing the layer to the queue.
       */
      mask        = layer->mask;
      layer->mask = NULL;

      gimp_object_queue_push (queue, layer);
      gimp_object_queue_push (queue, mask);

      layer->mask = mask;
    }

  if (queue)
    gimp_object_queue_pop (queue);

  if (gimp_link_is_vector (link_layer->p->link)    &&
      gimp_link_is_monitored (link_layer->p->link) &&
      gimp_matrix3_is_identity (&link_layer->p->matrix))
    {
      /* Non-modified vector images are always recomputed from the
       * source file and therefore are always sharp.
       */
      gimp_link_set_size (link_layer->p->link, new_width, new_height, FALSE);
      gimp_item_set_offset (item, new_offset_x, new_offset_y);
      gimp_link_layer_render_link (link_layer);
    }
  else if (! gimp_matrix3_is_identity (&link_layer->p->matrix))
    {
      GimpMatrix3 matrix = link_layer->p->matrix;
      gdouble     x_scale_factor;
      gdouble     y_scale_factor;
      gint        offset_x;
      gint        offset_y;

      x_scale_factor = (gdouble) new_width / gimp_item_get_width (item);
      y_scale_factor = (gdouble) new_height / gimp_item_get_height (item);

      gimp_matrix3_scale (&matrix, x_scale_factor, y_scale_factor);

      gimp_link_layer_set_transform (link_layer, &matrix, interpolation_type, TRUE);

      /* Unfortunately we can't know the proper translation offset to
       * set before we replay the full matrix (the offsets may have been
       * changed in previous transformations).
       * To avoid re-loading the source image twice though, I don't call
       * gimp_link_layer_set_transform() again but directly edit the
       * matrix and set the offset.
       */
      gimp_item_get_offset (item, &offset_x, &offset_y);
      gimp_matrix3_translate (&link_layer->p->matrix,
                              new_offset_x - offset_x,
                              new_offset_y - offset_y);
      gimp_item_set_offset (item, new_offset_x, new_offset_y);
      gimp_drawable_update_all (GIMP_DRAWABLE (layer));
    }
  else if (gimp_link_is_monitored (link_layer->p->link))
    {
      GimpMatrix3 matrix = link_layer->p->matrix;
      gdouble     x_scale_factor;
      gdouble     y_scale_factor;
      gint        offset_x;
      gint        offset_y;

      x_scale_factor = (gdouble) new_width / gimp_item_get_width (item);
      y_scale_factor = (gdouble) new_height / gimp_item_get_height (item);

      gimp_matrix3_scale (&matrix, x_scale_factor, y_scale_factor);
      gimp_item_get_offset (item, &offset_x, &offset_y);
      gimp_matrix3_translate (&matrix,
                              new_offset_x - offset_x,
                              new_offset_y - offset_y);
      gimp_link_layer_set_transform (link_layer, &matrix, interpolation_type, TRUE);
    }
  else
    {
      GIMP_ITEM_CLASS (parent_class)->scale (GIMP_ITEM (layer),
                                             new_width, new_height,
                                             new_offset_x, new_offset_y,
                                             interpolation_type, progress);
    }

  if (layer->mask)
    {
      if (queue)
        gimp_object_queue_pop (queue);

      gimp_item_scale (GIMP_ITEM (layer->mask),
                       new_width, new_height,
                       new_offset_x, new_offset_y,
                       interpolation_type, progress);
    }

  g_clear_object (&queue);
}

static void
gimp_link_layer_transform (GimpItem               *item,
                           GimpContext            *context,
                           const GimpMatrix3      *matrix,
                           GimpTransformDirection  direction,
                           GimpInterpolationType   interpolation_type,
                           GimpTransformResize     clip_result,
                           GimpProgress           *progress,
                           gboolean                push_undo)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (item);
  gboolean       keep_monitoring;

  if (gimp_matrix3_is_identity (matrix))
    return;

  if (push_undo)
    gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
                                     _("Transform Link Layer"), layer);

  if (gimp_link_is_vector (layer->p->link)    &&
      gimp_link_is_monitored (layer->p->link) &&
      gimp_matrix3_is_identity (&layer->p->matrix))
    {
      gint new_width;
      gint new_height;
      gint new_offset_x;
      gint new_offset_y;

      if (gimp_link_layer_is_scaling_matrix (layer, matrix,
                                             &new_width, &new_height,
                                             &new_offset_x, &new_offset_y))
        {
          /* Scaling when no other transformation has happened yet is
           * special-case for vector links, because we can do even
           * better by reloading from source.
           */
          gimp_link_layer_scale (item, new_width, new_height,
                                 new_offset_x, new_offset_y,
                                 interpolation_type, progress);
          return;
        }
    }

  /* Clipping would produce different results when applied on a single
   * step of multiple transformations. So only "adjust" transformations
   * are stored non-destructively. Any clip/crop transformation triggers
   * a destructive transform.
   *
   * Note: we could also store non-destructively a single clipped
   * transformation, but that would be harder to document and explain.
   */
  keep_monitoring = (gimp_link_is_monitored (layer->p->link) &&
                     clip_result == GIMP_TRANSFORM_RESIZE_ADJUST);

  if (keep_monitoring)
    {
      GimpMatrix3 left  = *matrix;
      GimpMatrix3 right = layer->p->matrix;

      if (direction == GIMP_TRANSFORM_BACKWARD)
        gimp_matrix3_invert (&left);

      gimp_matrix3_mult (&left, &right);
      gimp_link_layer_set_transform (layer, &right, interpolation_type, TRUE);
    }
  else
    {
      /* Reset the transformation matrix. */
      gimp_matrix3_identity (&layer->p->matrix);

      GIMP_ITEM_CLASS (parent_class)->transform (item, context,
                                                 matrix, direction,
                                                 interpolation_type,
                                                 clip_result, progress,
                                                 push_undo);
    }
}

static void
gimp_link_layer_set_buffer (GimpDrawable        *drawable,
                            gboolean             push_undo,
                            const gchar         *undo_desc,
                            GeglBuffer          *buffer,
                            const GeglRectangle *bounds)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable);
  GimpImage     *image = gimp_item_get_image (GIMP_ITEM (layer));

  if (push_undo)
    {
      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE_MOD, undo_desc);
      gimp_image_undo_push_link_layer (image, NULL, layer);
    }

  GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
                                                  FALSE, undo_desc,
                                                  buffer,
                                                  bounds);

  if (push_undo)
    {
      if (layer->p->link                          &&
          gimp_link_is_monitored (layer->p->link) &&
          ! layer->p->keep_monitoring)
        gimp_link_freeze (layer->p->link);

      gimp_image_undo_group_end (image);
    }
}

static void
gimp_link_layer_push_undo (GimpDrawable *drawable,
                           const gchar  *undo_desc,
                           GeglBuffer   *buffer,
                           gint          x,
                           gint          y,
                           gint          width,
                           gint          height)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (drawable);
  GimpImage     *image = gimp_item_get_image (GIMP_ITEM (layer));
  gboolean       monitored;

  monitored = gimp_link_is_monitored (layer->p->link);

  if (monitored)
    {
      gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_DRAWABLE, undo_desc);

      gimp_image_undo_push_link_layer (image, NULL, layer);
      gimp_link_freeze (layer->p->link);
    }

  GIMP_DRAWABLE_CLASS (parent_class)->push_undo (drawable, undo_desc,
                                                 buffer,
                                                 x, y, width, height);

  if (monitored)
    {
      gimp_image_undo_group_end (image);
    }
}

static void
gimp_link_layer_set_rasterized (GimpRasterizable *rasterizable,
                                gboolean          rasterized)
{
  GimpLinkLayer *layer = GIMP_LINK_LAYER (rasterizable);

  if (rasterized)
    {
      gimp_link_freeze (layer->p->link);
    }
  else
    {
      gimp_link_thaw (layer->p->link);
      gimp_matrix3_identity (&layer->p->matrix);
      gimp_link_layer_render_link (layer);
    }
}

static void
gimp_link_layer_convert_type (GimpLayer         *layer,
                              GimpImage         *dest_image,
                              const Babl        *new_format,
                              GimpColorProfile  *src_profile,
                              GimpColorProfile  *dest_profile,
                              GeglDitherMethod   layer_dither_type,
                              GeglDitherMethod   mask_dither_type,
                              gboolean           push_undo,
                              GimpProgress      *progress)
{
  GimpLinkLayer *link_layer = GIMP_LINK_LAYER (layer);
  GimpImage     *image      = gimp_item_get_image (GIMP_ITEM (link_layer));

  GIMP_LAYER_CLASS (parent_class)->convert_type (layer, dest_image,
                                                 new_format,
                                                 src_profile,
                                                 dest_profile,
                                                 layer_dither_type,
                                                 mask_dither_type,
                                                 push_undo,
                                                 progress);

  if (link_layer->p->link && gimp_link_is_monitored (link_layer->p->link))
    {
      if (push_undo)
        gimp_image_undo_push_link_layer (image, NULL, link_layer);

      gimp_link_layer_render_link (link_layer);
    }
}


/*  public functions  */

/**
 * gimp_link_layer_new:
 * @image: the #GimpImage the layer should belong to
 * @text: a #GimpText object
 *
 * Creates a new link layer.
 *
 * Return value: a new #GimpLinkLayer or %NULL in case of a problem
 **/
GimpLayer *
gimp_link_layer_new (GimpImage *image,
                     GimpLink  *link)
{
  GimpLinkLayer *layer;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);

  layer =
    GIMP_LINK_LAYER (gimp_drawable_new (GIMP_TYPE_LINK_LAYER,
                                        image, NULL,
                                        0, 0, 1, 1,
                                        gimp_image_get_layer_format (image,
                                                                     TRUE)));
  gimp_layer_set_mode (GIMP_LAYER (layer),
                       gimp_image_get_default_new_layer_mode (image),
                       FALSE);

  if (! gimp_link_layer_set_link (layer, link, FALSE))
    g_clear_object (&layer);

  return GIMP_LAYER (layer);
}

GimpLink *
gimp_link_layer_get_link (GimpLinkLayer *layer)
{
  g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), NULL);

  return layer->p->link;
}

gboolean
gimp_link_layer_set_link (GimpLinkLayer *layer,
                          GimpLink      *link,
                          gboolean       push_undo)
{
  return gimp_link_layer_set_link_with_matrix (layer, link, NULL,
                                               GIMP_INTERPOLATION_NONE,
                                               0, 0, push_undo);
}

gboolean
gimp_link_layer_set_link_with_matrix (GimpLinkLayer         *layer,
                                      GimpLink              *link,
                                      GimpMatrix3           *matrix,
                                      GimpInterpolationType  interpolation_type,
                                      gint                   offset_x,
                                      gint                   offset_y,
                                      gboolean               push_undo)
{
  gboolean rendered = FALSE;

  g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);
  g_return_val_if_fail (GIMP_IS_LINK (link), FALSE);

  if (gimp_item_is_attached (GIMP_ITEM (layer)) && push_undo)
    gimp_image_undo_push_link_layer (gimp_item_get_image (GIMP_ITEM (layer)),
                                     _("Set layer link"), layer);

  if (layer->p->link)
    {
      g_signal_handlers_disconnect_by_func (layer->p->link,
                                            G_CALLBACK (gimp_link_layer_render_full),
                                            layer);

    }

  g_set_object (&layer->p->link, link);

  if (link)
    {
      g_signal_connect_object (link, "changed",
                               G_CALLBACK (gimp_link_layer_render_full),
                               layer, G_CONNECT_SWAPPED);

      if (gimp_link_is_monitored (link))
        {
          if (matrix == NULL)
            {
              gimp_matrix3_identity (&layer->p->matrix);
              rendered = gimp_link_layer_render_link (layer);
            }
          else
            {
              if (! gimp_matrix3_is_identity (matrix))
                {
                  layer->p->offset_x = offset_x;
                  layer->p->offset_y = offset_y;
                }
              rendered = gimp_link_layer_set_transform (layer, matrix, interpolation_type, push_undo);
            }
        }
    }

  g_object_notify (G_OBJECT (layer), "link");
  gimp_viewable_invalidate_preview (GIMP_VIEWABLE (layer));

  return rendered;
}

gboolean
gimp_link_layer_is_monitored (GimpLinkLayer *layer)
{
  g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);

  return (GIMP_LINK_LAYER (layer)->p->link &&
          gimp_link_is_monitored (GIMP_LINK_LAYER (layer)->p->link));
}

gboolean
gimp_link_layer_get_transform (GimpLinkLayer         *layer,
                               GimpMatrix3           *matrix,
                               gint                  *offset_x,
                               gint                  *offset_y,
                               GimpInterpolationType *interpolation)
{
  g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);

  if (matrix)
    *matrix = layer->p->matrix;
  if (offset_x)
    *offset_x = layer->p->offset_x;
  if (offset_y)
    *offset_y = layer->p->offset_y;
  if (interpolation)
    *interpolation = layer->p->interpolation;

  return ! gimp_matrix3_is_identity (&layer->p->matrix);
}

gboolean
gimp_link_layer_set_transform (GimpLinkLayer         *layer,
                               GimpMatrix3           *matrix,
                               GimpInterpolationType  interpolation_type,
                               gboolean               push_undo)
{
  Gimp     *gimp;
  gboolean  rendered;

  g_return_val_if_fail (GIMP_IS_LINK_LAYER (layer), FALSE);
  g_return_val_if_fail (gimp_link_is_monitored (layer->p->link), FALSE);

  gimp = (gimp_item_get_image (GIMP_ITEM (layer)))->gimp;

  if (gimp_matrix3_is_identity (&layer->p->matrix))
    /* First transformation: store the initial offset. */
    gimp_item_get_offset (GIMP_ITEM (layer), &layer->p->offset_x, &layer->p->offset_y);

  gimp_item_set_offset (GIMP_ITEM (layer), layer->p->offset_x, layer->p->offset_y);

  rendered = gimp_link_layer_render_link (layer);
  if (! gimp_matrix3_is_identity (matrix))
    {
      layer->p->keep_monitoring = TRUE;
      GIMP_ITEM_CLASS (parent_class)->transform (GIMP_ITEM (layer),
                                                 gimp_get_user_context (gimp),
                                                 matrix,
                                                 GIMP_TRANSFORM_FORWARD,
                                                 interpolation_type,
                                                 GIMP_TRANSFORM_RESIZE_ADJUST,
                                                 NULL, push_undo);
      layer->p->keep_monitoring = FALSE;
    }

  layer->p->matrix = *matrix;

  /* Interpolations are used to obtain reasonable quality. Considering
   * that doing a single transform will always be better than several
   * transforms in a row, it's OK to just drop the interpolation
   * algorithm of previous transforms. We just store the last one.
   */
  layer->p->interpolation = interpolation_type;

  return rendered;
}

gboolean
gimp_item_is_link_layer (GimpItem *item)
{
  g_return_val_if_fail (GIMP_IS_ITEM (item), FALSE);

  return (GIMP_IS_LINK_LAYER (item) &&
          gimp_link_layer_is_monitored (GIMP_LINK_LAYER (item)));
}

void
gimp_link_layer_set_xcf_flags (GimpLinkLayer *layer,
                               guint32        flags)
{
  g_return_if_fail (GIMP_IS_LINK_LAYER (layer));

  gimp_rasterizable_set_auto_rename (GIMP_RASTERIZABLE (layer),
                                     (flags & LINK_LAYER_XCF_DONT_AUTO_RENAME) == 0);

  if ((flags & LINK_LAYER_XCF_MODIFIED) != 0)
    {
      gimp_link_freeze (layer->p->link);
      gimp_rasterizable_set_undo_rasterized (GIMP_RASTERIZABLE (layer), TRUE);
    }
}

guint32
gimp_link_layer_get_xcf_flags (GimpLinkLayer *link_layer)
{
  guint flags = 0;

  g_return_val_if_fail (GIMP_IS_LINK_LAYER (link_layer), 0);

  if (! gimp_rasterizable_get_auto_rename (GIMP_RASTERIZABLE (link_layer)))
    flags |= LINK_LAYER_XCF_DONT_AUTO_RENAME;

  if (! gimp_link_is_monitored (link_layer->p->link))
    flags |= LINK_LAYER_XCF_MODIFIED;

  return flags;
}


/*  private functions  */

static void
gimp_link_layer_render_full (GimpLinkLayer *layer)
{
  gimp_link_layer_render_link (layer);
  if (! gimp_matrix3_is_identity (&layer->p->matrix))
    {
      Gimp *gimp = (gimp_item_get_image (GIMP_ITEM (layer)))->gimp;

      gimp_item_set_offset (GIMP_ITEM (layer), layer->p->offset_x, layer->p->offset_y);

      layer->p->keep_monitoring = TRUE;
      GIMP_ITEM_CLASS (parent_class)->transform (GIMP_ITEM (layer),
                                                 gimp_get_user_context (gimp),
                                                 &layer->p->matrix,
                                                 GIMP_TRANSFORM_FORWARD,
                                                 layer->p->interpolation,
                                                 GIMP_TRANSFORM_RESIZE_ADJUST,
                                                 NULL, FALSE);
      layer->p->keep_monitoring = FALSE;
    }
}

static gboolean
gimp_link_layer_render_link (GimpLinkLayer *layer)
{
  GimpDrawable   *drawable;
  GimpItem       *item;
  GimpImage      *image;
  GeglBuffer     *buffer;
  gdouble         xres;
  gdouble         yres;
  gint            width;
  gint            height;

  if (! layer->p->link)
    return FALSE;

  drawable = GIMP_DRAWABLE (layer);
  item     = GIMP_ITEM (layer);
  image    = gimp_item_get_image (item);

  gimp_image_get_resolution (image, &xres, &yres);
  /* TODO: I could imagine a GimpBusyBox (to be made as GimpProgress) in
   * the later list showing layer update.
   */
  buffer = gimp_link_get_buffer (layer->p->link);

  if (! buffer)
    return FALSE;

  width  = gegl_buffer_get_width (buffer);
  height = gegl_buffer_get_height (buffer);

  g_object_freeze_notify (G_OBJECT (drawable));

  if ((width  != gimp_item_get_width  (item) ||
       height != gimp_item_get_height (item)))
    {
      GeglBuffer    *new_buffer;
      GeglRectangle  bounds;

      gimp_item_get_offset (GIMP_ITEM (drawable),
                            &bounds.x, &bounds.y);
      bounds.width  = 0;
      bounds.height = 0;

      new_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
                                    gimp_drawable_get_format (drawable));
      GIMP_DRAWABLE_CLASS (parent_class)->set_buffer (drawable,
                                                      FALSE, NULL,
                                                      new_buffer, &bounds);
      g_object_unref (new_buffer);

      if (gimp_layer_get_mask (GIMP_LAYER (layer)))
        {
          GimpLayerMask *mask = gimp_layer_get_mask (GIMP_LAYER (layer));

          static GimpContext *unused_eek = NULL;

          if (! unused_eek)
            unused_eek = gimp_context_new (image->gimp, "eek", NULL);

          gimp_item_resize (GIMP_ITEM (mask),
                            unused_eek, GIMP_FILL_TRANSPARENT,
                            width, height, 0, 0);
        }
    }

  gimp_rasterizable_auto_rename (GIMP_RASTERIZABLE (layer),
                                 GIMP_OBJECT (layer->p->link),
                                 NULL);

  gimp_gegl_buffer_copy (buffer, NULL, GEGL_ABYSS_NONE,
                         gimp_drawable_get_buffer (drawable), NULL);

  g_object_thaw_notify (G_OBJECT (drawable));

  gimp_drawable_update (drawable, 0, 0, width, height);
  gimp_image_flush (image);

  return (width > 0 && height > 0);
}

static gboolean
gimp_link_layer_is_scaling_matrix (GimpLinkLayer     *layer,
                                   const GimpMatrix3 *matrix,
                                   gint              *new_width,
                                   gint              *new_height,
                                   gint              *new_offset_x,
                                   gint              *new_offset_y)
{
  gboolean is_scaling;

  /* Scaling 3x3 matrix on a 2D plane (with optional translation). */
  is_scaling = (matrix->coeff[0][0] > 0.0 && matrix->coeff[0][1] == 0.0 &&
                matrix->coeff[1][0] < EPSILON && matrix->coeff[1][1] > 0.0 &&
                matrix->coeff[2][0] < EPSILON && matrix->coeff[2][1] < EPSILON && matrix->coeff[2][2] == 1.0);

  if (is_scaling)
    {
      gint width;
      gint height;
      gint offset_x;
      gint offset_y;

      width  = gimp_item_get_width (GIMP_ITEM (layer));
      height = gimp_item_get_height (GIMP_ITEM (layer));
      gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);

      *new_width  = (gint) (width * matrix->coeff[0][0]);
      *new_height = (gint) (height * matrix->coeff[1][1]);

      *new_offset_x = (gint) (offset_x * matrix->coeff[0][0] + matrix->coeff[0][2]);
      *new_offset_y = (gint) (offset_y * matrix->coeff[1][1] + matrix->coeff[1][2]);
    }

  return is_scaling;
}
