/* 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 "config/pikaguiconfig.h"

#include "core/pika.h"
#include "core/pikaasync.h"
#include "core/pikacancelable.h"
#include "core/pikacontainer.h"
#include "core/pikadrawable-bucket-fill.h"
#include "core/pikadrawable-edit.h"
#include "core/pikadrawablefilter.h"
#include "core/pikaerror.h"
#include "core/pikafilloptions.h"
#include "core/pikaimage.h"
#include "core/pikaimageproxy.h"
#include "core/pikaitem.h"
#include "core/pikalineart.h"
#include "core/pikapickable.h"
#include "core/pikapickable-contiguous-region.h"
#include "core/pikaprogress.h"
#include "core/pikaprojection.h"
#include "core/pikatoolinfo.h"
#include "core/pikawaitable.h"

#include "gegl/pika-gegl-nodes.h"

#include "operations/layer-modes/pika-layer-modes.h"

#include "widgets/pikahelp-ids.h"
#include "widgets/pikawidgets-utils.h"

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

#include "pikabucketfilloptions.h"
#include "pikabucketfilltool.h"
#include "pikacoloroptions.h"
#include "pikatoolcontrol.h"
#include "pikatools-utils.h"

#include "pika-intl.h"


struct _PikaBucketFillToolPrivate
{
  PikaLineArt        *line_art;
  PikaImage          *line_art_image;
  PikaDisplayShell   *line_art_shell;
  GList              *line_art_bindings;

  /* For preview */
  GeglNode           *graph;
  GeglNode           *fill_node;
  GeglNode           *offset_node;

  GeglBuffer         *fill_mask;

  PikaDrawableFilter *filter;

  /* Temp property save */
  PikaBucketFillMode  fill_mode;
  PikaBucketFillArea  fill_area;
};


/*  local function prototypes  */

static void     pika_bucket_fill_tool_constructed      (GObject               *object);
static void     pika_bucket_fill_tool_finalize         (GObject               *object);

