/* 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
 *
 * 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 "libpikawidgets/pikawidgets.h"

#include "tools-types.h"

#include "core/pika.h"
#include "core/pikaimage.h"
#include "core/pikaimage-crop.h"
#include "core/pikaimage-undo.h"
#include "core/pikaitem.h"
#include "core/pikatoolinfo.h"

#include "widgets/pikahelp-ids.h"

#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikatoolrectangle.h"

#include "pikacropoptions.h"
#include "pikacroptool.h"
#include "pikarectangleoptions.h"
#include "pikatoolcontrol.h"
#include "pikatools-utils.h"

#include "pika-intl.h"


static void      pika_crop_tool_constructed                (GObject              *object);
static void      pika_crop_tool_dispose                    (GObject              *object);

static void      pika_crop_tool_control                    (PikaTool             *tool,
                                                            PikaToolAction        action,
                                                            PikaDisplay          *display);
static void      pika_crop_tool_button_press               (PikaTool             *tool,
                                                            const PikaCoords     *coords,
                                                            guint32               time,
                                                            GdkModifierType       state,
                                                            PikaButtonPressType   press_type,
                                                            PikaDisplay          *display);
static void      pika_crop_tool_button_release             (PikaTool             *tool,
                                                            const PikaCoords     *coords,
                                                            guint32               time,
                                                            GdkModifierType       state,
                                                            PikaButtonReleaseType release_type,
                                                            PikaDisplay          *display);
static void     pika_crop_tool_motion                      (PikaTool             *tool,
                                                            const PikaCoords     *coords,
                                                            guint32               time,
                                                            GdkModifierType       state,
                                                            PikaDisplay          *display);
static void      pika_crop_tool_options_notify             (PikaTool             *tool,
                                                            PikaToolOptions      *options,
                                                            const GParamSpec     *pspec);

static void      pika_crop_tool_rectangle_changed          (PikaToolWidget       *rectangle,
                                                            PikaCropTool         *crop_tool);
static void      pika_crop_tool_rectangle_response         (PikaToolWidget       *rectangle,
                                                            gint                  response_id,
                                                            PikaCropTool         *crop_tool);
static void      pika_crop_tool_rectangle_change_complete  (PikaToolRectangle    *rectangle,
                                                            PikaCropTool         *crop_tool);

static void      pika_crop_tool_start                      (PikaCropTool         *crop_tool,
                                                            PikaDisplay          *display);
static void      pika_crop_tool_commit                     (PikaCropTool         *crop_tool);
static void      pika_crop_tool_halt                       (PikaCropTool         *crop_tool);

static void      pika_crop_tool_update_option_defaults     (PikaCropTool         *crop_tool,
                                                            gboolean              ignore_pending);
static PikaRectangleConstraint
                 pika_crop_tool_get_constraint             (PikaCropTool         *crop_tool);

static void      pika_crop_tool_image_changed              (PikaCropTool         *crop_tool,
                                                            PikaImage            *image,
                                                            PikaContext          *context);
static void      pika_crop_tool_image_size_changed         (PikaCropTool         *crop_tool);
static void   pika_crop_tool_image_selected_layers_changed (PikaCropTool         *crop_tool);
static void      pika_crop_tool_layer_size_changed         (PikaCropTool         *crop_tool);

static void      pika_crop_tool_auto_shrink                (PikaCropTool         *crop_tool);


G_DEFINE_TYPE (PikaCropTool, pika_crop_tool, PIKA_TYPE_DRAW_TOOL)

#define parent_class pika_crop_tool_parent_class


/*  public functions  */

