/* 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-2001 Spencer Kimball, Peter Mattis, and others
 *
 * 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 "libpikacolor/pikacolor.h"
#include "libpikawidgets/pikawidgets.h"

#include "tools-types.h"

#include "config/pikadisplayconfig.h"

#include "core/pika.h"
#include "core/pikadata.h"
#include "core/pikaimage.h"
#include "core/pikaimage-pick-color.h"
#include "core/pikaimage-pick-item.h"
#include "core/pikaimage-sample-points.h"
#include "core/pikamarshal.h"
#include "core/pikasamplepoint.h"

#include "widgets/pikacolormapeditor.h"
#include "widgets/pikadialogfactory.h"
#include "widgets/pikadockable.h"
#include "widgets/pikadockcontainer.h"
#include "widgets/pikapaletteeditor.h"
#include "widgets/pikawidgets-utils.h"
#include "widgets/pikawindowstrategy.h"

#include "display/pikacanvasitem.h"
#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-appearance.h"

#include "pikacoloroptions.h"
#include "pikacolortool.h"
#include "pikasamplepointtool.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


enum
{
  PICKED,
  LAST_SIGNAL
};


/*  local function prototypes  */

static void   pika_color_tool_finalize       (GObject               *object);

static void   pika_color_tool_button_press   (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              guint32                time,
                                              GdkModifierType        state,
                                              PikaButtonPressType    press_type,
                                              PikaDisplay           *display);
static void   pika_color_tool_button_release (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              guint32                time,
                                              GdkModifierType        state,
                                              PikaButtonReleaseType  release_type,
                                              PikaDisplay           *display);
static void   pika_color_tool_motion         (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              guint32                time,
                                              GdkModifierType        state,
                                              PikaDisplay           *display);
static void   pika_color_tool_oper_update    (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              GdkModifierType        state,
                                              gboolean               proximity,
                                              PikaDisplay           *display);
static void   pika_color_tool_cursor_update  (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              GdkModifierType        state,
                                              PikaDisplay           *display);

static void   pika_color_tool_draw           (PikaDrawTool          *draw_tool);

static gboolean
              pika_color_tool_real_can_pick  (PikaColorTool         *color_tool,
                                              const PikaCoords      *coords,
                                              PikaDisplay           *display);
static gboolean   pika_color_tool_real_pick  (PikaColorTool         *color_tool,
                                              const PikaCoords      *coords,
                                              PikaDisplay           *display,
                                              const Babl           **sample_format,
                                              gpointer               pixel,
                                              PikaRGB               *color);
static void   pika_color_tool_real_picked    (PikaColorTool         *color_tool,
                                              const PikaCoords      *coords,
                                              PikaDisplay           *display,
                                              PikaColorPickState     pick_state,
                                              const Babl            *sample_format,
                                              gpointer               pixel,
                                              const PikaRGB         *color);

static gboolean   pika_color_tool_can_pick   (PikaColorTool         *tool,
                                              const PikaCoords      *coords,
                                              PikaDisplay           *display);
static void   pika_color_tool_pick           (PikaColorTool         *tool,
                                              const PikaCoords      *coords,
                                              PikaDisplay           *display,
                                              PikaColorPickState     pick_state);


G_DEFINE_TYPE (PikaColorTool, pika_color_tool, PIKA_TYPE_DRAW_TOOL)

#define parent_class pika_color_tool_parent_class

static guint pika_color_tool_signals[LAST_SIGNAL] = { 0 };


