/* 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 <string.h>

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

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

#include "core-types.h"

#include "gegl/pika-gegl-apply-operation.h"
#include "gegl/pika-gegl-loops.h"
#include "gegl/pika-gegl-mask-combine.h"

#include "pika.h"
#include "pikachannel.h"
#include "pikachannel-select.h"
#include "pikachannel-combine.h"
#include "pikacontainer.h"
#include "pikaimage.h"
#include "pikaimage-new.h"
#include "pikapickable.h"
#include "pikapickable-contiguous-region.h"
#include "pikascanconvert.h"

#include "vectors/pikastroke.h"
#include "vectors/pikavectors.h"

#include "pika-intl.h"


/*  basic selection functions  */

void
pika_channel_select_rectangle (PikaChannel    *channel,
                               gint            x,
                               gint            y,
                               gint            w,
                               gint            h,
                               PikaChannelOps  op,
                               gboolean        feather,
                               gdouble         feather_radius_x,
                               gdouble         feather_radius_y,
                               gboolean        push_undo)
{
  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));

  if (push_undo)
    pika_channel_push_undo (channel, C_("undo-type", "Rectangle Select"));

  /*  if feathering for rect, make a new mask with the
   *  rectangle and feather that with the old mask
   */
  if (feather)
    {
      PikaItem   *item = PIKA_ITEM (channel);
      GeglBuffer *add_on;

      add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                pika_item_get_width  (item),
                                                pika_item_get_height (item)),
                                babl_format ("Y float"));

      pika_gegl_mask_combine_rect (add_on, PIKA_CHANNEL_OP_REPLACE, x, y, w, h);

      pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
                               feather_radius_x,
                               feather_radius_y,
                               TRUE);

      pika_channel_combine_buffer (channel, add_on, op, 0, 0);
      g_object_unref (add_on);
    }
  else
    {
      pika_channel_combine_rect (channel, op, x, y, w, h);
    }
}

void
pika_channel_select_ellipse (PikaChannel    *channel,
                             gint            x,
                             gint            y,
                             gint            w,
                             gint            h,
                             PikaChannelOps  op,
                             gboolean        antialias,
                             gboolean        feather,
                             gdouble         feather_radius_x,
                             gdouble         feather_radius_y,
                             gboolean        push_undo)
{
  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));

  if (push_undo)
    pika_channel_push_undo (channel, C_("undo-type", "Ellipse Select"));

  /*  if feathering for rect, make a new mask with the
   *  rectangle and feather that with the old mask
   */
  if (feather)
    {
      PikaItem   *item = PIKA_ITEM (channel);
      GeglBuffer *add_on;

      add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                pika_item_get_width  (item),
                                                pika_item_get_height (item)),
                                babl_format ("Y float"));

      pika_gegl_mask_combine_ellipse (add_on, PIKA_CHANNEL_OP_REPLACE,
                                      x, y, w, h, antialias);

      pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
                               feather_radius_x,
                               feather_radius_y,
                               TRUE);

      pika_channel_combine_buffer (channel, add_on, op, 0, 0);
      g_object_unref (add_on);
    }
  else
    {
      pika_channel_combine_ellipse (channel, op, x, y, w, h, antialias);
    }
}

void
pika_channel_select_round_rect (PikaChannel         *channel,
                                gint                 x,
                                gint                 y,
                                gint                 w,
                                gint                 h,
                                gdouble              corner_radius_x,
                                gdouble              corner_radius_y,
                                PikaChannelOps       op,
                                gboolean             antialias,
                                gboolean             feather,
                                gdouble              feather_radius_x,
                                gdouble              feather_radius_y,
                                gboolean             push_undo)
{
  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));

  if (push_undo)
    pika_channel_push_undo (channel, C_("undo-type", "Rounded Rectangle Select"));

  /*  if feathering for rect, make a new mask with the
   *  rectangle and feather that with the old mask
   */
  if (feather)
    {
      PikaItem   *item = PIKA_ITEM (channel);
      GeglBuffer *add_on;

      add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                pika_item_get_width  (item),
                                                pika_item_get_height (item)),
                                babl_format ("Y float"));

      pika_gegl_mask_combine_ellipse_rect (add_on, PIKA_CHANNEL_OP_REPLACE,
                                           x, y, w, h,
                                           corner_radius_x, corner_radius_y,
                                           antialias);

      pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
                               feather_radius_x,
                               feather_radius_y,
                               TRUE);

      pika_channel_combine_buffer (channel, add_on, op, 0, 0);
      g_object_unref (add_on);
    }
  else
    {
      pika_channel_combine_ellipse_rect (channel, op, x, y, w, h,
                                         corner_radius_x, corner_radius_y,
                                         antialias);
    }
}