void
pika_crop_tool_register (PikaToolRegisterCallback  callback,
                         gpointer                  data)
{
  (* callback) (PIKA_TYPE_CROP_TOOL,
                PIKA_TYPE_CROP_OPTIONS,
                pika_crop_options_gui,
                PIKA_CONTEXT_PROP_MASK_FOREGROUND |
                PIKA_CONTEXT_PROP_MASK_BACKGROUND |
                PIKA_CONTEXT_PROP_MASK_PATTERN,
                "pika-crop-tool",
                _("Crop"),
                _("Crop Tool: Remove edge areas from image or layer"),
                N_("_Crop"), "<shift>C",
                NULL, PIKA_HELP_TOOL_CROP,
                PIKA_ICON_TOOL_CROP,
                data);
}

static void
pika_crop_tool_class_init (PikaCropToolClass *klass)
{
  GObjectClass  *object_class = G_OBJECT_CLASS (klass);
  PikaToolClass *tool_class   = PIKA_TOOL_CLASS (klass);

  object_class->constructed  = pika_crop_tool_constructed;
  object_class->dispose      = pika_crop_tool_dispose;

  tool_class->control        = pika_crop_tool_control;
  tool_class->button_press   = pika_crop_tool_button_press;
  tool_class->button_release = pika_crop_tool_button_release;
  tool_class->motion         = pika_crop_tool_motion;
  tool_class->options_notify = pika_crop_tool_options_notify;
}

static void
pika_crop_tool_init (PikaCropTool *crop_tool)
{
  PikaTool *tool = PIKA_TOOL (crop_tool);

  pika_tool_control_set_wants_click      (tool->control, TRUE);
  pika_tool_control_set_active_modifiers (tool->control,
                                          PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE);
  pika_tool_control_set_precision        (tool->control,
                                          PIKA_CURSOR_PRECISION_PIXEL_BORDER);
  pika_tool_control_set_cursor           (tool->control,
                                          PIKA_CURSOR_CROSSHAIR_SMALL);
  pika_tool_control_set_tool_cursor      (tool->control,
                                          PIKA_TOOL_CURSOR_CROP);

  pika_draw_tool_set_default_status (PIKA_DRAW_TOOL (tool),
                                     _("Click-Drag to draw a crop rectangle"));
}

static void
pika_crop_tool_constructed (GObject *object)
{
  PikaCropTool    *crop_tool = PIKA_CROP_TOOL (object);
  PikaContext     *context;
  PikaToolInfo    *tool_info;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  tool_info = PIKA_TOOL (crop_tool)->tool_info;

  context = pika_get_user_context (tool_info->pika);

  g_signal_connect_object (context, "image-changed",
                           G_CALLBACK (pika_crop_tool_image_changed),
                           crop_tool,
                           G_CONNECT_SWAPPED);

  /* Make sure we are connected to "size-changed" for the initial
   * image.
   */
  pika_crop_tool_image_changed (crop_tool,
                                pika_context_get_image (context),
                                context);
}

static void
pika_crop_tool_dispose (GObject *object)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (object);

  /* Clean up current_image and current_layers. */
  pika_crop_tool_image_changed (crop_tool, NULL, NULL);

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

static void
pika_crop_tool_control (PikaTool       *tool,
                        PikaToolAction  action,
                        PikaDisplay    *display)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (tool);

  switch (action)
    {
    case PIKA_TOOL_ACTION_PAUSE:
    case PIKA_TOOL_ACTION_RESUME:
      break;

    case PIKA_TOOL_ACTION_HALT:
      pika_crop_tool_halt (crop_tool);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      pika_crop_tool_commit (crop_tool);
      break;
    }

  PIKA_TOOL_CLASS (parent_class)->control (tool, action, display);
}

