/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>

#include "libpikabase/pikabase.h"

#include "core-types.h"

#include "pikaboundary.h"
#include "pikadrawable-filters.h"
#include "pikadrawable-floating-selection.h"
#include "pikaerror.h"
#include "pikaimage.h"
#include "pikaimage-undo.h"
#include "pikaimage-undo-push.h"
#include "pikalayer.h"
#include "pikalayer-floating-selection.h"
#include "pikalayermask.h"

#include "pika-intl.h"


/* public functions  */

void
floating_sel_attach (PikaLayer    *layer,
                     PikaDrawable *drawable)
{
  PikaImage *image;
  PikaLayer *floating_sel;
  PikaLayer *parent   = NULL;
  gint       position = 0;

  g_return_if_fail (PIKA_IS_LAYER (layer));
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)));
  g_return_if_fail (drawable != PIKA_DRAWABLE (layer));
  g_return_if_fail (pika_item_get_image (PIKA_ITEM (layer)) ==
                    pika_item_get_image (PIKA_ITEM (drawable)));

  image = pika_item_get_image (PIKA_ITEM (drawable));

  floating_sel = pika_image_get_floating_selection (image);

  /*  If there is already a floating selection, anchor it  */
  if (floating_sel)
    {
      floating_sel_anchor (floating_sel);

      /*  if we were pasting to the old floating selection, paste now
       *  to the drawable
       */
      if (drawable == (PikaDrawable *) floating_sel)
        {
          GList *drawables = pika_image_get_selected_drawables (image);

          g_return_if_fail (g_list_length (drawables) == 1);
          drawable = drawables->data;
          g_list_free (drawables);
        }
    }

  pika_layer_set_lock_alpha (layer, TRUE, FALSE);

  pika_layer_set_floating_sel_drawable (layer, drawable);

  /*  Floating selection layer placement, default to the top of the
   *  layers stack; parent and position are adapted according to the
   *  drawable associated with the floating selection.
   */

  if (PIKA_IS_LAYER_MASK (drawable))
    {
       PikaLayer *tmp = pika_layer_mask_get_layer (PIKA_LAYER_MASK (drawable));

       parent   = PIKA_LAYER (pika_item_get_parent (PIKA_ITEM (tmp)));
       position = pika_item_get_index (PIKA_ITEM (tmp));
    }
  else if (PIKA_IS_LAYER (drawable))
    {
      parent   = PIKA_LAYER (pika_item_get_parent (PIKA_ITEM (drawable)));
      position = pika_item_get_index (PIKA_ITEM (drawable));
    }

  pika_image_add_layer (image, layer, parent, position, TRUE);
}

void
floating_sel_anchor (PikaLayer *layer)
{
  PikaImage     *image;
  PikaDrawable  *drawable;
  PikaFilter    *filter = NULL;
  GeglRectangle  bounding_box;
  GeglRectangle  dr_bounding_box;
  gint           off_x, off_y;
  gint           dr_off_x, dr_off_y;

  g_return_if_fail (PIKA_IS_LAYER (layer));
  g_return_if_fail (pika_layer_is_floating_sel (layer));

  /* Don't let pika_image_remove_layer free the layer while we still need it */
  g_object_ref (layer);

  image = pika_item_get_image (PIKA_ITEM (layer));

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_FS_ANCHOR,
                               C_("undo-type", "Anchor Floating Selection"));

  drawable = pika_layer_get_floating_sel_drawable (layer);

  pika_item_get_offset (PIKA_ITEM (layer), &off_x, &off_y);
  pika_item_get_offset (PIKA_ITEM (drawable), &dr_off_x, &dr_off_y);

  bounding_box    = pika_drawable_get_bounding_box (PIKA_DRAWABLE (layer));
  dr_bounding_box = pika_drawable_get_bounding_box (drawable);

  bounding_box.x    += off_x;
  bounding_box.y    += off_y;

  dr_bounding_box.x += dr_off_x;
  dr_bounding_box.y += dr_off_y;

  if (pika_item_get_visible (PIKA_ITEM (layer)) &&
      gegl_rectangle_intersect (NULL, &bounding_box, &dr_bounding_box))
    {
      filter = pika_drawable_get_floating_sel_filter (drawable);
    }

  if (filter)
    {
      pika_drawable_merge_filter (drawable, filter, NULL, NULL,
                                  NULL, FALSE, FALSE, FALSE);
    }

  pika_image_remove_layer (image, layer, TRUE, NULL);

  pika_image_undo_group_end (image);

  /*  invalidate the boundaries  */
  pika_drawable_invalidate_boundary (PIKA_DRAWABLE (pika_image_get_mask (image)));

  g_object_unref (layer);
}