/*  select by PikaScanConvert functions  */

void
pika_channel_select_scan_convert (PikaChannel     *channel,
                                  const gchar     *undo_desc,
                                  PikaScanConvert *scan_convert,
                                  gint             offset_x,
                                  gint             offset_y,
                                  PikaChannelOps   op,
                                  gboolean         antialias,
                                  gboolean         feather,
                                  gdouble          feather_radius_x,
                                  gdouble          feather_radius_y,
                                  gboolean         push_undo)
{
  PikaItem   *item;
  GeglBuffer *add_on;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (undo_desc != NULL);
  g_return_if_fail (scan_convert != NULL);

  if (push_undo)
    pika_channel_push_undo (channel, undo_desc);

  item = PIKA_ITEM (channel);

  add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                            pika_item_get_width  (item),
                                            pika_item_get_height (item)),
                            babl_format ("Y float"));

  pika_scan_convert_render (scan_convert, add_on,
                            offset_x, offset_y, antialias);

  if (feather)
    pika_gegl_apply_feather (add_on, NULL, NULL, add_on, NULL,
                             feather_radius_x,
                             feather_radius_y,
                             TRUE);

  pika_channel_combine_buffer (channel, add_on, op, 0, 0);
  g_object_unref (add_on);
}

void
pika_channel_select_polygon (PikaChannel       *channel,
                             const gchar       *undo_desc,
                             gint               n_points,
                             const PikaVector2 *points,
                             PikaChannelOps     op,
                             gboolean           antialias,
                             gboolean           feather,
                             gdouble            feather_radius_x,
                             gdouble            feather_radius_y,
                             gboolean           push_undo)
{
  PikaScanConvert *scan_convert;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (undo_desc != NULL);

  scan_convert = pika_scan_convert_new ();

  pika_scan_convert_add_polyline (scan_convert, n_points, points, TRUE);

  pika_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0,
                                    op, antialias, feather,
                                    feather_radius_x, feather_radius_y,
                                    push_undo);

  pika_scan_convert_free (scan_convert);
}

void
pika_channel_select_vectors (PikaChannel    *channel,
                             const gchar    *undo_desc,
                             PikaVectors    *vectors,
                             PikaChannelOps  op,
                             gboolean        antialias,
                             gboolean        feather,
                             gdouble         feather_radius_x,
                             gdouble         feather_radius_y,
                             gboolean        push_undo)
{
  const PikaBezierDesc *bezier;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (undo_desc != NULL);
  g_return_if_fail (PIKA_IS_VECTORS (vectors));

  bezier = pika_vectors_get_bezier (vectors);

  if (bezier && bezier->num_data > 4)
    {
      PikaScanConvert *scan_convert;

      scan_convert = pika_scan_convert_new ();
      pika_scan_convert_add_bezier (scan_convert, bezier);

      pika_channel_select_scan_convert (channel, undo_desc, scan_convert, 0, 0,
                                        op, antialias, feather,
                                        feather_radius_x, feather_radius_y,
                                        push_undo);

      pika_scan_convert_free (scan_convert);
    }
}


/*  select by PikaChannel functions  */

void
pika_channel_select_buffer (PikaChannel    *channel,
                            const gchar    *undo_desc,
                            GeglBuffer     *add_on,
                            gint            offset_x,
                            gint            offset_y,
                            PikaChannelOps  op,
                            gboolean        feather,
                            gdouble         feather_radius_x,
                            gdouble         feather_radius_y)
{
  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (undo_desc != NULL);
  g_return_if_fail (GEGL_IS_BUFFER (add_on));

  pika_channel_push_undo (channel, undo_desc);

  if (feather)
    {
      PikaItem   *item = PIKA_ITEM (channel);
      GeglBuffer *add_on2;

      add_on2 = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                 pika_item_get_width  (item),
                                                 pika_item_get_height (item)),
                                 babl_format ("Y float"));

      pika_gegl_mask_combine_buffer (add_on2, add_on,
                                     PIKA_CHANNEL_OP_REPLACE,
                                     offset_x, offset_y);

      pika_gegl_apply_feather (add_on2, NULL, NULL, add_on2, NULL,
                               feather_radius_x,
                               feather_radius_y,
                               TRUE);

      pika_channel_combine_buffer (channel, add_on2, op, 0, 0);
      g_object_unref (add_on2);
    }
  else
    {
      pika_channel_combine_buffer (channel, add_on, op, offset_x, offset_y);
    }
}