static void
pika_crop_tool_button_press (PikaTool            *tool,
                             const PikaCoords    *coords,
                             guint32              time,
                             GdkModifierType      state,
                             PikaButtonPressType  press_type,
                             PikaDisplay         *display)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (tool);

  if (tool->display && display != tool->display)
    pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);

  if (! tool->display)
    {
      pika_crop_tool_start (crop_tool, display);

      pika_tool_widget_hover (crop_tool->widget, coords, state, TRUE);

      /* HACK: force CREATING on a newly created rectangle; otherwise,
       * property bindings would cause the rectangle to start with the
       * size from tool options.
       */
      pika_tool_rectangle_set_function (PIKA_TOOL_RECTANGLE (crop_tool->widget),
                                        PIKA_TOOL_RECTANGLE_CREATING);
    }

  if (pika_tool_widget_button_press (crop_tool->widget, coords, time, state,
                                     press_type))
    {
      crop_tool->grab_widget = crop_tool->widget;
    }

  pika_tool_control_activate (tool->control);
}

static void
pika_crop_tool_button_release (PikaTool              *tool,
                               const PikaCoords      *coords,
                               guint32                time,
                               GdkModifierType        state,
                               PikaButtonReleaseType  release_type,
                               PikaDisplay           *display)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (tool);

  pika_tool_control_halt (tool->control);

  if (crop_tool->grab_widget)
    {
      pika_tool_widget_button_release (crop_tool->grab_widget,
                                       coords, time, state, release_type);
      crop_tool->grab_widget = NULL;
    }

  pika_tool_push_status (tool, display, _("Click or press Enter to crop"));
}

static void
pika_crop_tool_motion (PikaTool         *tool,
                       const PikaCoords *coords,
                       guint32           time,
                       GdkModifierType   state,
                       PikaDisplay      *display)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (tool);

  if (crop_tool->grab_widget)
    {
      pika_tool_widget_motion (crop_tool->grab_widget, coords, time, state);
    }
}

static void
pika_crop_tool_options_notify (PikaTool         *tool,
                               PikaToolOptions  *options,
                               const GParamSpec *pspec)
{
  PikaCropTool *crop_tool = PIKA_CROP_TOOL (tool);

  if (! strcmp (pspec->name, "layer-only") ||
      ! strcmp (pspec->name, "allow-growing"))
    {
      if (crop_tool->widget)
        {
          pika_tool_rectangle_set_constraint (PIKA_TOOL_RECTANGLE (crop_tool->widget),
                                              pika_crop_tool_get_constraint (crop_tool));
        }
      else
        {
          pika_crop_tool_update_option_defaults (crop_tool, FALSE);
        }
    }
}

static void
pika_crop_tool_rectangle_changed (PikaToolWidget *rectangle,
                                  PikaCropTool   *crop_tool)
{
}

static void
pika_crop_tool_rectangle_response (PikaToolWidget *rectangle,
                                   gint            response_id,
                                   PikaCropTool   *crop_tool)
{
  PikaTool *tool = PIKA_TOOL (crop_tool);

  switch (response_id)
    {
    case PIKA_TOOL_WIDGET_RESPONSE_CONFIRM:
      pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, tool->display);
      break;

    case PIKA_TOOL_WIDGET_RESPONSE_CANCEL:
      pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display);
      break;
    }
}

static void
pika_crop_tool_rectangle_change_complete (PikaToolRectangle *rectangle,
                                          PikaCropTool      *crop_tool)
{
  pika_crop_tool_update_option_defaults (crop_tool, FALSE);
}

