/* 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/pika.h"
#include "core/pikaguide.h"
#include "core/pikaimage.h"
#include "core/pikaimage-guides.h"
#include "core/pikaimage-undo.h"

#include "display/pikadisplay.h"
#include "display/pikadisplayshell.h"
#include "display/pikadisplayshell-selection.h"
#include "display/pikadisplayshell-transform.h"

#include "pikaguidetool.h"
#include "pikatoolcontrol.h"
#include "tool_manager.h"

#include "pika-intl.h"


#define SWAP_ORIENT(orient) ((orient) == PIKA_ORIENTATION_HORIZONTAL ? \
                             PIKA_ORIENTATION_VERTICAL : \
                             PIKA_ORIENTATION_HORIZONTAL)


/*  local function prototypes  */

static void   pika_guide_tool_finalize       (GObject               *object);

static void   pika_guide_tool_button_release (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              guint32                time,
                                              GdkModifierType        state,
                                              PikaButtonReleaseType  release_type,
                                              PikaDisplay           *display);
static void   pika_guide_tool_motion         (PikaTool              *tool,
                                              const PikaCoords      *coords,
                                              guint32                time,
                                              GdkModifierType        state,
                                              PikaDisplay           *display);

static void   pika_guide_tool_draw           (PikaDrawTool          *draw_tool);

static void   pika_guide_tool_start          (PikaTool              *parent_tool,
                                              PikaDisplay           *display,
                                              GList                 *guides,
                                              PikaOrientationType    orientation);

static void   pika_guide_tool_push_status    (PikaGuideTool         *guide_tool,
                                              PikaDisplay           *display,
                                              gboolean               remove_guides);


G_DEFINE_TYPE (PikaGuideTool, pika_guide_tool, PIKA_TYPE_DRAW_TOOL)

#define parent_class pika_guide_tool_parent_class


static void
pika_guide_tool_class_init (PikaGuideToolClass *klass)
{
  GObjectClass      *object_class    = G_OBJECT_CLASS (klass);
  PikaToolClass     *tool_class      = PIKA_TOOL_CLASS (klass);
  PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass);

  object_class->finalize     = pika_guide_tool_finalize;

  tool_class->button_release = pika_guide_tool_button_release;
  tool_class->motion         = pika_guide_tool_motion;

  draw_tool_class->draw      = pika_guide_tool_draw;
}

static void
pika_guide_tool_init (PikaGuideTool *guide_tool)
{
  PikaTool *tool = PIKA_TOOL (guide_tool);

  pika_tool_control_set_snap_to            (tool->control, FALSE);
  pika_tool_control_set_handle_empty_image (tool->control, TRUE);
  pika_tool_control_set_tool_cursor        (tool->control,
                                            PIKA_TOOL_CURSOR_MOVE);
  pika_tool_control_set_scroll_lock        (tool->control, TRUE);
  pika_tool_control_set_precision          (tool->control,
                                            PIKA_CURSOR_PRECISION_PIXEL_BORDER);

  guide_tool->guides   = NULL;
  guide_tool->n_guides = 0;
}