static void
pika_color_tool_class_init (PikaColorToolClass *klass)
{
  GObjectClass      *object_class = G_OBJECT_CLASS (klass);
  PikaToolClass     *tool_class   = PIKA_TOOL_CLASS (klass);
  PikaDrawToolClass *draw_class   = PIKA_DRAW_TOOL_CLASS (klass);

  pika_color_tool_signals[PICKED] =
    g_signal_new ("picked",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (PikaColorToolClass, picked),
                  NULL, NULL,
                  pika_marshal_VOID__POINTER_OBJECT_ENUM_POINTER_POINTER_BOXED,
                  G_TYPE_NONE, 6,
                  G_TYPE_POINTER,
                  PIKA_TYPE_DISPLAY,
                  PIKA_TYPE_COLOR_PICK_STATE,
                  G_TYPE_POINTER,
                  G_TYPE_POINTER,
                  PIKA_TYPE_RGB | G_SIGNAL_TYPE_STATIC_SCOPE);

  object_class->finalize     = pika_color_tool_finalize;

  tool_class->button_press   = pika_color_tool_button_press;
  tool_class->button_release = pika_color_tool_button_release;
  tool_class->motion         = pika_color_tool_motion;
  tool_class->oper_update    = pika_color_tool_oper_update;
  tool_class->cursor_update  = pika_color_tool_cursor_update;

  draw_class->draw           = pika_color_tool_draw;

  klass->can_pick            = pika_color_tool_real_can_pick;
  klass->pick                = pika_color_tool_real_pick;
  klass->picked              = pika_color_tool_real_picked;
}

static void
pika_color_tool_init (PikaColorTool *color_tool)
{
  PikaTool *tool = PIKA_TOOL (color_tool);

  pika_tool_control_set_action_size (tool->control,
                                     "tools-color-average-radius-set");
}

static void
pika_color_tool_finalize (GObject *object)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (object);

  g_clear_object (&color_tool->options);

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

static void
pika_color_tool_button_press (PikaTool            *tool,
                              const PikaCoords    *coords,
                              guint32              time,
                              GdkModifierType      state,
                              PikaButtonPressType  press_type,
                              PikaDisplay         *display)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool);

  if (color_tool->enabled)
    {
      if (color_tool->sample_point)
        {
          pika_sample_point_tool_start_edit (tool, display,
                                             color_tool->sample_point);
        }
      else if (pika_color_tool_can_pick (color_tool, coords, display))
        {
          pika_color_tool_pick (color_tool, coords, display,
                                PIKA_COLOR_PICK_STATE_START);

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

static void
pika_color_tool_button_release (PikaTool              *tool,
                                const PikaCoords      *coords,
                                guint32                time,
                                GdkModifierType        state,
                                PikaButtonReleaseType  release_type,
                                PikaDisplay           *display)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool);

  if (color_tool->enabled)
    {
      pika_tool_control_halt (tool->control);

      if (! color_tool->sample_point &&
          pika_color_tool_can_pick (color_tool, coords, display))
        {
          pika_color_tool_pick (color_tool, coords, display,
                                PIKA_COLOR_PICK_STATE_END);
        }
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state,
                                                      release_type, display);
    }
}

static void
pika_color_tool_motion (PikaTool         *tool,
                        const PikaCoords *coords,
                        guint32           time,
                        GdkModifierType   state,
                        PikaDisplay      *display)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool);

  if (color_tool->enabled)
    {
      if (! color_tool->sample_point)
        {
          pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

          color_tool->can_pick = pika_color_tool_can_pick (color_tool,
                                                           coords, display);
          color_tool->center_x = coords->x;
          color_tool->center_y = coords->y;

          if (color_tool->can_pick)
            {
              pika_color_tool_pick (color_tool, coords, display,
                                    PIKA_COLOR_PICK_STATE_UPDATE);
            }

          pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
        }
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state,
                                              display);
    }
}