static void     pika_bucket_fill_tool_button_press     (PikaTool              *tool,
                                                        const PikaCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        PikaButtonPressType    press_type,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_motion           (PikaTool              *tool,
                                                        const PikaCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_button_release   (PikaTool              *tool,
                                                        const PikaCoords      *coords,
                                                        guint32                time,
                                                        GdkModifierType        state,
                                                        PikaButtonReleaseType  release_type,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_modifier_key     (PikaTool              *tool,
                                                        GdkModifierType        key,
                                                        gboolean               press,
                                                        GdkModifierType        state,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_cursor_update    (PikaTool              *tool,
                                                        const PikaCoords      *coords,
                                                        GdkModifierType        state,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_options_notify   (PikaTool              *tool,
                                                        PikaToolOptions       *options,
                                                        const GParamSpec      *pspec);

static void pika_bucket_fill_tool_line_art_computing_start (PikaBucketFillTool *tool);
static void pika_bucket_fill_tool_line_art_computing_end   (PikaBucketFillTool *tool);

static gboolean pika_bucket_fill_tool_coords_in_active_pickable
                                                       (PikaBucketFillTool    *tool,
                                                        PikaDisplay           *display,
                                                        const PikaCoords      *coords);

static void     pika_bucket_fill_tool_start            (PikaBucketFillTool    *tool,
                                                        const PikaCoords      *coords,
                                                        PikaDisplay           *display);
static void     pika_bucket_fill_tool_preview          (PikaBucketFillTool    *tool,
                                                        const PikaCoords      *coords,
                                                        PikaDisplay           *display,
                                                        PikaFillOptions       *fill_options);
static void     pika_bucket_fill_tool_commit           (PikaBucketFillTool    *tool);
static void     pika_bucket_fill_tool_halt             (PikaBucketFillTool    *tool);
static void     pika_bucket_fill_tool_filter_flush     (PikaDrawableFilter    *filter,
                                                        PikaTool              *tool);
static void     pika_bucket_fill_tool_create_graph     (PikaBucketFillTool    *tool);

static void     pika_bucket_fill_tool_reset_line_art   (PikaBucketFillTool    *tool);


G_DEFINE_TYPE_WITH_PRIVATE (PikaBucketFillTool, pika_bucket_fill_tool,
                            PIKA_TYPE_COLOR_TOOL)

#define parent_class pika_bucket_fill_tool_parent_class


void
pika_bucket_fill_tool_register (PikaToolRegisterCallback  callback,
                                gpointer                  data)
{
  (* callback) (PIKA_TYPE_BUCKET_FILL_TOOL,
                PIKA_TYPE_BUCKET_FILL_OPTIONS,
                pika_bucket_fill_options_gui,
                PIKA_CONTEXT_PROP_MASK_FOREGROUND |
                PIKA_CONTEXT_PROP_MASK_BACKGROUND |
                PIKA_CONTEXT_PROP_MASK_OPACITY    |
                PIKA_CONTEXT_PROP_MASK_PAINT_MODE |
                PIKA_CONTEXT_PROP_MASK_PATTERN,
                "pika-bucket-fill-tool",
                _("Bucket Fill"),
                _("Bucket Fill Tool: Fill selected area with a color or pattern"),
                N_("_Bucket Fill"), "<shift>B",
                NULL, PIKA_HELP_TOOL_BUCKET_FILL,
                PIKA_ICON_TOOL_BUCKET_FILL,
                data);
}

static void
pika_bucket_fill_tool_class_init (PikaBucketFillToolClass *klass)
{
  GObjectClass  *object_class = G_OBJECT_CLASS (klass);
  PikaToolClass *tool_class   = PIKA_TOOL_CLASS (klass);

  object_class->constructed  = pika_bucket_fill_tool_constructed;
  object_class->finalize     = pika_bucket_fill_tool_finalize;

  tool_class->button_press   = pika_bucket_fill_tool_button_press;
  tool_class->motion         = pika_bucket_fill_tool_motion;
  tool_class->button_release = pika_bucket_fill_tool_button_release;
  tool_class->modifier_key   = pika_bucket_fill_tool_modifier_key;
  tool_class->cursor_update  = pika_bucket_fill_tool_cursor_update;
  tool_class->options_notify = pika_bucket_fill_tool_options_notify;
}

static void
pika_bucket_fill_tool_init (PikaBucketFillTool *bucket_fill_tool)
{
  PikaTool *tool = PIKA_TOOL (bucket_fill_tool);

  pika_tool_control_set_scroll_lock     (tool->control, TRUE);
  pika_tool_control_set_wants_click     (tool->control, TRUE);
  pika_tool_control_set_tool_cursor     (tool->control,
                                         PIKA_TOOL_CURSOR_BUCKET_FILL);
  pika_tool_control_set_action_opacity  (tool->control,
                                         "context-opacity-set");
  pika_tool_control_set_action_object_1 (tool->control,
                                         "context-pattern-select-set");

  bucket_fill_tool->priv =
    pika_bucket_fill_tool_get_instance_private (bucket_fill_tool);
}

static void
pika_bucket_fill_tool_constructed (GObject *object)
{
  PikaTool              *tool        = PIKA_TOOL (object);
  PikaBucketFillTool    *bucket_tool = PIKA_BUCKET_FILL_TOOL (object);
  PikaBucketFillOptions *options     = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  Pika                  *pika        = PIKA_CONTEXT (options)->pika;
  PikaContext           *context     = pika_get_user_context (pika);
  GList                 *bindings    = NULL;
  GBinding              *binding;
  PikaLineArt           *line_art;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika_tool_control_set_motion_mode (tool->control,
                                     options->fill_area == PIKA_BUCKET_FILL_LINE_ART ?
                                     PIKA_MOTION_MODE_EXACT : PIKA_MOTION_MODE_COMPRESS);

  line_art = pika_context_take_line_art (context);
  binding = g_object_bind_property (options,  "fill-transparent",
                                    line_art, "select-transparent",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-threshold",
                                    line_art, "threshold",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-grow",
                                    line_art, "max-grow",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-automatic-closure",
                                    line_art, "automatic-closure",
                                    G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-gap-length",
                                    line_art, "spline-max-length",
                                    G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
  bindings = g_list_prepend (bindings, binding);
  binding = g_object_bind_property (options,  "line-art-max-gap-length",
                                    line_art, "segment-max-length",
                                    G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT);
  bindings = g_list_prepend (bindings, binding);
  g_signal_connect_swapped (line_art, "computing-start",
                            G_CALLBACK (pika_bucket_fill_tool_line_art_computing_start),
                            tool);
  g_signal_connect_swapped (line_art, "computing-end",
                            G_CALLBACK (pika_bucket_fill_tool_line_art_computing_end),
                            tool);
  pika_line_art_bind_gap_length (line_art, TRUE);
  bucket_tool->priv->line_art = line_art;
  bucket_tool->priv->line_art_bindings = bindings;

  pika_bucket_fill_tool_reset_line_art (bucket_tool);

  g_signal_connect_swapped (options, "notify::line-art-source",
                            G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                            tool);
  g_signal_connect_swapped (context, "display-changed",
                            G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                            tool);

  PIKA_COLOR_TOOL (tool)->pick_target =
    (options->fill_mode == PIKA_BUCKET_FILL_BG) ?
    PIKA_COLOR_PICK_TARGET_BACKGROUND : PIKA_COLOR_PICK_TARGET_FOREGROUND;
}

static void
pika_bucket_fill_tool_finalize (GObject *object)
{
  PikaBucketFillTool    *tool     = PIKA_BUCKET_FILL_TOOL (object);
  PikaBucketFillOptions *options  = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  Pika                  *pika     = PIKA_CONTEXT (options)->pika;
  PikaContext           *context  = pika_get_user_context (pika);

  if (tool->priv->line_art_image)
    {
      g_signal_handlers_disconnect_by_data (pika_image_get_layers (tool->priv->line_art_image), tool);
      g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
      tool->priv->line_art_image = NULL;
    }
  if (tool->priv->line_art_shell)
    {
      g_signal_handlers_disconnect_by_func (
        tool->priv->line_art_shell,
        pika_bucket_fill_tool_reset_line_art,
        tool);
      tool->priv->line_art_shell = NULL;
    }

  /* We don't free the line art object, but gives temporary ownership to
   * the user context which will free it if a timer runs out.
   *
   * This way, we allow people to not suffer a new computational delay
   * if for instance they just needed to switch tools for a few seconds
   * while the source layer stays the same.
   */
  g_signal_handlers_disconnect_by_data (tool->priv->line_art, tool);
  g_list_free_full (tool->priv->line_art_bindings,
                    (GDestroyNotify) g_binding_unbind);
  pika_context_store_line_art (context, tool->priv->line_art);
  tool->priv->line_art = NULL;

  g_signal_handlers_disconnect_by_data (options, tool);
  g_signal_handlers_disconnect_by_data (context, tool);

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

static gboolean
pika_bucket_fill_tool_coords_in_active_pickable (PikaBucketFillTool *tool,
                                                 PikaDisplay        *display,
                                                 const PikaCoords   *coords)
{
  PikaBucketFillOptions *options       = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaDisplayShell      *shell         = pika_display_get_shell (display);
  PikaImage             *image         = pika_display_get_image (display);
  gboolean               sample_merged = FALSE;

  switch (options->fill_area)
    {
    case PIKA_BUCKET_FILL_SELECTION:
      break;

    case PIKA_BUCKET_FILL_SIMILAR_COLORS:
      sample_merged = options->sample_merged;
      break;

    case PIKA_BUCKET_FILL_LINE_ART:
      sample_merged = options->line_art_source ==
                      PIKA_LINE_ART_SOURCE_SAMPLE_MERGED;
      break;
    }

  return pika_image_coords_in_active_pickable (image, coords,
                                               shell->show_all, sample_merged,
                                               TRUE);
}

static void
pika_bucket_fill_tool_start (PikaBucketFillTool *tool,
                             const PikaCoords   *coords,
                             PikaDisplay        *display)
{
  PikaBucketFillOptions *options   = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaContext           *context   = PIKA_CONTEXT (options);
  PikaImage             *image     = pika_display_get_image (display);
  GList                 *drawables = pika_image_get_selected_drawables (image);

  g_return_if_fail (! tool->priv->filter);
  g_return_if_fail (g_list_length (drawables) == 1);

  pika_line_art_freeze (tool->priv->line_art);

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

  pika_bucket_fill_tool_create_graph (tool);

  tool->priv->filter = pika_drawable_filter_new (drawables->data, _("Bucket fill"),
                                                 tool->priv->graph,
                                                 PIKA_ICON_TOOL_BUCKET_FILL);

  pika_drawable_filter_set_region (tool->priv->filter,
                                   PIKA_FILTER_REGION_DRAWABLE);

  /* We only set these here, and don't need to update it since we assume
   * the settings can't change while the fill started.
   */
  pika_drawable_filter_set_mode (tool->priv->filter,
                                 pika_context_get_paint_mode (context),
                                 PIKA_LAYER_COLOR_SPACE_AUTO,
                                 PIKA_LAYER_COLOR_SPACE_AUTO,
                                 pika_layer_mode_get_paint_composite_mode (pika_context_get_paint_mode (context)));
  pika_drawable_filter_set_opacity (tool->priv->filter,
                                    pika_context_get_opacity (context));

  g_signal_connect (tool->priv->filter, "flush",
                    G_CALLBACK (pika_bucket_fill_tool_filter_flush),
                    tool);
}

static void
pika_bucket_fill_tool_preview (PikaBucketFillTool *tool,
                               const PikaCoords   *coords,
                               PikaDisplay        *display,
                               PikaFillOptions    *fill_options)
{
  PikaBucketFillOptions *options   = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaDisplayShell      *shell     = pika_display_get_shell (display);
  PikaImage             *image     = pika_display_get_image (display);
  GList                 *drawables = pika_image_get_selected_drawables (image);
  PikaDrawable          *drawable;

  g_return_if_fail (g_list_length (drawables) == 1);

  drawable = drawables->data;
  g_list_free (drawables);

  if (tool->priv->filter)
    {
      GeglBuffer *fill     = NULL;
      gdouble     x        = coords->x;
      gdouble     y        = coords->y;

      if (options->fill_area == PIKA_BUCKET_FILL_SIMILAR_COLORS)
        {
          if (! options->sample_merged)
            {
              gint off_x, off_y;

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

              x -= (gdouble) off_x;
              y -= (gdouble) off_y;
            }
          fill = pika_drawable_get_bucket_fill_buffer (drawable,
                                                       fill_options,
                                                       options->fill_transparent,
                                                       options->fill_criterion,
                                                       options->threshold / 255.0,
                                                       shell->show_all,
                                                       options->sample_merged,
                                                       options->diagonal_neighbors,
                                                       x, y,
                                                       &tool->priv->fill_mask,
                                                       &x, &y, NULL, NULL);
        }
      else
        {
          gint source_off_x = 0;
          gint source_off_y = 0;

          if (options->line_art_source != PIKA_LINE_ART_SOURCE_SAMPLE_MERGED)
            {
              PikaPickable *input;

              input = pika_line_art_get_input (tool->priv->line_art);
              g_return_if_fail (PIKA_IS_ITEM (input));

              pika_item_get_offset (PIKA_ITEM (input), &source_off_x, &source_off_y);

              x -= (gdouble) source_off_x;
              y -= (gdouble) source_off_y;
            }
          fill = pika_drawable_get_line_art_fill_buffer (drawable,
                                                         tool->priv->line_art,
                                                         fill_options,
                                                         options->line_art_source ==
                                                         PIKA_LINE_ART_SOURCE_SAMPLE_MERGED,
                                                         options->fill_as_line_art &&
                                                         options->fill_mode != PIKA_BUCKET_FILL_PATTERN,
                                                         options->fill_as_line_art_threshold / 255.0,
                                                         options->line_art_stroke,
                                                         options->stroke_options,
                                                         x, y,
                                                         &tool->priv->fill_mask,
                                                         &x, &y, NULL, NULL);
          if (options->line_art_source != PIKA_LINE_ART_SOURCE_SAMPLE_MERGED)
            {
              gint off_x, off_y;

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

              x -= (gdouble) off_x - (gdouble) source_off_x;
              y -= (gdouble) off_y - (gdouble) source_off_y;
            }
        }
      if (fill)
        {
          gegl_node_set (tool->priv->fill_node,
                         "buffer", fill,
                         NULL);
          gegl_node_set (tool->priv->offset_node,
                         "x", x,
                         "y", y,
                         NULL);
          pika_drawable_filter_apply (tool->priv->filter, NULL);
          g_object_unref (fill);
        }
    }
}

static void
pika_bucket_fill_tool_commit (PikaBucketFillTool *tool)
{
  if (tool->priv->filter)
    {
      pika_drawable_filter_commit (tool->priv->filter,
                                   PIKA_PROGRESS (tool), FALSE);
      pika_image_flush (pika_display_get_image (PIKA_TOOL (tool)->display));
    }
}

static void
pika_bucket_fill_tool_halt (PikaBucketFillTool *tool)
{
  if (tool->priv->graph)
    {
      g_clear_object (&tool->priv->graph);
      tool->priv->fill_node   = NULL;
      tool->priv->offset_node = NULL;
    }

  if (tool->priv->filter)
    {
      pika_drawable_filter_abort (tool->priv->filter);
      g_clear_object (&tool->priv->filter);
    }

  g_clear_object (&tool->priv->fill_mask);

  if (pika_line_art_is_frozen (tool->priv->line_art))
    pika_line_art_thaw (tool->priv->line_art);

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

static void
pika_bucket_fill_tool_filter_flush (PikaDrawableFilter *filter,
                                    PikaTool           *tool)
{
  PikaImage *image = pika_display_get_image (tool->display);

  pika_projection_flush (pika_image_get_projection (image));
}

static void
pika_bucket_fill_tool_create_graph (PikaBucketFillTool *tool)
{
  GeglNode *graph;
  GeglNode *output;
  GeglNode *fill_node;
  GeglNode *offset_node;

  g_return_if_fail (! tool->priv->graph     &&
                    ! tool->priv->fill_node &&
                    ! tool->priv->offset_node);

  graph = gegl_node_new ();

  fill_node = gegl_node_new_child (graph,
                                   "operation", "gegl:buffer-source",
                                   NULL);
  offset_node = gegl_node_new_child (graph,
                                     "operation", "gegl:translate",
                                     NULL);
  output = gegl_node_get_output_proxy (graph, "output");
  gegl_node_link_many (fill_node, offset_node, output, NULL);

  tool->priv->graph       = graph;
  tool->priv->fill_node   = fill_node;
  tool->priv->offset_node = offset_node;
}

static void
pika_bucket_fill_tool_button_press (PikaTool            *tool,
                                    const PikaCoords    *coords,
                                    guint32              time,
                                    GdkModifierType      state,
                                    PikaButtonPressType  press_type,
                                    PikaDisplay         *display)
{
  PikaBucketFillTool    *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool);
  PikaBucketFillOptions *options     = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaGuiConfig         *config      = PIKA_GUI_CONFIG (display->pika->config);
  PikaImage             *image       = pika_display_get_image (display);
  PikaItem              *locked_item = NULL;
  GList                 *drawables   = pika_image_get_selected_drawables (image);
  PikaDrawable          *drawable;

  if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
    {
      PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state,
                                                    press_type, display);
      return;
    }

  if (g_list_length (drawables) != 1)
    {
      if (g_list_length (drawables) > 1)
        pika_tool_message_literal (tool, display,
                                   _("Cannot fill multiple layers. Select only one layer."));
      else
        pika_tool_message_literal (tool, display, _("No selected drawables."));

      g_list_free (drawables);
      return;
    }

  drawable = drawables->data;
  g_list_free (drawables);

  if (pika_viewable_get_children (PIKA_VIEWABLE (drawable)))
    {
      pika_tool_message_literal (tool, display,
                                 _("Cannot modify the pixels of layer groups."));
      return;
    }

  if (! pika_item_is_visible (PIKA_ITEM (drawable)) &&
      ! config->edit_non_visible)
    {
      pika_tool_message_literal (tool, display,
                                 _("The active layer is not visible."));
      return;
    }

  if (pika_item_is_content_locked (PIKA_ITEM (drawable), &locked_item))
    {
      pika_tool_message_literal (tool, display,
                                 _("The selected layer's pixels are locked."));
      pika_tools_blink_lock_box (display->pika, locked_item);
      return;
    }

  if (options->fill_area == PIKA_BUCKET_FILL_LINE_ART &&
      ! pika_line_art_get_input (bucket_tool->priv->line_art))
    {
      pika_tool_message_literal (tool, display,
                                 _("No valid line art source selected."));
      pika_blink_dockable (display->pika, "pika-tool-options", "line-art-source", NULL, NULL);
      return;
    }

  if (press_type == PIKA_BUTTON_PRESS_NORMAL &&
      pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords))
    {
      PikaContext     *context  = PIKA_CONTEXT (options);
      PikaFillOptions *fill_options;
      GError          *error = NULL;

      fill_options = pika_fill_options_new (image->pika, NULL, FALSE);

      if (pika_fill_options_set_by_fill_mode (fill_options, context,
                                              options->fill_mode,
                                              &error))
        {
          pika_fill_options_set_antialias (fill_options, options->antialias);
          pika_fill_options_set_feather (fill_options, options->feather,
                                         options->feather_radius);

          pika_context_set_opacity (PIKA_CONTEXT (fill_options),
                                    pika_context_get_opacity (context));
          pika_context_set_paint_mode (PIKA_CONTEXT (fill_options),
                                       pika_context_get_paint_mode (context));

          if (options->fill_area == PIKA_BUCKET_FILL_SELECTION)
            {
              pika_drawable_edit_fill (drawable, fill_options, NULL);
              pika_image_flush (image);
            }
          else /* PIKA_BUCKET_FILL_SIMILAR_COLORS || PIKA_BUCKET_FILL_LINE_ART */
            {
              pika_bucket_fill_tool_start (bucket_tool, coords, display);
              pika_bucket_fill_tool_preview (bucket_tool, coords, display,
                                             fill_options);
            }
        }
      else
        {
          pika_message_literal (display->pika, G_OBJECT (display),
                                PIKA_MESSAGE_WARNING, error->message);
          g_clear_error (&error);
        }

      g_object_unref (fill_options);
    }

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

static void
pika_bucket_fill_tool_motion (PikaTool         *tool,
                              const PikaCoords *coords,
                              guint32           time,
                              GdkModifierType   state,
                              PikaDisplay      *display)
{
  PikaBucketFillTool    *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool);
  PikaBucketFillOptions *options     = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaImage             *image       = pika_display_get_image (display);

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

  if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
    return;

  if (pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords) &&
      /* Fill selection only needs to happen once. */
      options->fill_area != PIKA_BUCKET_FILL_SELECTION)
    {
      PikaContext     *context  = PIKA_CONTEXT (options);
      PikaFillOptions *fill_options;
      GError          *error = NULL;

      fill_options = pika_fill_options_new (image->pika, NULL, FALSE);

      if (pika_fill_options_set_by_fill_mode (fill_options, context,
                                              options->fill_mode,
                                              &error))
        {
          pika_fill_options_set_antialias (fill_options, options->antialias);
          pika_fill_options_set_feather (fill_options, options->feather,
                                         options->feather_radius);

          pika_context_set_opacity (PIKA_CONTEXT (fill_options),
                                    pika_context_get_opacity (context));
          pika_context_set_paint_mode (PIKA_CONTEXT (fill_options),
                                       pika_context_get_paint_mode (context));

          pika_bucket_fill_tool_preview (bucket_tool, coords, display,
                                         fill_options);
        }
      else
        {
          pika_message_literal (display->pika, G_OBJECT (display),
                                PIKA_MESSAGE_WARNING, error->message);
          g_clear_error (&error);
        }

      g_object_unref (fill_options);
    }
}

static void
pika_bucket_fill_tool_button_release (PikaTool              *tool,
                                      const PikaCoords      *coords,
                                      guint32                time,
                                      GdkModifierType        state,
                                      PikaButtonReleaseType  release_type,
                                      PikaDisplay           *display)
{
  PikaBucketFillTool    *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool);
  PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
    {
      PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time,
                                                      state, release_type,
                                                      display);
      return;
    }

  if (release_type != PIKA_BUTTON_RELEASE_CANCEL)
    pika_bucket_fill_tool_commit (bucket_tool);

  if (options->fill_area != PIKA_BUCKET_FILL_SELECTION)
    pika_bucket_fill_tool_halt (bucket_tool);

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

static void
pika_bucket_fill_tool_modifier_key (PikaTool        *tool,
                                    GdkModifierType  key,
                                    gboolean         press,
                                    GdkModifierType  state,
                                    PikaDisplay     *display)
{
  PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  if (key == GDK_MOD1_MASK)
    {
      if (press)
        {
          PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_mode = options->fill_mode;

          switch (options->fill_mode)
            {
            case PIKA_BUCKET_FILL_FG:
              g_object_set (options, "fill-mode", PIKA_BUCKET_FILL_BG, NULL);
              break;

            default:
              /* PIKA_BUCKET_FILL_BG || PIKA_BUCKET_FILL_PATTERN */
              g_object_set (options, "fill-mode", PIKA_BUCKET_FILL_FG, NULL);
              break;

              break;
            }
        }
      else /* release */
        {
          g_object_set (options, "fill-mode",
                        PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_mode,
                        NULL);
        }
    }
  else if (key == pika_get_toggle_behavior_mask ())
    {
      PikaToolInfo *info = pika_get_tool_info (display->pika,
                                               "pika-color-picker-tool");

      if (! pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
        {
          switch (PIKA_COLOR_TOOL (tool)->pick_target)
            {
            case PIKA_COLOR_PICK_TARGET_BACKGROUND:
              pika_tool_push_status (tool, display,
                                     _("Click in any image to pick the "
                                       "background color"));
              break;

            case PIKA_COLOR_PICK_TARGET_FOREGROUND:
            default:
              pika_tool_push_status (tool, display,
                                     _("Click in any image to pick the "
                                       "foreground color"));
              break;
            }

          PIKA_TOOL (tool)->display = display;
          pika_color_tool_enable (PIKA_COLOR_TOOL (tool),
                                  PIKA_COLOR_OPTIONS (info->tool_options));
        }
      else
        {
          pika_tool_pop_status (tool, display);
          pika_color_tool_disable (PIKA_COLOR_TOOL (tool));
          PIKA_TOOL (tool)->display = NULL;
        }
    }
  else if (key == pika_get_extend_selection_mask ())
    {
      if (press)
        {
          PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_area = options->fill_area;

          switch (options->fill_area)
            {
            case PIKA_BUCKET_FILL_SIMILAR_COLORS:
              g_object_set (options,
                            "fill-area", PIKA_BUCKET_FILL_SELECTION,
                            NULL);
              break;

            default:
              /* PIKA_BUCKET_FILL_SELECTION || PIKA_BUCKET_FILL_LINE_ART */
              g_object_set (options,
                            "fill-area", PIKA_BUCKET_FILL_SIMILAR_COLORS,
                            NULL);
              break;
            }
        }
      else /* release */
        {
          g_object_set (options, "fill-area",
                        PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_area,
                        NULL);
        }
    }
}

static void
pika_bucket_fill_tool_cursor_update (PikaTool         *tool,
                                     const PikaCoords *coords,
                                     GdkModifierType   state,
                                     PikaDisplay      *display)
{
  PikaBucketFillTool    *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool);
  PikaBucketFillOptions *options     = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaGuiConfig         *config      = PIKA_GUI_CONFIG (display->pika->config);
  PikaCursorModifier     modifier    = PIKA_CURSOR_MODIFIER_BAD;
  PikaImage             *image       = pika_display_get_image (display);

  if (pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool,
                                                       display, coords))
    {
      GList        *drawables = pika_image_get_selected_drawables (image);
      PikaDrawable *drawable  = NULL;

      if (g_list_length (drawables) == 1)
        drawable = drawables->data;

      if (drawable                                                   &&
          ! pika_viewable_get_children (PIKA_VIEWABLE (drawable))    &&
          ! pika_item_is_content_locked (PIKA_ITEM (drawable), NULL) &&
          (pika_item_is_visible (PIKA_ITEM (drawable)) ||
           config->edit_non_visible))
        {
          switch (options->fill_mode)
            {
            case PIKA_BUCKET_FILL_FG:
              modifier = PIKA_CURSOR_MODIFIER_FOREGROUND;
              break;

            case PIKA_BUCKET_FILL_BG:
              modifier = PIKA_CURSOR_MODIFIER_BACKGROUND;
              break;

            case PIKA_BUCKET_FILL_PATTERN:
              modifier = PIKA_CURSOR_MODIFIER_PATTERN;
              break;
            }
        }

      g_list_free (drawables);
    }

  pika_tool_control_set_cursor_modifier (tool->control, modifier);

  PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display);
}

static void
pika_bucket_fill_tool_options_notify (PikaTool         *tool,
                                      PikaToolOptions  *options,
                                      const GParamSpec *pspec)
{
  PikaBucketFillTool    *bucket_tool    = PIKA_BUCKET_FILL_TOOL (tool);
  PikaBucketFillOptions *bucket_options = PIKA_BUCKET_FILL_OPTIONS (options);

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

  if (! strcmp (pspec->name, "fill-area"))
    {
      /* We want more motion events when the tool is used in a paint tool
       * fashion. Unfortunately we only set exact mode in line art fill,
       * because we can't as easily remove events from the similar color
       * mode just because a point has already been selected  (unless
       * threshold were 0, but that's an edge case).
       */
      pika_tool_control_set_motion_mode (tool->control,
                                         bucket_options->fill_area == PIKA_BUCKET_FILL_LINE_ART ?
                                         PIKA_MOTION_MODE_EXACT : PIKA_MOTION_MODE_COMPRESS);

      pika_bucket_fill_tool_reset_line_art (bucket_tool);
    }
  else if (! strcmp (pspec->name, "fill-mode"))
    {
      if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
        pika_tool_pop_status (tool, tool->display);

      switch (bucket_options->fill_mode)
        {
        case PIKA_BUCKET_FILL_BG:
          PIKA_COLOR_TOOL (tool)->pick_target = PIKA_COLOR_PICK_TARGET_BACKGROUND;
          if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
            pika_tool_push_status (tool, tool->display,
                                   _("Click in any image to pick the "
                                     "background color"));
          break;

        case PIKA_BUCKET_FILL_FG:
        default:
          PIKA_COLOR_TOOL (tool)->pick_target = PIKA_COLOR_PICK_TARGET_FOREGROUND;
          if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool)))
            pika_tool_push_status (tool, tool->display,
                                   _("Click in any image to pick the "
                                     "foreground color"));
          break;
        }
    }
}