static void
pika_guide_tool_finalize (GObject *object)
{
  PikaGuideTool *guide_tool = PIKA_GUIDE_TOOL (object);
  gint           i;

  for (i = 0; i < guide_tool->n_guides; i++)
    g_clear_object (&guide_tool->guides[i].guide);

  g_free (guide_tool->guides);

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

static void
pika_guide_tool_button_release (PikaTool              *tool,
                                const PikaCoords      *coords,
                                guint32                time,
                                GdkModifierType        state,
                                PikaButtonReleaseType  release_type,
                                PikaDisplay           *display)
{
  PikaGuideTool    *guide_tool = PIKA_GUIDE_TOOL (tool);
  PikaDisplayShell *shell      = pika_display_get_shell (display);
  PikaImage        *image      = pika_display_get_image (display);
  gint              i;

  pika_tool_pop_status (tool, display);

  pika_tool_control_halt (tool->control);

  pika_draw_tool_stop (PIKA_DRAW_TOOL (tool));

  if (release_type == PIKA_BUTTON_RELEASE_CANCEL)
    {
      for (i = 0; i < guide_tool->n_guides; i++)
        {
          PikaGuideToolGuide *guide = &guide_tool->guides[i];

          /* custom guides are moved live */
          if (guide->custom)
            {
              pika_image_move_guide (image, guide->guide, guide->old_position,
                                     TRUE);
            }
        }
    }
  else
    {
      gint     n_non_custom_guides = 0;
      gboolean remove_guides       = FALSE;

      for (i = 0; i < guide_tool->n_guides; i++)
        {
          PikaGuideToolGuide *guide = &guide_tool->guides[i];

          n_non_custom_guides += ! guide->custom;

          if (guide->position == PIKA_GUIDE_POSITION_UNDEFINED)
            {
              remove_guides = TRUE;
            }
        }

      if (n_non_custom_guides > 1)
        {
          pika_image_undo_group_start (image, PIKA_UNDO_GROUP_GUIDE,
                                       remove_guides ?
                                         C_("undo-type", "Remove Guides") :
                                         C_("undo-type", "Move Guides"));
        }

      for (i = 0; i < guide_tool->n_guides; i++)
        {
          PikaGuideToolGuide *guide = &guide_tool->guides[i];

          if (remove_guides)
            {
              /* removing a guide can cause other guides to be removed as well
               * (in particular, in case of symmetry guides).  these guides
               * will be kept alive, since we hold a reference on them, but we
               * need to make sure that they're still part of the image.
               */
              if (g_list_find (pika_image_get_guides (image), guide->guide))
                pika_image_remove_guide (image, guide->guide, TRUE);
            }
          else
            {
              if (guide->guide)
                {
                  /* custom guides are moved live */
                  if (! guide->custom)
                    {
                      pika_image_move_guide (image, guide->guide,
                                             guide->position, TRUE);
                    }
                }
              else
                {
                  switch (guide->orientation)
                    {
                    case PIKA_ORIENTATION_HORIZONTAL:
                      pika_image_add_hguide (image,
                                             guide->position,
                                             TRUE);
                      break;

                    case PIKA_ORIENTATION_VERTICAL:
                      pika_image_add_vguide (image,
                                             guide->position,
                                             TRUE);
                      break;

                    default:
                      pika_assert_not_reached ();
                    }
                }
            }
        }

      if (n_non_custom_guides > 1)
        pika_image_undo_group_end (image);

      pika_image_flush (image);
    }

  pika_display_shell_selection_resume (shell);

  tool_manager_pop_tool (display->pika);
  g_object_unref (guide_tool);

  {
    PikaTool *active_tool = tool_manager_get_active (display->pika);

    if (PIKA_IS_DRAW_TOOL (active_tool))
      pika_draw_tool_pause (PIKA_DRAW_TOOL (active_tool));

    tool_manager_oper_update_active (display->pika, coords, state,
                                     TRUE, display);
    tool_manager_cursor_update_active (display->pika, coords, state,
                                       display);

    if (PIKA_IS_DRAW_TOOL (active_tool))
      pika_draw_tool_resume (PIKA_DRAW_TOOL (active_tool));
  }
}

static void
pika_guide_tool_motion (PikaTool         *tool,
                        const PikaCoords *coords,
                        guint32           time,
                        GdkModifierType   state,
                        PikaDisplay      *display)

{
  PikaGuideTool    *guide_tool    = PIKA_GUIDE_TOOL (tool);
  PikaDisplayShell *shell         = pika_display_get_shell (display);
  PikaImage        *image         = pika_display_get_image (display);
  gboolean          remove_guides = FALSE;
  gint              tx, ty;
  gint              i;

  pika_draw_tool_pause (PIKA_DRAW_TOOL (tool));

  pika_display_shell_transform_xy (shell,
                                   coords->x, coords->y,
                                   &tx, &ty);

  for (i = 0; i < guide_tool->n_guides; i++)
    {
      PikaGuideToolGuide *guide = &guide_tool->guides[i];
      gint                max_position;
      gint                position;

      if (guide->orientation == PIKA_ORIENTATION_HORIZONTAL)
        max_position = pika_image_get_height (image);
      else
        max_position = pika_image_get_width (image);

      if (guide->orientation == PIKA_ORIENTATION_HORIZONTAL)
        guide->position = RINT (coords->y);
      else
        guide->position = RINT (coords->x);

      position = CLAMP (guide->position, 0, max_position);

      if (tx < 0 || tx >= shell->disp_width ||
          ty < 0 || ty >= shell->disp_height)
        {
          guide->position = PIKA_GUIDE_POSITION_UNDEFINED;

          remove_guides = TRUE;
        }
      else
        {
          if (guide->position < 0 || guide->position > max_position)
            remove_guides = TRUE;
        }

      /* custom guides are moved live */
      if (guide->custom)
        pika_image_move_guide (image, guide->guide, position, TRUE);
    }

  pika_draw_tool_resume (PIKA_DRAW_TOOL (tool));

  pika_tool_pop_status (tool, display);

  pika_guide_tool_push_status (guide_tool, display, remove_guides);
}

static void
pika_guide_tool_draw (PikaDrawTool *draw_tool)
{
  PikaGuideTool *guide_tool = PIKA_GUIDE_TOOL (draw_tool);
  gint           i;

  for (i = 0; i < guide_tool->n_guides; i++)
    {
      PikaGuideToolGuide *guide = &guide_tool->guides[i];

      if (guide->position != PIKA_GUIDE_POSITION_UNDEFINED)
        {
          /* custom guides are moved live */
          if (! guide->custom)
            {
              pika_draw_tool_add_guide (draw_tool,
                                        guide->orientation,
                                        guide->position,
                                        PIKA_GUIDE_STYLE_NONE);
            }
        }
    }
}

static void
pika_guide_tool_start (PikaTool            *parent_tool,
                       PikaDisplay         *display,
                       GList               *guides,
                       PikaOrientationType  orientation)
{
  PikaGuideTool *guide_tool;
  PikaTool      *tool;

  guide_tool = g_object_new (PIKA_TYPE_GUIDE_TOOL,
                             "tool-info", parent_tool->tool_info,
                             NULL);

  tool = PIKA_TOOL (guide_tool);

  pika_display_shell_selection_pause (pika_display_get_shell (display));

  if (guides)
    {
      gint i;

      guide_tool->n_guides = g_list_length (guides);
      guide_tool->guides   = g_new (PikaGuideToolGuide, guide_tool->n_guides);

      for (i = 0; i < guide_tool->n_guides; i++)
        {
          PikaGuide *guide = guides->data;

          guide_tool->guides[i].guide        = g_object_ref (guide);
          guide_tool->guides[i].old_position = pika_guide_get_position (guide);
          guide_tool->guides[i].position     = pika_guide_get_position (guide);
          guide_tool->guides[i].orientation  = pika_guide_get_orientation (guide);
          guide_tool->guides[i].custom       = pika_guide_is_custom (guide);

          guides = g_list_next (guides);
        }
    }
  else
    {
      guide_tool->n_guides = 1;
      guide_tool->guides   = g_new (PikaGuideToolGuide, 1);

      guide_tool->guides[0].guide        = NULL;
      guide_tool->guides[0].old_position = 0;
      guide_tool->guides[0].position     = PIKA_GUIDE_POSITION_UNDEFINED;
      guide_tool->guides[0].orientation  = orientation;
      guide_tool->guides[0].custom       = FALSE;
    }

  pika_tool_set_cursor (tool, display,
                        PIKA_CURSOR_MOUSE,
                        PIKA_TOOL_CURSOR_HAND,
                        PIKA_CURSOR_MODIFIER_MOVE);

  tool_manager_push_tool (display->pika, tool);

  tool->display = display;
  pika_tool_control_activate (tool->control);

  pika_draw_tool_start (PIKA_DRAW_TOOL (guide_tool), display);

  pika_guide_tool_push_status (guide_tool, display, FALSE);
}

static void
pika_guide_tool_push_status (PikaGuideTool *guide_tool,
                             PikaDisplay   *display,
                             gboolean       remove_guides)
{
  PikaTool *tool = PIKA_TOOL (guide_tool);

  if (remove_guides)
    {
      pika_tool_push_status (tool, display,
                             guide_tool->n_guides > 1    ? _("Remove Guides") :
                             guide_tool->guides[0].guide ? _("Remove Guide")  :
                                                           _("Cancel Guide"));
    }
  else
    {
      PikaGuideToolGuide *guides[2] = { 0, };
      gint                n_guides = 0;
      gint                i;

      for (i = 0; i < guide_tool->n_guides; i++)
        {
          PikaGuideToolGuide *guide = &guide_tool->guides[i];

          if (guide_tool->guides[i].guide)
            {
              if (n_guides == 0 || guide->orientation != guides[0]->orientation)
                {
                  guides[n_guides++] = guide;

                  if (n_guides == 2)
                    break;
                }
            }
        }

      if (n_guides == 2 &&
          guides[0]->orientation == PIKA_ORIENTATION_HORIZONTAL)
        {
          PikaGuideToolGuide *temp;

          temp      = guides[0];
          guides[0] = guides[1];
          guides[1] = temp;
        }

       if (n_guides == 1)
        {
          pika_tool_push_status_length (tool, display,
                                        _("Move Guide: "),
                                        SWAP_ORIENT (guides[0]->orientation),
                                        guides[0]->position -
                                        guides[0]->old_position,
                                        NULL);
        }
      else if (n_guides == 2)
        {
          pika_tool_push_status_coords (tool, display,
                                        PIKA_CURSOR_PRECISION_PIXEL_BORDER,
                                        _("Move Guides: "),
                                        guides[0]->position -
                                        guides[0]->old_position,
                                        ", ",
                                        guides[1]->position -
                                        guides[1]->old_position,
                                        NULL);
        }
      else
        {
          pika_tool_push_status_length (tool, display,
                                        _("Add Guide: "),
                                        SWAP_ORIENT (guide_tool->guides[0].orientation),
                                        guide_tool->guides[0].position,
                                        NULL);
        }
    }
}


/*  public functions  */

void
pika_guide_tool_start_new (PikaTool            *parent_tool,
                           PikaDisplay         *display,
                           PikaOrientationType  orientation)
{
  g_return_if_fail (PIKA_IS_TOOL (parent_tool));
  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (orientation != PIKA_ORIENTATION_UNKNOWN);

  pika_guide_tool_start (parent_tool, display,
                         NULL, orientation);
}

void
pika_guide_tool_start_edit (PikaTool    *parent_tool,
                            PikaDisplay *display,
                            PikaGuide   *guide)
{
  GList *guides = NULL;

  g_return_if_fail (PIKA_IS_TOOL (parent_tool));
  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (PIKA_IS_GUIDE (guide));

  guides = g_list_append (guides, guide);

  pika_guide_tool_start (parent_tool, display,
                         guides, PIKA_ORIENTATION_UNKNOWN);

  g_list_free (guides);
}

void
pika_guide_tool_start_edit_many (PikaTool    *parent_tool,
                                 PikaDisplay *display,
                                 GList       *guides)
{
  g_return_if_fail (PIKA_IS_TOOL (parent_tool));
  g_return_if_fail (PIKA_IS_DISPLAY (display));
  g_return_if_fail (guides != NULL);

  pika_guide_tool_start (parent_tool, display,
                         guides, PIKA_ORIENTATION_UNKNOWN);
}