static void
pika_color_tool_oper_update (PikaTool         *tool,
                             const PikaCoords *coords,
                             GdkModifierType   state,
                             gboolean          proximity,
                             PikaDisplay      *display)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool);

  if (color_tool->enabled)
    {
      PikaDrawTool     *draw_tool    = PIKA_DRAW_TOOL (tool);
      PikaDisplayShell *shell        = pika_display_get_shell (display);
      PikaSamplePoint  *sample_point = NULL;

      pika_draw_tool_pause (draw_tool);

      if (! draw_tool->widget &&

          pika_draw_tool_is_active (draw_tool) &&
          (draw_tool->display != display ||
           ! proximity))
        {
          pika_draw_tool_stop (draw_tool);
        }

      if (pika_display_shell_get_show_sample_points (shell) &&
          proximity)
        {
          PikaImage *image         = pika_display_get_image (display);
          gint       snap_distance = display->config->snap_distance;

          sample_point =
            pika_image_pick_sample_point (image,
                                          coords->x, coords->y,
                                          FUNSCALEX (shell, snap_distance),
                                          FUNSCALEY (shell, snap_distance));
        }

      color_tool->sample_point = sample_point;

      color_tool->can_pick = pika_color_tool_can_pick (color_tool,
                                                       coords, display);
      color_tool->center_x = coords->x;
      color_tool->center_y = coords->y;

      if (! draw_tool->widget &&

          ! pika_draw_tool_is_active (draw_tool) &&
          proximity)
        {
          pika_draw_tool_start (draw_tool, display);
        }

      pika_draw_tool_resume (draw_tool);
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state,
                                                   proximity, display);
    }
}

static void
pika_color_tool_cursor_update (PikaTool         *tool,
                               const PikaCoords *coords,
                               GdkModifierType   state,
                               PikaDisplay      *display)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (tool);

  if (color_tool->enabled)
    {
      if (color_tool->sample_point)
        {
          pika_tool_set_cursor (tool, display,
                                PIKA_CURSOR_MOUSE,
                                PIKA_TOOL_CURSOR_COLOR_PICKER,
                                PIKA_CURSOR_MODIFIER_MOVE);
        }
      else
        {
          PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_BAD;

          if (pika_color_tool_can_pick (color_tool, coords, display))
            {
              switch (color_tool->pick_target)
                {
                case PIKA_COLOR_PICK_TARGET_NONE:
                  modifier = PIKA_CURSOR_MODIFIER_NONE;
                  break;
                case PIKA_COLOR_PICK_TARGET_FOREGROUND:
                  modifier = PIKA_CURSOR_MODIFIER_FOREGROUND;
                  break;
                case PIKA_COLOR_PICK_TARGET_BACKGROUND:
                  modifier = PIKA_CURSOR_MODIFIER_BACKGROUND;
                  break;
                case PIKA_COLOR_PICK_TARGET_PALETTE:
                  modifier = PIKA_CURSOR_MODIFIER_PLUS;
                  break;
                }
            }

          pika_tool_set_cursor (tool, display,
                                PIKA_CURSOR_COLOR_PICKER,
                                PIKA_TOOL_CURSOR_COLOR_PICKER,
                                modifier);
        }
    }
  else
    {
      PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state,
                                                     display);
    }
}

static void
pika_color_tool_draw (PikaDrawTool *draw_tool)
{
  PikaColorTool *color_tool = PIKA_COLOR_TOOL (draw_tool);

  PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);

  if (color_tool->enabled)
    {
      if (color_tool->sample_point)
        {
          PikaImage      *image = pika_display_get_image (draw_tool->display);
          PikaCanvasItem *item;
          gint            index;
          gint            x;
          gint            y;

          pika_sample_point_get_position (color_tool->sample_point, &x, &y);

          index = g_list_index (pika_image_get_sample_points (image),
                                color_tool->sample_point) + 1;

          item = pika_draw_tool_add_sample_point (draw_tool, x, y, index);
          pika_canvas_item_set_highlight (item, TRUE);
        }
      else if (color_tool->can_pick && color_tool->options->sample_average)
        {
          gdouble radius = color_tool->options->average_radius;

          pika_draw_tool_add_rectangle (draw_tool,
                                        FALSE,
                                        color_tool->center_x - radius,
                                        color_tool->center_y - radius,
                                        2 * radius + 1,
                                        2 * radius + 1);
        }
    }
}

static gboolean
pika_color_tool_real_can_pick (PikaColorTool    *color_tool,
                               const PikaCoords *coords,
                               PikaDisplay      *display)
{
  PikaDisplayShell *shell = pika_display_get_shell (display);
  PikaImage        *image = pika_display_get_image (display);

  return pika_image_coords_in_active_pickable (image, coords,
                                               shell->show_all,
                                               color_tool->options->sample_merged,
                                               FALSE);
}

