/* 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 "libpikamath/pikamath.h"

#include "tools-types.h"

#include "core/pikachannel.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"

#include "paint/pikasourcecore.h"
#include "paint/pikasourceoptions.h"

#include "widgets/pikawidgets-utils.h"

#include "display/pikacanvashandle.h"
#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-items.h"

#include "pikasourcetool.h"
#include "pikatoolcontrol.h"

#include "pika-intl.h"


static gboolean      pika_source_tool_has_display   (PikaTool            *tool,
                                                     PikaDisplay         *display);
static PikaDisplay * pika_source_tool_has_image     (PikaTool            *tool,
                                                     PikaImage           *image);
static void          pika_source_tool_control       (PikaTool            *tool,
                                                     PikaToolAction       action,
                                                     PikaDisplay         *display);
static void          pika_source_tool_button_press  (PikaTool            *tool,
                                                     const PikaCoords    *coords,
                                                     guint32              time,
                                                     GdkModifierType      state,
                                                     PikaButtonPressType  press_type,
                                                     PikaDisplay         *display);
static void          pika_source_tool_motion        (PikaTool            *tool,
                                                     const PikaCoords    *coords,
                                                     guint32              time,
                                                     GdkModifierType      state,
                                                     PikaDisplay         *display);
static void          pika_source_tool_cursor_update (PikaTool            *tool,
                                                     const PikaCoords    *coords,
                                                     GdkModifierType      state,
                                                     PikaDisplay         *display);
static void          pika_source_tool_modifier_key  (PikaTool            *tool,
                                                     GdkModifierType      key,
                                                     gboolean             press,
                                                     GdkModifierType      state,
                                                     PikaDisplay         *display);
static void          pika_source_tool_oper_update   (PikaTool            *tool,
                                                     const PikaCoords    *coords,
                                                     GdkModifierType      state,
                                                     gboolean             proximity,
                                                     PikaDisplay         *display);

static void          pika_source_tool_draw          (PikaDrawTool        *draw_tool);

static void          pika_source_tool_paint_prepare (PikaPaintTool       *paint_tool,
                                                     PikaDisplay         *display);

static void          pika_source_tool_set_src_display (PikaSourceTool      *source_tool,
                                                       PikaDisplay         *display);


G_DEFINE_TYPE (PikaSourceTool, pika_source_tool, PIKA_TYPE_BRUSH_TOOL)

#define parent_class pika_source_tool_parent_class


static void
pika_source_tool_class_init (PikaSourceToolClass *klass)
{
  PikaToolClass      *tool_class       = PIKA_TOOL_CLASS (klass);
  PikaDrawToolClass  *draw_tool_class  = PIKA_DRAW_TOOL_CLASS (klass);
  PikaPaintToolClass *paint_tool_class = PIKA_PAINT_TOOL_CLASS (klass);

  tool_class->has_display         = pika_source_tool_has_display;
  tool_class->has_image           = pika_source_tool_has_image;
  tool_class->control             = pika_source_tool_control;
  tool_class->button_press        = pika_source_tool_button_press;
  tool_class->motion              = pika_source_tool_motion;
  tool_class->modifier_key        = pika_source_tool_modifier_key;
  tool_class->oper_update         = pika_source_tool_oper_update;
  tool_class->cursor_update       = pika_source_tool_cursor_update;

  draw_tool_class->draw           = pika_source_tool_draw;

  paint_tool_class->paint_prepare = pika_source_tool_paint_prepare;
}

static void
pika_source_tool_init (PikaSourceTool *source)
{
  source->show_source_outline = TRUE;

  pika_paint_tool_enable_multi_paint (PIKA_PAINT_TOOL (source));
}

static gboolean
pika_source_tool_has_display (PikaTool    *tool,
                              PikaDisplay *display)
{
  PikaSourceTool *source_tool = PIKA_SOURCE_TOOL (tool);

  return (display == source_tool->src_display ||
          PIKA_TOOL_CLASS (parent_class)->has_display (tool, display));
}

static PikaDisplay *
pika_source_tool_has_image (PikaTool  *tool,
                            PikaImage *image)
{
  PikaSourceTool *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaDisplay    *display;

  display = PIKA_TOOL_CLASS (parent_class)->has_image (tool, image);

  if (! display && source_tool->src_display)
    {
      if (image && pika_display_get_image (source_tool->src_display) == image)
        display = source_tool->src_display;

      /*  NULL image means any display  */
      if (! image)
        display = source_tool->src_display;
    }

  return display;
}

