/* 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
 *
 * Major improvement to support polygonal segments
 * Copyright (C) 2008 Martin Nordholts
 *
 * 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/pikachannel.h"
#include "core/pikachannel-select.h"
#include "core/pikaimage.h"
#include "core/pikalayer-floating-selection.h"

#include "widgets/pikahelp-ids.h"

#include "display/pikadisplay.h"
#include "display/pikatoolpolygon.h"

#include "pikafreeselecttool.h"
#include "pikaselectionoptions.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


struct _PikaFreeSelectToolPrivate
{
  gboolean        started;
  gboolean        changed;

  /* The selection operation active when the tool was started */
  PikaChannelOps  operation_at_start;
};


static void       pika_free_select_tool_control         (PikaTool              *tool,
                                                         PikaToolAction         action,
                                                         PikaDisplay           *display);
static void       pika_free_select_tool_button_press    (PikaTool              *tool,
                                                         const PikaCoords      *coords,
                                                         guint32                time,
                                                         GdkModifierType        state,
                                                         PikaButtonPressType    press_type,
                                                         PikaDisplay           *display);
static void       pika_free_select_tool_button_release  (PikaTool              *tool,
                                                         const PikaCoords      *coords,
                                                         guint32                time,
                                                         GdkModifierType        state,
                                                         PikaButtonReleaseType  release_type,
                                                         PikaDisplay           *display);
static void       pika_free_select_tool_options_notify  (PikaTool              *tool,
                                                         PikaToolOptions       *options,
                                                         const GParamSpec      *pspec);

static gboolean   pika_free_select_tool_have_selection  (PikaSelectionTool     *sel_tool,
                                                         PikaDisplay           *display);

static void       pika_free_select_tool_change_complete (PikaPolygonSelectTool *poly_sel,
                                                         PikaDisplay           *display);

static void       pika_free_select_tool_commit          (PikaFreeSelectTool    *free_sel,
                                                         PikaDisplay           *display);
static void       pika_free_select_tool_halt            (PikaFreeSelectTool    *free_sel);

static gboolean   pika_free_select_tool_select          (PikaFreeSelectTool    *free_sel,
                                                         PikaDisplay           *display);


G_DEFINE_TYPE_WITH_PRIVATE (PikaFreeSelectTool, pika_free_select_tool,
                            PIKA_TYPE_POLYGON_SELECT_TOOL)

#define parent_class pika_free_select_tool_parent_class


void
pika_free_select_tool_register (PikaToolRegisterCallback  callback,
                                gpointer                  data)
{
  (* callback) (PIKA_TYPE_FREE_SELECT_TOOL,
                PIKA_TYPE_SELECTION_OPTIONS,
                pika_selection_options_gui,
                0,
                "pika-free-select-tool",
                _("Free Select"),
                _("Free Select Tool: Select a hand-drawn region with free "
                  "and polygonal segments"),
                N_("_Free Select"), "F",
                NULL, PIKA_HELP_TOOL_FREE_SELECT,
                PIKA_ICON_TOOL_FREE_SELECT,
                data);
}

static void
pika_free_select_tool_class_init (PikaFreeSelectToolClass *klass)
{
  PikaToolClass              *tool_class     = PIKA_TOOL_CLASS (klass);
  PikaSelectionToolClass     *sel_class      = PIKA_SELECTION_TOOL_CLASS (klass);
  PikaPolygonSelectToolClass *poly_sel_class = PIKA_POLYGON_SELECT_TOOL_CLASS (klass);

  tool_class->control             = pika_free_select_tool_control;
  tool_class->button_press        = pika_free_select_tool_button_press;
  tool_class->button_release      = pika_free_select_tool_button_release;
  tool_class->options_notify      = pika_free_select_tool_options_notify;

  sel_class->have_selection       = pika_free_select_tool_have_selection;

  poly_sel_class->change_complete = pika_free_select_tool_change_complete;
}

static void
pika_free_select_tool_init (PikaFreeSelectTool *free_sel)
{
  PikaTool          *tool     = PIKA_TOOL (free_sel);
  PikaSelectionTool *sel_tool = PIKA_SELECTION_TOOL (tool);

  free_sel->priv = pika_free_select_tool_get_instance_private (free_sel);

  pika_tool_control_set_preserve     (tool->control, FALSE);
  pika_tool_control_set_dirty_mask   (tool->control,
                                      PIKA_DIRTY_SELECTION);
  pika_tool_control_set_dirty_action (tool->control,
                                      PIKA_TOOL_ACTION_COMMIT);
  pika_tool_control_set_tool_cursor  (tool->control,
                                      PIKA_TOOL_CURSOR_FREE_SELECT);

  sel_tool->allow_move = TRUE;
}

static void
pika_free_select_tool_control (PikaTool       *tool,
                               PikaToolAction  action,
                               PikaDisplay    *display)
{
  PikaFreeSelectTool *free_sel = PIKA_FREE_SELECT_TOOL (tool);

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

    case PIKA_TOOL_ACTION_HALT:
      pika_free_select_tool_halt (free_sel);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      pika_free_select_tool_commit (free_sel, display);
      break;
    }

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