static gboolean
pika_color_tool_real_pick (PikaColorTool     *color_tool,
                           const PikaCoords  *coords,
                           PikaDisplay       *display,
                           const Babl       **sample_format,
                           gpointer           pixel,
                           PikaRGB           *color)
{
  PikaDisplayShell *shell     = pika_display_get_shell (display);
  PikaImage        *image     = pika_display_get_image (display);
  GList            *drawables = pika_image_get_selected_drawables (image);

  g_return_val_if_fail (drawables != NULL, FALSE);

  return pika_image_pick_color (image, drawables,
                                coords->x, coords->y,
                                shell->show_all,
                                color_tool->options->sample_merged,
                                color_tool->options->sample_average,
                                color_tool->options->average_radius,
                                sample_format,
                                pixel,
                                color);
}

static void
pika_color_tool_real_picked (PikaColorTool      *color_tool,
                             const PikaCoords   *coords,
                             PikaDisplay        *display,
                             PikaColorPickState  pick_state,
                             const Babl         *sample_format,
                             gpointer            pixel,
                             const PikaRGB      *color)
{
  PikaTool          *tool  = PIKA_TOOL (color_tool);
  PikaDisplayShell  *shell = pika_display_get_shell (display);
  PikaImageWindow   *image_window;
  PikaDialogFactory *dialog_factory;
  PikaContext       *context;

  image_window   = pika_display_shell_get_window (shell);
  dialog_factory = pika_dock_container_get_dialog_factory (PIKA_DOCK_CONTAINER (image_window));

  /*  use this tool's own options here (NOT color_tool->options)  */
  context = PIKA_CONTEXT (pika_tool_get_options (tool));

  if (color_tool->pick_target == PIKA_COLOR_PICK_TARGET_FOREGROUND ||
      color_tool->pick_target == PIKA_COLOR_PICK_TARGET_BACKGROUND)
    {
      GtkWidget *widget;

      widget = pika_dialog_factory_find_widget (dialog_factory,
                                                "pika-indexed-palette");
      if (widget)
        {
          GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget));
          PikaImage *image  = pika_display_get_image (display);

          if (babl_format_is_palette (sample_format))
            {
              guchar *index  = pixel;

              pika_colormap_editor_set_index (PIKA_COLORMAP_EDITOR (editor),
                                              *index, NULL);
            }
          else if (pika_image_get_base_type (image) == PIKA_INDEXED)
            {
              /* When Sample merged is set, we don't have the index
               * information and it is possible to pick colors out of
               * the colormap (with compositing). In such a case, the
               * sample format will not be a palette format even though
               * the image is indexed. Still search if the color exists
               * in the colormap.
               * Note that even if it does, we might still pick the
               * wrong color, since several indexes may contain the same
               * color and we can't know for sure which is the right
               * one.
               */
              gint index = pika_colormap_editor_get_index (PIKA_COLORMAP_EDITOR (editor),
                                                           color);
              if (index > -1)
                pika_colormap_editor_set_index (PIKA_COLORMAP_EDITOR (editor),
                                                index, NULL);
            }
        }

      widget = pika_dialog_factory_find_widget (dialog_factory,
                                                "pika-palette-editor");
      if (widget)
        {
          GtkWidget *editor = gtk_bin_get_child (GTK_BIN (widget));
          gint       index;

          index = pika_palette_editor_get_index (PIKA_PALETTE_EDITOR (editor),
                                                 color);
          if (index != -1)
            pika_palette_editor_set_index (PIKA_PALETTE_EDITOR (editor),
                                           index, NULL);
        }
    }

  switch (color_tool->pick_target)
    {
    case PIKA_COLOR_PICK_TARGET_NONE:
      break;

    case PIKA_COLOR_PICK_TARGET_FOREGROUND:
      pika_context_set_foreground (context, color);
      break;

    case PIKA_COLOR_PICK_TARGET_BACKGROUND:
      pika_context_set_background (context, color);
      break;

    case PIKA_COLOR_PICK_TARGET_PALETTE:
      {
        GdkMonitor *monitor = pika_widget_get_monitor (GTK_WIDGET (shell));
        GtkWidget  *dockable;

        dockable =
          pika_window_strategy_show_dockable_dialog (PIKA_WINDOW_STRATEGY (pika_get_window_strategy (display->pika)),
                                                     display->pika,
                                                     dialog_factory,
                                                     monitor,
                                                     "pika-palette-editor");

        if (dockable)
          {
            GtkWidget *palette_editor;
            PikaData  *data;

            /* don't blink like mad when updating */
            if (pick_state != PIKA_COLOR_PICK_STATE_START)
              pika_widget_blink_cancel (dockable);

            palette_editor = gtk_bin_get_child (GTK_BIN (dockable));

            data = pika_data_editor_get_data (PIKA_DATA_EDITOR (palette_editor));

            if (! data)
              {
                data = PIKA_DATA (pika_context_get_palette (context));

                pika_data_editor_set_data (PIKA_DATA_EDITOR (palette_editor),
                                           data);
              }

            pika_palette_editor_pick_color (PIKA_PALETTE_EDITOR (palette_editor),
                                            color, pick_state);
          }
      }
      break;
    }
}