void
pika_channel_select_channel (PikaChannel    *channel,
                             const gchar    *undo_desc,
                             PikaChannel    *add_on,
                             gint            offset_x,
                             gint            offset_y,
                             PikaChannelOps  op,
                             gboolean        feather,
                             gdouble         feather_radius_x,
                             gdouble         feather_radius_y)
{
  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (undo_desc != NULL);
  g_return_if_fail (PIKA_IS_CHANNEL (add_on));

  pika_channel_select_buffer (channel, undo_desc,
                              pika_drawable_get_buffer (PIKA_DRAWABLE (add_on)),
                              offset_x, offset_y, op,
                              feather,
                              feather_radius_x, feather_radius_y);
}

void
pika_channel_select_alpha (PikaChannel    *channel,
                           PikaDrawable   *drawable,
                           PikaChannelOps  op,
                           gboolean        feather,
                           gdouble         feather_radius_x,
                           gdouble         feather_radius_y)
{
  PikaItem    *item;
  PikaChannel *add_on;
  gint         off_x, off_y;
  const gchar *undo_desc = NULL;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));

  item = PIKA_ITEM (channel);

  if (pika_drawable_has_alpha (drawable))
    {
      add_on = pika_channel_new_from_alpha (pika_item_get_image (item),
                                            drawable, NULL, NULL);
    }
  else
    {
      /*  no alpha is equivalent to completely opaque alpha,
       *  so simply select the whole layer's extents.  --mitch
       */
      add_on = pika_channel_new_mask (pika_item_get_image (item),
                                      pika_item_get_width  (PIKA_ITEM (drawable)),
                                      pika_item_get_height (PIKA_ITEM (drawable)));
      pika_channel_all (add_on, FALSE);
    }

  switch (op)
    {
    case PIKA_CHANNEL_OP_ADD:
      undo_desc = C_("undo-type", "Add Alpha to Selection");
      break;
    case PIKA_CHANNEL_OP_SUBTRACT:
      undo_desc = C_("undo-type", "Subtract Alpha from Selection");
      break;
    case PIKA_CHANNEL_OP_REPLACE:
      undo_desc = C_("undo-type", "Alpha to Selection");
      break;
    case PIKA_CHANNEL_OP_INTERSECT:
      undo_desc = C_("undo-type", "Intersect Alpha with Selection");
      break;
    }

  pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);

  pika_channel_select_channel (channel, undo_desc, add_on,
                               off_x, off_y,
                               op, feather,
                               feather_radius_x,
                               feather_radius_y);
  g_object_unref (add_on);
}

void
pika_channel_select_component (PikaChannel     *channel,
                               PikaChannelType  component,
                               PikaChannelOps   op,
                               gboolean         feather,
                               gdouble          feather_radius_x,
                               gdouble          feather_radius_y)
{
  PikaItem    *item;
  PikaChannel *add_on;
  const gchar *desc;
  gchar       *undo_desc;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));

  item = PIKA_ITEM (channel);

  add_on = pika_channel_new_from_component (pika_item_get_image (item),
                                            component, NULL, NULL);

  if (feather)
    pika_channel_feather (add_on,
                          feather_radius_x,
                          feather_radius_y,
                          TRUE,
                          FALSE /* no undo */);

  pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component,
                       NULL, NULL, &desc, NULL);

  undo_desc = g_strdup_printf (C_("undo-type", "%s Channel to Selection"), desc);

  pika_channel_select_channel (channel, undo_desc, add_on,
                               0, 0, op,
                               FALSE, 0.0, 0.0);

  g_free (undo_desc);
  g_object_unref (add_on);
}