static void
pika_crop_tool_start (PikaCropTool *crop_tool,
                      PikaDisplay  *display)
{
  static const gchar *properties[] =
  {
    "highlight",
    "highlight-opacity",
    "guide",
    "x",
    "y",
    "width",
    "height",
    "fixed-rule-active",
    "fixed-rule",
    "desired-fixed-width",
    "desired-fixed-height",
    "desired-fixed-size-width",
    "desired-fixed-size-height",
    "aspect-numerator",
    "aspect-denominator",
    "fixed-center"
  };

  PikaTool         *tool    = PIKA_TOOL (crop_tool);
  PikaDisplayShell *shell   = pika_display_get_shell (display);
  PikaCropOptions  *options = PIKA_CROP_TOOL_GET_OPTIONS (crop_tool);
  PikaToolWidget   *widget;
  gint              i;

  tool->display = display;

  crop_tool->widget = widget = pika_tool_rectangle_new (shell);

  g_object_set (widget,
                "status-title", _("Crop to: "),
                NULL);

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), widget);

  for (i = 0; i < G_N_ELEMENTS (properties); i++)
    {
      GBinding *binding =
        g_object_bind_property (G_OBJECT (options), properties[i],
                                G_OBJECT (widget),  properties[i],
                                G_BINDING_SYNC_CREATE |
                                G_BINDING_BIDIRECTIONAL);

      crop_tool->bindings = g_list_prepend (crop_tool->bindings, binding);
    }

  pika_rectangle_options_connect (PIKA_RECTANGLE_OPTIONS (options),
                                  pika_display_get_image (shell->display),
                                  G_CALLBACK (pika_crop_tool_auto_shrink),
                                  crop_tool);

  pika_tool_rectangle_set_constraint (PIKA_TOOL_RECTANGLE (widget),
                                      pika_crop_tool_get_constraint (crop_tool));

  g_signal_connect (widget, "changed",
                    G_CALLBACK (pika_crop_tool_rectangle_changed),
                    crop_tool);
  g_signal_connect (widget, "response",
                    G_CALLBACK (pika_crop_tool_rectangle_response),
                    crop_tool);
  g_signal_connect (widget, "change-complete",
                    G_CALLBACK (pika_crop_tool_rectangle_change_complete),
                    crop_tool);

  pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display);
}

static void
pika_crop_tool_commit (PikaCropTool *crop_tool)
{
  PikaTool *tool = PIKA_TOOL (crop_tool);

  if (tool->display)
    {
      PikaCropOptions *options = PIKA_CROP_TOOL_GET_OPTIONS (tool);
      PikaImage       *image   = pika_display_get_image (tool->display);
      gdouble          x, y;
      gdouble          x2, y2;
      gint             w, h;

      pika_tool_rectangle_get_public_rect (PIKA_TOOL_RECTANGLE (crop_tool->widget),
                                           &x, &y, &x2, &y2);
      w = x2 - x;
      h = y2 - y;

      pika_tool_pop_status (tool, tool->display);

      /* if rectangle exists, crop it */
      if (w > 0 && h > 0)
        {
          if (options->layer_only)
            {
              GList *layers = pika_image_get_selected_layers (image);
              GList *iter;
              gint   off_x, off_y;
              gchar *undo_text;

              if (! layers)
                {
                  pika_tool_message_literal (tool, tool->display,
                                             _("There are no selected layers to crop."));
                  return;
                }

              for (iter = layers; iter; iter = iter->next)
                if (! pika_item_is_content_locked (PIKA_ITEM (iter->data), NULL))
                  break;

              if (iter == NULL)
                {
                  pika_tool_message_literal (tool, tool->display,
                                             _("All selected layers' pixels are locked."));
                  pika_tools_blink_lock_box (tool->display->pika, PIKA_ITEM (layers->data));
                  return;
                }

              undo_text = ngettext ("Resize Layer", "Resize %d layers",
                                    g_list_length (layers));
              undo_text = g_strdup_printf (undo_text, g_list_length (layers));
              pika_image_undo_group_start (image,
                                           PIKA_UNDO_GROUP_IMAGE_CROP,
                                           undo_text);
              g_free (undo_text);
              for (iter = layers; iter; iter = iter->next)
                {
                  pika_item_get_offset (PIKA_ITEM (iter->data), &off_x, &off_y);

                  off_x -= x;
                  off_y -= y;

                  pika_item_resize (PIKA_ITEM (iter->data),
                                    PIKA_CONTEXT (options), options->fill_type,
                                    w, h, off_x, off_y);
                }
              pika_image_undo_group_end (image);
            }
          else
            {
              pika_image_crop (image,
                               PIKA_CONTEXT (options), PIKA_FILL_TRANSPARENT,
                               x, y, w, h, options->delete_pixels);
            }

          pika_image_flush (image);
        }
    }
}