static gboolean
pika_color_tool_can_pick (PikaColorTool    *tool,
                          const PikaCoords *coords,
                          PikaDisplay      *display)
{
  PikaColorToolClass *klass;

  klass = PIKA_COLOR_TOOL_GET_CLASS (tool);

  return klass->can_pick && klass->can_pick (tool, coords, display);
}

static void
pika_color_tool_pick (PikaColorTool      *tool,
                      const PikaCoords   *coords,
                      PikaDisplay        *display,
                      PikaColorPickState  pick_state)
{
  PikaColorToolClass *klass;
  const Babl         *sample_format;
  gdouble             pixel[4];
  PikaRGB             color;

  klass = PIKA_COLOR_TOOL_GET_CLASS (tool);

  if (klass->pick &&
      klass->pick (tool, coords, display, &sample_format, pixel, &color))
    {
      g_signal_emit (tool, pika_color_tool_signals[PICKED], 0,
                     coords, display, pick_state,
                     sample_format, pixel, &color);
    }
}


/*  public functions  */

void
pika_color_tool_enable (PikaColorTool    *color_tool,
                        PikaColorOptions *options)
{
  PikaTool *tool;

  g_return_if_fail (PIKA_IS_COLOR_TOOL (color_tool));
  g_return_if_fail (PIKA_IS_COLOR_OPTIONS (options));

  tool = PIKA_TOOL (color_tool);

  if (pika_tool_control_is_active (tool->control))
    {
      g_warning ("Trying to enable PikaColorTool while it is active.");
      return;
    }

  g_set_object (&color_tool->options, options);

  /*  color picking doesn't snap, see bug #768058  */
  color_tool->saved_snap_to = pika_tool_control_get_snap_to (tool->control);
  pika_tool_control_set_snap_to (tool->control, FALSE);

  color_tool->enabled = TRUE;
}

void
pika_color_tool_disable (PikaColorTool *color_tool)
{
  PikaTool *tool;

  g_return_if_fail (PIKA_IS_COLOR_TOOL (color_tool));

  tool = PIKA_TOOL (color_tool);

  if (pika_tool_control_is_active (tool->control))
    {
      g_warning ("Trying to disable PikaColorTool while it is active.");
      return;
    }

  g_clear_object (&color_tool->options);

  pika_tool_control_set_snap_to (tool->control, color_tool->saved_snap_to);
  color_tool->saved_snap_to = FALSE;

  color_tool->enabled = FALSE;
}

gboolean
pika_color_tool_is_enabled (PikaColorTool *color_tool)
{
  return color_tool->enabled;
}