void
pika_channel_select_fuzzy (PikaChannel         *channel,
                           PikaDrawable        *drawable,
                           gboolean             sample_merged,
                           gint                 x,
                           gint                 y,
                           gfloat               threshold,
                           gboolean             select_transparent,
                           PikaSelectCriterion  select_criterion,
                           gboolean             diagonal_neighbors,
                           PikaChannelOps       op,
                           gboolean             antialias,
                           gboolean             feather,
                           gdouble              feather_radius_x,
                           gdouble              feather_radius_y)
{
  PikaPickable *pickable;
  GeglBuffer   *add_on;
  gint          add_on_x = 0;
  gint          add_on_y = 0;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));

  if (sample_merged)
    pickable = PIKA_PICKABLE (pika_item_get_image (PIKA_ITEM (drawable)));
  else
    pickable = PIKA_PICKABLE (drawable);

  add_on = pika_pickable_contiguous_region_by_seed (pickable,
                                                    antialias,
                                                    threshold,
                                                    select_transparent,
                                                    select_criterion,
                                                    diagonal_neighbors,
                                                    x, y);

  if (! sample_merged)
    pika_item_get_offset (PIKA_ITEM (drawable), &add_on_x, &add_on_y);

  pika_channel_select_buffer (channel, C_("undo-type", "Fuzzy Select"),
                              add_on, add_on_x, add_on_y,
                              op,
                              feather,
                              feather_radius_x,
                              feather_radius_y);
  g_object_unref (add_on);
}

void
pika_channel_select_by_color (PikaChannel         *channel,
                              GList               *drawables,
                              gboolean             sample_merged,
                              const PikaRGB       *color,
                              gfloat               threshold,
                              gboolean             select_transparent,
                              PikaSelectCriterion  select_criterion,
                              PikaChannelOps       op,
                              gboolean             antialias,
                              gboolean             feather,
                              gdouble              feather_radius_x,
                              gdouble              feather_radius_y)
{
  PikaPickable *pickable;
  GeglBuffer   *add_on;
  PikaImage    *image;
  PikaImage    *sel_image = NULL;
  gint          add_on_x  = 0;
  gint          add_on_y  = 0;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (drawables != NULL);
  g_return_if_fail (color != NULL);

  image = pika_item_get_image (drawables->data);
  if (sample_merged)
    {
      pickable = PIKA_PICKABLE (image);
    }
  else
    {
      if (g_list_length (drawables) == 1)
        {
          pickable = PIKA_PICKABLE (drawables->data);
        }
      else
        {
          sel_image = pika_image_new_from_drawables (image->pika, drawables, FALSE, FALSE);
          pika_container_remove (image->pika->images, PIKA_OBJECT (sel_image));

          pickable = PIKA_PICKABLE (sel_image);
          pika_pickable_flush (pickable);
        }
    }

  add_on = pika_pickable_contiguous_region_by_color (pickable,
                                                     antialias,
                                                     threshold,
                                                     select_transparent,
                                                     select_criterion,
                                                     color);

  if (! sample_merged && ! sel_image)
    pika_item_get_offset (PIKA_ITEM (drawables->data), &add_on_x, &add_on_y);

  pika_channel_select_buffer (channel, C_("undo-type", "Select by Color"),
                              add_on, add_on_x, add_on_y,
                              op,
                              feather,
                              feather_radius_x,
                              feather_radius_y);

  g_object_unref (add_on);
  if (sel_image)
    g_object_unref (sel_image);

}

void
pika_channel_select_by_index (PikaChannel    *channel,
                              PikaDrawable   *drawable,
                              gint            index,
                              PikaChannelOps  op,
                              gboolean        feather,
                              gdouble         feather_radius_x,
                              gdouble         feather_radius_y)
{
  GeglBuffer *add_on;
  gint        add_on_x = 0;
  gint        add_on_y = 0;

  g_return_if_fail (PIKA_IS_CHANNEL (channel));
  g_return_if_fail (pika_item_is_attached (PIKA_ITEM (channel)));
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
  g_return_if_fail (pika_drawable_is_indexed (drawable));

  add_on = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                            pika_item_get_width  (PIKA_ITEM (drawable)),
                                            pika_item_get_height (PIKA_ITEM (drawable))),
                            babl_format ("Y float"));

  pika_gegl_index_to_mask (pika_drawable_get_buffer (drawable), NULL,
                           pika_drawable_get_format_without_alpha (drawable),
                           add_on, NULL,
                           index);

  pika_item_get_offset (PIKA_ITEM (drawable), &add_on_x, &add_on_y);

  pika_channel_select_buffer (channel, C_("undo-type", "Select by Indexed Color"),
                              add_on, add_on_x, add_on_y,
                              op,
                              feather,
                              feather_radius_x,
                              feather_radius_y);
  g_object_unref (add_on);
}