static void
pika_crop_tool_halt (PikaCropTool *crop_tool)
{
  PikaTool         *tool    = PIKA_TOOL (crop_tool);
  PikaCropOptions  *options = PIKA_CROP_TOOL_GET_OPTIONS (crop_tool);

  if (tool->display)
    {
      PikaDisplayShell *shell = pika_display_get_shell (tool->display);

      pika_display_shell_set_highlight (shell, NULL, 0.0);

      pika_rectangle_options_disconnect (PIKA_RECTANGLE_OPTIONS (options),
                                         G_CALLBACK (pika_crop_tool_auto_shrink),
                                         crop_tool);
    }

  if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool)))
    pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));

  /*  disconnect bindings manually so they are really gone *now*, we
   *  might be in the middle of a signal emission that keeps the
   *  widget and its bindings alive.
   */
  g_list_free_full (crop_tool->bindings, (GDestroyNotify) g_object_unref);
  crop_tool->bindings = NULL;

  pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), NULL);
  g_clear_object (&crop_tool->widget);

  tool->display   = NULL;
  g_list_free (tool->drawables);
  tool->drawables = NULL;

  pika_crop_tool_update_option_defaults (crop_tool, TRUE);
}

/**
 * pika_crop_tool_update_option_defaults:
 * @crop_tool:
 * @ignore_pending: %TRUE to ignore any pending crop rectangle.
 *
 * Sets the default Fixed: Aspect ratio and Fixed: Size option
 * properties.
 */
static void
pika_crop_tool_update_option_defaults (PikaCropTool *crop_tool,
                                       gboolean      ignore_pending)
{
  PikaTool             *tool      = PIKA_TOOL (crop_tool);
  PikaToolRectangle    *rectangle = PIKA_TOOL_RECTANGLE (crop_tool->widget);
  PikaRectangleOptions *options;

  options = PIKA_RECTANGLE_OPTIONS (PIKA_TOOL_GET_OPTIONS (tool));

  if (rectangle && ! ignore_pending)
    {
      /* There is a pending rectangle and we should not ignore it, so
       * set default Fixed: Aspect ratio to the same as the current
       * pending rectangle width/height.
       */

      pika_tool_rectangle_pending_size_set (rectangle,
                                            G_OBJECT (options),
                                            "default-aspect-numerator",
                                            "default-aspect-denominator");

      g_object_set (G_OBJECT (options),
                    "use-string-current", TRUE,
                    NULL);
    }
  else
    {
      /* There is no pending rectangle, set default Fixed: Aspect
       * ratio to that of the current image/layer.
       */

      if (! rectangle)
        {
          /* ugly hack: if we don't have a widget, construct a temporary one
           * so that we can use it to call
           * pika_tool_rectangle_constraint_size_set().
           */

          PikaContext *context = pika_get_user_context (tool->tool_info->pika);
          PikaDisplay *display = pika_context_get_display (context);

          if (display)
            {
              PikaDisplayShell *shell = pika_display_get_shell (display);

              rectangle = PIKA_TOOL_RECTANGLE (pika_tool_rectangle_new (shell));

              pika_tool_rectangle_set_constraint (
                rectangle, pika_crop_tool_get_constraint (crop_tool));
            }
        }

      if (rectangle)
        {
          pika_tool_rectangle_constraint_size_set (rectangle,
                                                   G_OBJECT (options),
                                                   "default-aspect-numerator",
                                                   "default-aspect-denominator");

          if (! crop_tool->widget)
            g_object_unref (rectangle);
        }

      g_object_set (G_OBJECT (options),
                    "use-string-current", FALSE,
                    NULL);
    }
}