gboolean
floating_sel_to_layer (PikaLayer  *layer,
                       GError    **error)
{
  PikaItem  *item;
  PikaImage *image;

  g_return_val_if_fail (PIKA_IS_LAYER (layer), FALSE);
  g_return_val_if_fail (pika_layer_is_floating_sel (layer), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  item  = PIKA_ITEM (layer);
  image = pika_item_get_image (item);

  /*  Check if the floating layer belongs to a channel  */
  if (PIKA_IS_CHANNEL (pika_layer_get_floating_sel_drawable (layer)))
    {
      g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
                           _("Cannot create a new layer from the floating "
                             "selection because it belongs to a layer mask "
                             "or channel."));
      return FALSE;
    }

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_FS_TO_LAYER,
                               C_("undo-type", "Floating Selection to Layer"));

  pika_image_undo_push_fs_to_layer (image, NULL, layer);

  pika_drawable_detach_floating_sel (pika_layer_get_floating_sel_drawable (layer));
  pika_layer_set_floating_sel_drawable (layer, NULL);

  pika_item_set_visible (item, TRUE, TRUE);
  pika_layer_set_lock_alpha (layer, FALSE, TRUE);

  pika_image_undo_group_end (image);

  /* When the floating selection is converted to/from a normal layer
   * it does something resembling a name change, so emit the
   * "name-changed" signal
   */
  pika_object_name_changed (PIKA_OBJECT (layer));

  pika_drawable_update (PIKA_DRAWABLE (layer),
                        0, 0,
                        pika_item_get_width  (item),
                        pika_item_get_height (item));

  return TRUE;
}

void
floating_sel_activate_drawable (PikaLayer *layer)
{
  PikaImage    *image;
  PikaDrawable *drawable;
  GList        *selected_drawables;

  g_return_if_fail (PIKA_IS_LAYER (layer));
  g_return_if_fail (pika_layer_is_floating_sel (layer));

  image = pika_item_get_image (PIKA_ITEM (layer));

  drawable = pika_layer_get_floating_sel_drawable (layer);

  /*  set the underlying drawable to active  */
  if (PIKA_IS_LAYER_MASK (drawable))
    {
      PikaLayerMask *mask = PIKA_LAYER_MASK (drawable);

      selected_drawables = g_list_prepend (NULL, pika_layer_mask_get_layer (mask));
      pika_image_set_selected_layers (image, selected_drawables);
    }
  else if (PIKA_IS_CHANNEL (drawable))
    {
      selected_drawables = g_list_prepend (NULL, drawable);
      pika_image_set_selected_channels (image, selected_drawables);
    }
  else
    {
      selected_drawables = g_list_prepend (NULL, drawable);
      pika_image_set_selected_layers (image, selected_drawables);
    }

  g_list_free (selected_drawables);
}

const PikaBoundSeg *
floating_sel_boundary (PikaLayer *layer,
                       gint      *n_segs)
{
  g_return_val_if_fail (PIKA_IS_LAYER (layer), NULL);
  g_return_val_if_fail (pika_layer_is_floating_sel (layer), NULL);
  g_return_val_if_fail (n_segs != NULL, NULL);

  if (layer->fs.boundary_known == FALSE)
    {
      gint width, height;
      gint off_x, off_y;

      width  = pika_item_get_width  (PIKA_ITEM (layer));
      height = pika_item_get_height (PIKA_ITEM (layer));
      pika_item_get_offset (PIKA_ITEM (layer), &off_x, &off_y);

      if (layer->fs.segs)
        g_free (layer->fs.segs);

      if (pika_drawable_has_alpha (PIKA_DRAWABLE (layer)))
        {
          GeglBuffer *buffer;
          gint        i;

          /*  find the segments  */
          buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));

          layer->fs.segs = pika_boundary_find (buffer, NULL,
                                               babl_format ("A float"),
                                               PIKA_BOUNDARY_WITHIN_BOUNDS,
                                               0, 0, width, height,
                                               PIKA_BOUNDARY_HALF_WAY,
                                               &layer->fs.num_segs);

          /*  offset the segments  */
          for (i = 0; i < layer->fs.num_segs; i++)
            {
              layer->fs.segs[i].x1 += off_x;
              layer->fs.segs[i].y1 += off_y;
              layer->fs.segs[i].x2 += off_x;
              layer->fs.segs[i].y2 += off_y;
            }
        }
      else
        {
          layer->fs.num_segs = 4;
          layer->fs.segs     = g_new0 (PikaBoundSeg, 4);

          /* top */
          layer->fs.segs[0].x1 = off_x;
          layer->fs.segs[0].y1 = off_y;
          layer->fs.segs[0].x2 = off_x + width;
          layer->fs.segs[0].y2 = off_y;

          /* left */
          layer->fs.segs[1].x1 = off_x;
          layer->fs.segs[1].y1 = off_y;
          layer->fs.segs[1].x2 = off_x;
          layer->fs.segs[1].y2 = off_y + height;

          /* right */
          layer->fs.segs[2].x1 = off_x + width;
          layer->fs.segs[2].y1 = off_y;
          layer->fs.segs[2].x2 = off_x + width;
          layer->fs.segs[2].y2 = off_y + height;

          /* bottom */
          layer->fs.segs[3].x1 = off_x;
          layer->fs.segs[3].y1 = off_y + height;
          layer->fs.segs[3].x2 = off_x + width;
          layer->fs.segs[3].y2 = off_y + height;
        }

      layer->fs.boundary_known = TRUE;
    }

  *n_segs = layer->fs.num_segs;

  return layer->fs.segs;
}

void
floating_sel_invalidate (PikaLayer *layer)
{
  g_return_if_fail (PIKA_IS_LAYER (layer));
  g_return_if_fail (pika_layer_is_floating_sel (layer));

  /*  Invalidate the attached-to drawable's preview  */
  pika_viewable_invalidate_preview (PIKA_VIEWABLE (pika_layer_get_floating_sel_drawable (layer)));

  /*  Invalidate the boundary  */
  layer->fs.boundary_known = FALSE;
}