static void
pika_bucket_fill_tool_line_art_computing_start (PikaBucketFillTool *tool)
{
  PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  gtk_widget_show (options->line_art_busy_box);
}

static void
pika_bucket_fill_tool_line_art_computing_end (PikaBucketFillTool *tool)
{
  PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);

  gtk_widget_hide (options->line_art_busy_box);
}

static void
pika_bucket_fill_tool_reset_line_art (PikaBucketFillTool *tool)
{
  PikaBucketFillOptions *options  = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool);
  PikaLineArt           *line_art = tool->priv->line_art;
  PikaDisplayShell      *shell    = NULL;
  PikaImage             *image    = NULL;

  if (options->fill_area == PIKA_BUCKET_FILL_LINE_ART)
    {
      PikaContext *context;
      PikaDisplay *display;

      context = pika_get_user_context (PIKA_CONTEXT (options)->pika);
      display = pika_context_get_display (context);

      if (display)
        {
          shell = pika_display_get_shell (display);
          image = pika_display_get_image (display);
        }
    }

  if (image != tool->priv->line_art_image)
    {
      if (tool->priv->line_art_image)
        {
          g_signal_handlers_disconnect_by_data (pika_image_get_layers (tool->priv->line_art_image), tool);
          g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool);
        }

      tool->priv->line_art_image = image;

      if (image)
        {
          g_signal_connect_swapped (image, "selected-layers-changed",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (image, "selected-channels-changed",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);

          g_signal_connect_swapped (pika_image_get_layers (image), "add",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (pika_image_get_layers (image), "remove",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);
          g_signal_connect_swapped (pika_image_get_layers (image), "reorder",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);
        }
    }

  if (shell != tool->priv->line_art_shell)
    {
      if (tool->priv->line_art_shell)
        {
          g_signal_handlers_disconnect_by_func (
            tool->priv->line_art_shell,
            pika_bucket_fill_tool_reset_line_art,
            tool);
        }

      tool->priv->line_art_shell = shell;

      if (shell)
        {
          g_signal_connect_swapped (shell, "notify::show-all",
                                    G_CALLBACK (pika_bucket_fill_tool_reset_line_art),
                                    tool);
        }
    }

  if (image)
    {
      GList        *drawables = pika_image_get_selected_drawables (image);
      PikaDrawable *drawable  = NULL;

      if (g_list_length (drawables) == 1)
        {
          drawable = drawables->data;
          if (pika_viewable_get_children (PIKA_VIEWABLE (drawable)))
            drawable = NULL;
        }
      g_list_free (drawables);

      if (options->line_art_source == PIKA_LINE_ART_SOURCE_SAMPLE_MERGED)
        {
          PikaImageProxy *image_proxy = pika_image_proxy_new (image);

          pika_image_proxy_set_show_all (image_proxy, shell->show_all);

          pika_line_art_set_input (line_art, PIKA_PICKABLE (image_proxy));

          g_object_unref (image_proxy);
        }
      else if (drawable)
        {
          PikaItem      *parent;
          PikaContainer *container;
          PikaObject    *neighbour = NULL;
          PikaPickable  *source    = NULL;
          gint           index;

          parent = pika_item_get_parent (PIKA_ITEM (drawable));
          if (parent)
            container = pika_viewable_get_children (PIKA_VIEWABLE (parent));
          else
            container = pika_image_get_layers (image);

          index = pika_item_get_index (PIKA_ITEM (drawable));

          if (options->line_art_source == PIKA_LINE_ART_SOURCE_ACTIVE_LAYER)
            source = PIKA_PICKABLE (drawable);
          else if (options->line_art_source == PIKA_LINE_ART_SOURCE_LOWER_LAYER)
            neighbour = pika_container_get_child_by_index (container, index + 1);
          else if (options->line_art_source == PIKA_LINE_ART_SOURCE_UPPER_LAYER)
            neighbour = pika_container_get_child_by_index (container, index - 1);

          source = neighbour ? PIKA_PICKABLE (neighbour) : source;
          pika_line_art_set_input (line_art, source);
        }
      else
        {
          pika_line_art_set_input (line_art, NULL);
        }
    }
  else
    {
      pika_line_art_set_input (line_art, NULL);
    }
}