static void
pika_free_select_tool_button_press (PikaTool            *tool,
                                    const PikaCoords    *coords,
                                    guint32              time,
                                    GdkModifierType      state,
                                    PikaButtonPressType  press_type,
                                    PikaDisplay         *display)
{
  PikaSelectionOptions      *options  = PIKA_SELECTION_TOOL_GET_OPTIONS (tool);
  PikaFreeSelectTool        *free_sel = PIKA_FREE_SELECT_TOOL (tool);
  PikaPolygonSelectTool     *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool);
  PikaFreeSelectToolPrivate *priv     = free_sel->priv;

  if (press_type == PIKA_BUTTON_PRESS_NORMAL &&
      pika_selection_tool_start_edit (PIKA_SELECTION_TOOL (poly_sel),
                                      display, coords))
    return;

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

  if (press_type == PIKA_BUTTON_PRESS_NORMAL &&
      pika_polygon_select_tool_is_grabbed (poly_sel))
    {
      if (! priv->started)
        {
          priv->started            = TRUE;
          priv->operation_at_start = options->operation;
        }

      pika_selection_tool_start_change (
        PIKA_SELECTION_TOOL (tool),
        ! pika_polygon_select_tool_is_closed (poly_sel),
        priv->operation_at_start);

      priv->changed = FALSE;
    }
}

static void
pika_free_select_tool_button_release (PikaTool              *tool,
                                      const PikaCoords      *coords,
                                      guint32                time,
                                      GdkModifierType        state,
                                      PikaButtonReleaseType  release_type,
                                      PikaDisplay           *display)
{
  PikaFreeSelectTool        *free_sel = PIKA_FREE_SELECT_TOOL (tool);
  PikaPolygonSelectTool     *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool);
  PikaFreeSelectToolPrivate *priv     = free_sel->priv;

  if (pika_polygon_select_tool_is_grabbed (poly_sel))
    {
      pika_selection_tool_end_change (PIKA_SELECTION_TOOL (tool),
                                      ! priv->changed);

      priv->changed = FALSE;
    }

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

static void
pika_free_select_tool_options_notify (PikaTool         *tool,
                                      PikaToolOptions  *options,
                                      const GParamSpec *pspec)
{
  if (! strcmp (pspec->name, "antialias") ||
      ! strcmp (pspec->name, "feather")   ||
      ! strcmp (pspec->name, "feather-radius"))
    {
      if (tool->display)
        {
          pika_free_select_tool_change_complete (
            PIKA_POLYGON_SELECT_TOOL (tool), tool->display);
        }
    }

  PIKA_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec);
}

static gboolean
pika_free_select_tool_have_selection (PikaSelectionTool *sel_tool,
                                      PikaDisplay       *display)
{
  PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (sel_tool);
  PikaTool              *tool     = PIKA_TOOL (sel_tool);

  if (display == tool->display)
    {
      gint n_points;

      pika_polygon_select_tool_get_points (poly_sel, NULL, &n_points);

      if (n_points > 2)
        return TRUE;
    }

  return PIKA_SELECTION_TOOL_CLASS (parent_class)->have_selection (sel_tool,
                                                                   display);
}

static void
pika_free_select_tool_change_complete (PikaPolygonSelectTool *poly_sel,
                                       PikaDisplay           *display)
{
  PikaFreeSelectTool        *free_sel = PIKA_FREE_SELECT_TOOL (poly_sel);
  PikaFreeSelectToolPrivate *priv     = free_sel->priv;

  priv->changed = TRUE;

  pika_selection_tool_start_change (PIKA_SELECTION_TOOL (free_sel),
                                    FALSE,
                                    priv->operation_at_start);

  if (pika_polygon_select_tool_is_closed (poly_sel))
    pika_free_select_tool_select (free_sel, display);

  pika_selection_tool_end_change (PIKA_SELECTION_TOOL (free_sel),
                                  FALSE);
}

static void
pika_free_select_tool_halt (PikaFreeSelectTool *free_sel)
{
  PikaFreeSelectToolPrivate *priv = free_sel->priv;

  priv->started = FALSE;
}

static void
pika_free_select_tool_commit (PikaFreeSelectTool *free_sel,
                              PikaDisplay        *display)
{
  PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (free_sel);

  if (! pika_polygon_select_tool_is_closed (poly_sel))
    {
      if (pika_free_select_tool_select (free_sel, display))
        pika_image_flush (pika_display_get_image (display));
    }
}

static gboolean
pika_free_select_tool_select (PikaFreeSelectTool *free_sel,
                              PikaDisplay        *display)
{
  PikaSelectionOptions      *options = PIKA_SELECTION_TOOL_GET_OPTIONS (free_sel);
  PikaTool                  *tool    = PIKA_TOOL (free_sel);
  PikaFreeSelectToolPrivate *priv    = free_sel->priv;
  PikaImage                 *image   = pika_display_get_image (display);
  const PikaVector2         *points;
  gint                       n_points;

  pika_polygon_select_tool_get_points (PIKA_POLYGON_SELECT_TOOL (free_sel),
                                       &points, &n_points);

  if (n_points > 2)
    {
      /* prevent this change from halting the tool */
      pika_tool_control_push_preserve (tool->control, TRUE);

      pika_channel_select_polygon (pika_image_get_mask (image),
                                   C_("command", "Free Select"),
                                   n_points,
                                   points,
                                   priv->operation_at_start,
                                   options->antialias,
                                   options->feather,
                                   options->feather_radius,
                                   options->feather_radius,
                                   TRUE);

      pika_tool_control_pop_preserve (tool->control);

      return TRUE;
    }

  return FALSE;
}