static void
pika_source_tool_control (PikaTool       *tool,
                          PikaToolAction  action,
                          PikaDisplay    *display)
{
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);

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

    case PIKA_TOOL_ACTION_HALT:
      pika_source_tool_set_src_display (source_tool, NULL);
      g_object_set (options,
                    "src-drawables", NULL,
                    "src-x",         0,
                    "src-y",         0,
                    NULL);
      break;

    case PIKA_TOOL_ACTION_COMMIT:
      break;
    }

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

static void
pika_source_tool_button_press (PikaTool            *tool,
                               const PikaCoords    *coords,
                               guint32              time,
                               GdkModifierType      state,
                               PikaButtonPressType  press_type,
                               PikaDisplay         *display)
{
  PikaPaintTool     *paint_tool  = PIKA_PAINT_TOOL (tool);
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaSourceCore    *source      = PIKA_SOURCE_CORE (paint_tool->core);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);
  GdkModifierType    extend_mask = pika_get_extend_selection_mask ();
  GdkModifierType    toggle_mask = pika_get_toggle_behavior_mask ();

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  if ((state & (toggle_mask | extend_mask)) == toggle_mask)
    {
      source->set_source = TRUE;

      pika_source_tool_set_src_display (source_tool, display);
    }
  else
    {
      source->set_source = FALSE;
    }

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

  g_object_get (options,
                "src-x", &source_tool->src_x,
                "src-y", &source_tool->src_y,
                NULL);

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

static void
pika_source_tool_motion (PikaTool         *tool,
                         const PikaCoords *coords,
                         guint32           time,
                         GdkModifierType   state,
                         PikaDisplay      *display)
{
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

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

  g_object_get (options,
                "src-x", &source_tool->src_x,
                "src-y", &source_tool->src_y,
                NULL);

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
}

static void
pika_source_tool_modifier_key (PikaTool        *tool,
                               GdkModifierType  key,
                               gboolean         press,
                               GdkModifierType  state,
                               PikaDisplay     *display)
{
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaPaintTool     *paint_tool  = PIKA_PAINT_TOOL (tool);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);

  if (pika_source_core_use_source (PIKA_SOURCE_CORE (paint_tool->core),
                                   options) &&
      key == pika_get_toggle_behavior_mask ())
    {
      pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

      if (press)
        {
          paint_tool->status = source_tool->status_set_source;

          source_tool->show_source_outline = FALSE;

          source_tool->saved_precision =
            pika_tool_control_get_precision (tool->control);
          pika_tool_control_set_precision (tool->control,
                                           PIKA_CURSOR_PRECISION_PIXEL_CENTER);
        }
      else
        {
          paint_tool->status = source_tool->status_paint;

          source_tool->show_source_outline = TRUE;

          pika_tool_control_set_precision (tool->control,
                                           source_tool->saved_precision);
        }

      pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
    }

  PIKA_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state,
                                                display);
}

static void
pika_source_tool_cursor_update (PikaTool         *tool,
                                const PikaCoords *coords,
                                GdkModifierType   state,
                                PikaDisplay      *display)
{
  PikaPaintTool      *paint_tool = PIKA_PAINT_TOOL (tool);
  PikaSourceOptions  *options    = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);
  PikaCursorType      cursor     = PIKA_CURSOR_MOUSE;
  PikaCursorModifier  modifier   = PIKA_CURSOR_MODIFIER_NONE;

  if (pika_source_core_use_source (PIKA_SOURCE_CORE (paint_tool->core),
                                   options))
    {
      GdkModifierType extend_mask = pika_get_extend_selection_mask ();
      GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();

      if ((state & (toggle_mask | extend_mask)) == toggle_mask)
        {
          cursor = PIKA_CURSOR_CROSSHAIR_SMALL;
        }
      else if (! options->src_drawables)
        {
          modifier = PIKA_CURSOR_MODIFIER_BAD;
        }
    }

  pika_tool_control_set_cursor          (tool->control, cursor);
  pika_tool_control_set_cursor_modifier (tool->control, modifier);

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

static void
pika_source_tool_oper_update (PikaTool         *tool,
                              const PikaCoords *coords,
                              GdkModifierType   state,
                              gboolean          proximity,
                              PikaDisplay      *display)
{
  PikaPaintTool     *paint_tool  = PIKA_PAINT_TOOL (tool);
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (tool);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (tool);
  PikaSourceCore    *source;

  source = PIKA_SOURCE_CORE (PIKA_PAINT_TOOL (tool)->core);

  if (proximity)
    {
      if (pika_source_core_use_source (source, options))
        paint_tool->status_ctrl = source_tool->status_set_source_ctrl;
      else
        paint_tool->status_ctrl = NULL;
    }

  PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity,
                                               display);

  if (pika_source_core_use_source (source, options))
    {
      if (options->src_drawables == NULL)
        {
          GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();

          if (state & toggle_mask)
            {
              pika_tool_replace_status (tool, display, "%s",
                                        source_tool->status_set_source);
            }
          else
            {
              pika_tool_replace_status (tool, display, "%s-%s",
                                        pika_get_mod_string (toggle_mask),
                                        source_tool->status_set_source);
            }
        }
      else
        {
          pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

          g_object_get (options,
                        "src-x", &source_tool->src_x,
                        "src-y", &source_tool->src_y,
                        NULL);

          if (! source->first_stroke)
            {
              switch (options->align_mode)
                {
                case PIKA_SOURCE_ALIGN_YES:
                  source_tool->src_x = floor (coords->x) + source->offset_x;
                  source_tool->src_y = floor (coords->y) + source->offset_y;
                  break;

                case PIKA_SOURCE_ALIGN_REGISTERED:
                  source_tool->src_x = floor (coords->x);
                  source_tool->src_y = floor (coords->y);
                  break;

                default:
                  break;
                }
            }

          pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));
        }
    }
}