static PikaRectangleConstraint
pika_crop_tool_get_constraint (PikaCropTool *crop_tool)
{
  PikaCropOptions *crop_options = PIKA_CROP_TOOL_GET_OPTIONS (crop_tool);

  if (crop_options->allow_growing)
    {
      return PIKA_RECTANGLE_CONSTRAIN_NONE;
    }
  else
    {
      return crop_options->layer_only ? PIKA_RECTANGLE_CONSTRAIN_DRAWABLE :
                                        PIKA_RECTANGLE_CONSTRAIN_IMAGE;
    }
}

static void
pika_crop_tool_image_changed (PikaCropTool *crop_tool,
                              PikaImage    *image,
                              PikaContext  *context)
{
  if (crop_tool->current_image)
    {
      g_signal_handlers_disconnect_by_func (crop_tool->current_image,
                                            pika_crop_tool_image_size_changed,
                                            NULL);
      g_signal_handlers_disconnect_by_func (crop_tool->current_image,
                                            pika_crop_tool_image_selected_layers_changed,
                                            NULL);
    }

  g_set_weak_pointer (&crop_tool->current_image, image);

  if (crop_tool->current_image)
    {
      g_signal_connect_object (crop_tool->current_image, "size-changed",
                               G_CALLBACK (pika_crop_tool_image_size_changed),
                               crop_tool,
                               G_CONNECT_SWAPPED);
      g_signal_connect_object (crop_tool->current_image, "selected-layers-changed",
                               G_CALLBACK (pika_crop_tool_image_selected_layers_changed),
                               crop_tool,
                               G_CONNECT_SWAPPED);
    }

  /* Make sure we are connected to "size-changed" for the initial
   * layer.
   */
  pika_crop_tool_image_selected_layers_changed (crop_tool);

  pika_crop_tool_update_option_defaults (PIKA_CROP_TOOL (crop_tool), FALSE);
}

static void
pika_crop_tool_image_size_changed (PikaCropTool *crop_tool)
{
  pika_crop_tool_update_option_defaults (crop_tool, FALSE);
}

static void
pika_crop_tool_image_selected_layers_changed (PikaCropTool *crop_tool)
{
  GList *iter;

  if (crop_tool->current_layers)
    {
      for (iter = crop_tool->current_layers; iter; iter = iter->next)
        {
          if (iter->data)
            {
              g_signal_handlers_disconnect_by_func (iter->data,
                                                    pika_crop_tool_layer_size_changed,
                                                    NULL);

              g_clear_weak_pointer (&iter->data);
            }
        }
      g_list_free (crop_tool->current_layers);
      crop_tool->current_layers = NULL;
    }

  if (crop_tool->current_image)
    {
      crop_tool->current_layers = pika_image_get_selected_layers (crop_tool->current_image);
      crop_tool->current_layers = g_list_copy (crop_tool->current_layers);
    }
  else
    {
      crop_tool->current_layers = NULL;
    }

  if (crop_tool->current_layers)
    {
      for (iter = crop_tool->current_layers; iter; iter = iter->next)
        {
          /* NOT g_set_weak_pointer() because the pointer is already set */
          g_object_add_weak_pointer (G_OBJECT (iter->data),
                                     (gpointer) &iter->data);

          g_signal_connect_object (iter->data, "size-changed",
                                   G_CALLBACK (pika_crop_tool_layer_size_changed),
                                   crop_tool,
                                   G_CONNECT_SWAPPED);
        }
    }

  pika_crop_tool_update_option_defaults (crop_tool, FALSE);
}

static void
pika_crop_tool_layer_size_changed (PikaCropTool *crop_tool)
{
  pika_crop_tool_update_option_defaults (crop_tool, FALSE);
}

static void
pika_crop_tool_auto_shrink (PikaCropTool *crop_tool)
{
  gboolean shrink_merged ;

  g_object_get (pika_tool_get_options (PIKA_TOOL (crop_tool)),
                "shrink-merged", &shrink_merged,
                NULL);

  pika_tool_rectangle_auto_shrink (PIKA_TOOL_RECTANGLE (crop_tool->widget),
                                   shrink_merged);
}