static void
pika_source_tool_draw (PikaDrawTool *draw_tool)
{
  PikaSourceTool    *source_tool = PIKA_SOURCE_TOOL (draw_tool);
  PikaSourceOptions *options     = PIKA_SOURCE_TOOL_GET_OPTIONS (draw_tool);
  PikaSourceCore    *source;

  source = PIKA_SOURCE_CORE (PIKA_PAINT_TOOL (draw_tool)->core);

  PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool);

  if (pika_source_core_use_source (source, options) &&
      options->src_drawables && source_tool->src_display)
    {
      PikaDisplayShell *src_shell;
      gdouble           src_x;
      gdouble           src_y;

      src_shell = pika_display_get_shell (source_tool->src_display);

      src_x = (gdouble) source_tool->src_x + 0.5;
      src_y = (gdouble) source_tool->src_y + 0.5;

      if (source_tool->src_outline)
        {
          pika_display_shell_remove_tool_item (src_shell,
                                               source_tool->src_outline);
          source_tool->src_outline = NULL;
        }

      if (source_tool->show_source_outline)
        {
          source_tool->src_outline =
            pika_brush_tool_create_outline (PIKA_BRUSH_TOOL (source_tool),
                                            source_tool->src_display,
                                            src_x, src_y);

          if (source_tool->src_outline)
            {
              pika_display_shell_add_tool_item (src_shell,
                                                source_tool->src_outline);
              g_object_unref (source_tool->src_outline);
            }
        }

      if (source_tool->src_outline)
        {
          if (source_tool->src_handle)
            {
              pika_display_shell_remove_tool_item (src_shell,
                                                   source_tool->src_handle);
              source_tool->src_handle = NULL;
            }
        }
      else
        {
          if (! source_tool->src_handle)
            {
              source_tool->src_handle =
                pika_canvas_handle_new (src_shell,
                                        PIKA_HANDLE_CROSS,
                                        PIKA_HANDLE_ANCHOR_CENTER,
                                        src_x, src_y,
                                        PIKA_TOOL_HANDLE_SIZE_CROSS,
                                        PIKA_TOOL_HANDLE_SIZE_CROSS);
              pika_display_shell_add_tool_item (src_shell,
                                                source_tool->src_handle);
              g_object_unref (source_tool->src_handle);
            }
          else
            {
              pika_canvas_handle_set_position (source_tool->src_handle,
                                               src_x, src_y);
            }
        }
    }
}

static void
pika_source_tool_paint_prepare (PikaPaintTool *paint_tool,
                                PikaDisplay   *display)
{
  PikaSourceTool *source_tool = PIKA_SOURCE_TOOL (paint_tool);

  if (PIKA_PAINT_TOOL_CLASS (parent_class)->paint_prepare)
    PIKA_PAINT_TOOL_CLASS (parent_class)->paint_prepare (paint_tool, display);

  if (source_tool->src_display)
    {
      PikaDisplayShell *src_shell;

      src_shell = pika_display_get_shell (source_tool->src_display);

      pika_paint_core_set_show_all (paint_tool->core, src_shell->show_all);
    }
}

static void
pika_source_tool_set_src_display (PikaSourceTool *source_tool,
                                  PikaDisplay    *display)
{
  if (source_tool->src_display != display)
    {
      if (source_tool->src_display)
        {
          PikaDisplayShell *src_shell;

          src_shell = pika_display_get_shell (source_tool->src_display);

          if (source_tool->src_handle)
            {
              pika_display_shell_remove_tool_item (src_shell,
                                                   source_tool->src_handle);
              source_tool->src_handle = NULL;
            }

          if (source_tool->src_outline)
            {
              pika_display_shell_remove_tool_item (src_shell,
                                                   source_tool->src_outline);
              source_tool->src_outline = NULL;
            }
        }

      source_tool->src_display = display;
    }
}
