/* 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 <string.h>

#include <gegl.h>
#include <gtk/gtk.h>

#include "libpikabase/pikabase.h"
#include "libpikawidgets/pikawidgets.h"

#include "display-types.h"

#include "core/pika.h"
#include "core/pika-edit.h"
#include "core/pikabuffer.h"
#include "core/pikadrawable-edit.h"
#include "core/pikafilloptions.h"
#include "core/pikaimage.h"
#include "core/pikaimage-new.h"
#include "core/pikaimage-undo.h"
#include "core/pikalayer.h"
#include "core/pikalayer-new.h"
#include "core/pikalayermask.h"
#include "core/pikapattern.h"
#include "core/pikaprogress.h"

#include "file/file-open.h"

#include "text/pikatext.h"
#include "text/pikatextlayer.h"

#include "vectors/pikavectors.h"
#include "vectors/pikavectors-import.h"

#include "widgets/pikadnd.h"

#include "pikadisplay.h"
#include "pikadisplayshell.h"
#include "pikadisplayshell-dnd.h"
#include "pikadisplayshell-transform.h"

#include "pika-log.h"
#include "pika-intl.h"


/*  local function prototypes  */

static void   pika_display_shell_drop_drawable  (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 PikaViewable    *viewable,
                                                 gpointer         data);
static void   pika_display_shell_drop_vectors   (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 PikaViewable    *viewable,
                                                 gpointer         data);
static void   pika_display_shell_drop_svg       (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 const guchar    *svg_data,
                                                 gsize            svg_data_length,
                                                 gpointer         data);
static void   pika_display_shell_drop_pattern   (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 PikaViewable    *viewable,
                                                 gpointer         data);
static void   pika_display_shell_drop_color     (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 const PikaRGB   *color,
                                                 gpointer         data);
static void   pika_display_shell_drop_buffer    (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 PikaViewable    *viewable,
                                                 gpointer         data);
static void   pika_display_shell_drop_uri_list  (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 GList           *uri_list,
                                                 gpointer         data);
static void   pika_display_shell_drop_component (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 PikaImage       *image,
                                                 PikaChannelType  component,
                                                 gpointer         data);
static void   pika_display_shell_drop_pixbuf    (GtkWidget       *widget,
                                                 gint             x,
                                                 gint             y,
                                                 GdkPixbuf       *pixbuf,
                                                 gpointer         data);


/*  public functions  */

void
pika_display_shell_dnd_init (PikaDisplayShell *shell)
{
  g_return_if_fail (PIKA_IS_DISPLAY_SHELL (shell));

  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_LAYER,
                               pika_display_shell_drop_drawable,
                               shell);
  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_LAYER_MASK,
                               pika_display_shell_drop_drawable,
                               shell);
  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_CHANNEL,
                               pika_display_shell_drop_drawable,
                               shell);
  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_VECTORS,
                               pika_display_shell_drop_vectors,
                               shell);
  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_PATTERN,
                               pika_display_shell_drop_pattern,
                               shell);
  pika_dnd_viewable_dest_add  (shell->canvas, PIKA_TYPE_BUFFER,
                               pika_display_shell_drop_buffer,
                               shell);
  pika_dnd_color_dest_add     (shell->canvas,
                               pika_display_shell_drop_color,
                               shell);
  pika_dnd_component_dest_add (shell->canvas,
                               pika_display_shell_drop_component,
                               shell);
  pika_dnd_uri_list_dest_add  (shell->canvas,
                               pika_display_shell_drop_uri_list,
                               shell);
  pika_dnd_svg_dest_add       (shell->canvas,
                               pika_display_shell_drop_svg,
                               shell);
  pika_dnd_pixbuf_dest_add    (shell->canvas,
                               pika_display_shell_drop_pixbuf,
                               shell);
}


/*  private functions  */

/*
 * Position the dropped item in the middle of the viewport.
 */
static void
pika_display_shell_dnd_position_item (PikaDisplayShell *shell,
                                      PikaImage        *image,
                                      PikaItem         *item)
{
  gint item_width  = pika_item_get_width  (item);
  gint item_height = pika_item_get_height (item);
  gint off_x, off_y;

  if (item_width  >= pika_image_get_width  (image) &&
      item_height >= pika_image_get_height (image))
    {
      off_x = (pika_image_get_width  (image) - item_width)  / 2;
      off_y = (pika_image_get_height (image) - item_height) / 2;
    }
  else
    {
      gint x, y;
      gint width, height;

      pika_display_shell_untransform_viewport (
        shell,
        ! pika_display_shell_get_infinite_canvas (shell),
        &x, &y, &width, &height);

      off_x = x + (width  - item_width)  / 2;
      off_y = y + (height - item_height) / 2;
    }

  pika_item_translate (item,
                       off_x - pika_item_get_offset_x (item),
                       off_y - pika_item_get_offset_y (item),
                       FALSE);
}

static void
pika_display_shell_dnd_flush (PikaDisplayShell *shell,
                              PikaImage        *image)
{
  pika_display_shell_present (shell);

  pika_image_flush (image);

  pika_context_set_display (pika_get_user_context (shell->display->pika),
                            shell->display);
}

static void
pika_display_shell_drop_drawable (GtkWidget    *widget,
                                  gint          x,
                                  gint          y,
                                  PikaViewable *viewable,
                                  gpointer      data)
{
  PikaDisplayShell *shell     = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image     = pika_display_get_image (shell->display);
  GType             new_type;
  PikaItem         *new_item;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! image)
    {
      image = pika_image_new_from_drawable (shell->display->pika,
                                            PIKA_DRAWABLE (viewable));
      pika_create_display (shell->display->pika, image, PIKA_UNIT_PIXEL, 1.0,
                           G_OBJECT (pika_widget_get_monitor (widget)));
      g_object_unref (image);

      return;
    }

  if (PIKA_IS_LAYER (viewable))
    new_type = G_TYPE_FROM_INSTANCE (viewable);
  else
    new_type = PIKA_TYPE_LAYER;

  new_item = pika_item_convert (PIKA_ITEM (viewable), image, new_type);

  if (new_item)
    {
      PikaLayer *new_layer = PIKA_LAYER (new_item);

      pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE,
                                   _("Drop New Layer"));

      pika_display_shell_dnd_position_item (shell, image, new_item);

      pika_item_set_visible (new_item, TRUE, FALSE);

      pika_image_add_layer (image, new_layer,
                            PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

      pika_image_undo_group_end (image);

      pika_display_shell_dnd_flush (shell, image);
    }
}

static void
pika_display_shell_drop_vectors (GtkWidget    *widget,
                                 gint          x,
                                 gint          y,
                                 PikaViewable *viewable,
                                 gpointer      data)
{
  PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image = pika_display_get_image (shell->display);
  PikaItem         *new_item;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! image)
    return;

  new_item = pika_item_convert (PIKA_ITEM (viewable),
                                image, G_TYPE_FROM_INSTANCE (viewable));

  if (new_item)
    {
      PikaVectors *new_vectors = PIKA_VECTORS (new_item);

      pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE,
                                   _("Drop New Path"));

      pika_image_add_vectors (image, new_vectors,
                              PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

      pika_image_undo_group_end (image);

      pika_display_shell_dnd_flush (shell, image);
    }
}

static void
pika_display_shell_drop_svg (GtkWidget     *widget,
                             gint           x,
                             gint           y,
                             const guchar  *svg_data,
                             gsize          svg_data_len,
                             gpointer       data)
{
  PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image = pika_display_get_image (shell->display);
  GError           *error  = NULL;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! image)
    return;

  if (! pika_vectors_import_buffer (image,
                                    (const gchar *) svg_data, svg_data_len,
                                    TRUE, FALSE,
                                    PIKA_IMAGE_ACTIVE_PARENT, -1,
                                    NULL, &error))
    {
      pika_message_literal (shell->display->pika, G_OBJECT (shell->display),
                            PIKA_MESSAGE_ERROR,
                            error->message);
      g_clear_error (&error);
    }
  else
    {
      pika_display_shell_dnd_flush (shell, image);
    }
}

static void
pika_display_shell_dnd_fill (PikaDisplayShell *shell,
                             PikaFillOptions  *options,
                             const gchar      *undo_desc)
{
  PikaImage    *image = pika_display_get_image (shell->display);
  GList        *drawables;
  GList        *iter;

  if (shell->display->pika->busy)
    return;

  if (! image)
    return;

  drawables = pika_image_get_selected_drawables (image);

  if (! drawables)
    return;

  for (iter = drawables; iter; iter = iter->next)
    {
      if (pika_viewable_get_children (iter->data))
        {
          pika_message_literal (shell->display->pika, G_OBJECT (shell->display),
                                PIKA_MESSAGE_ERROR,
                                _("Cannot modify the pixels of layer groups."));
          g_list_free (drawables);
          return;
        }

      if (pika_item_is_content_locked (iter->data, NULL))
        {
          pika_message_literal (shell->display->pika, G_OBJECT (shell->display),
                                PIKA_MESSAGE_ERROR,
                                _("A selected layer's pixels are locked."));
          g_list_free (drawables);
          return;
        }
    }

  pika_image_undo_group_start (image, PIKA_UNDO_GROUP_PAINT, undo_desc);

  for (iter = drawables; iter; iter = iter->next)
    {
      /* FIXME: there should be a virtual method for this that the
       *        PikaTextLayer can override.
       */
      if (pika_item_is_text_layer (iter->data) &&
          (pika_fill_options_get_style (options) == PIKA_FILL_STYLE_FG_COLOR ||
           pika_fill_options_get_style (options) == PIKA_FILL_STYLE_BG_COLOR))
        {
          PikaRGB color;

          if (pika_fill_options_get_style (options) == PIKA_FILL_STYLE_FG_COLOR)
            pika_context_get_foreground (PIKA_CONTEXT (options), &color);
          else
            pika_context_get_background (PIKA_CONTEXT (options), &color);

          pika_text_layer_set (iter->data, NULL,
                               "color", &color,
                               NULL);
        }
      else
        {
          pika_drawable_edit_fill (iter->data, options, undo_desc);
        }
    }

  g_list_free (drawables);
  pika_image_undo_group_end (image);
  pika_display_shell_dnd_flush (shell, image);
}

static void
pika_display_shell_drop_pattern (GtkWidget    *widget,
                                 gint          x,
                                 gint          y,
                                 PikaViewable *viewable,
                                 gpointer      data)
{
  PikaDisplayShell *shell   = PIKA_DISPLAY_SHELL (data);
  PikaFillOptions  *options = pika_fill_options_new (shell->display->pika,
                                                     NULL, FALSE);

  PIKA_LOG (DND, NULL);

  pika_fill_options_set_style (options, PIKA_FILL_STYLE_PATTERN);
  pika_context_set_pattern (PIKA_CONTEXT (options), PIKA_PATTERN (viewable));

  pika_display_shell_dnd_fill (shell, options,
                               C_("undo-type", "Drop pattern to layer"));

  g_object_unref (options);
}

static void
pika_display_shell_drop_color (GtkWidget     *widget,
                               gint           x,
                               gint           y,
                               const PikaRGB *color,
                               gpointer       data)
{
  PikaDisplayShell *shell   = PIKA_DISPLAY_SHELL (data);
  PikaFillOptions  *options = pika_fill_options_new (shell->display->pika,
                                                     NULL, FALSE);

  PIKA_LOG (DND, NULL);

  pika_fill_options_set_style (options, PIKA_FILL_STYLE_FG_COLOR);
  pika_context_set_foreground (PIKA_CONTEXT (options), color);

  pika_display_shell_dnd_fill (shell, options,
                               C_("undo-type", "Drop color to layer"));

  g_object_unref (options);
}

static void
pika_display_shell_drop_buffer (GtkWidget    *widget,
                                gint          drop_x,
                                gint          drop_y,
                                PikaViewable *viewable,
                                gpointer      data)
{
  PikaDisplayShell *shell    = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image    = pika_display_get_image (shell->display);
  PikaContext      *context;
  GList            *drawables;
  PikaBuffer       *buffer;
  PikaPasteType     paste_type;
  gint              x, y, width, height;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! image)
    {
      image = pika_image_new_from_buffer (shell->display->pika,
                                          PIKA_BUFFER (viewable));
      pika_create_display (image->pika, image, PIKA_UNIT_PIXEL, 1.0,
                           G_OBJECT (pika_widget_get_monitor (widget)));
      g_object_unref (image);

      return;
    }

  paste_type = PIKA_PASTE_TYPE_NEW_LAYER_OR_FLOATING;
  drawables  = pika_image_get_selected_drawables (image);
  context    = pika_get_user_context (shell->display->pika);
  buffer     = PIKA_BUFFER (viewable);

  pika_display_shell_untransform_viewport (
    shell,
    ! pika_display_shell_get_infinite_canvas (shell),
    &x, &y, &width, &height);

  /* FIXME: popup a menu for selecting "Paste Into" */

  g_list_free (pika_edit_paste (image, drawables, PIKA_OBJECT (buffer),
                                paste_type, context, FALSE,
                                x, y, width, height));

  g_list_free (drawables);
  pika_display_shell_dnd_flush (shell, image);
}

static void
pika_display_shell_drop_uri_list (GtkWidget *widget,
                                  gint       x,
                                  gint       y,
                                  GList     *uri_list,
                                  gpointer   data)
{
  PikaDisplayShell *shell   = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image;
  PikaContext      *context;
  GList            *list;
  gboolean          open_as_layers;

  /* If the app is already being torn down, shell->display might be
   * NULL here.  Play it safe.
   */
  if (! shell->display)
    return;

  image = pika_display_get_image (shell->display);
  context = pika_get_user_context (shell->display->pika);

  PIKA_LOG (DND, NULL);

  open_as_layers = (image != NULL);

  if (image)
    g_object_ref (image);

  for (list = uri_list; list; list = g_list_next (list))
    {
      GFile             *file  = g_file_new_for_uri (list->data);
      PikaPDBStatusType  status;
      GError            *error = NULL;
      gboolean           warn  = FALSE;

      if (! shell->display)
        {
          /* It seems as if PIKA is being torn down for quitting. Bail out. */
          g_object_unref (file);
          g_clear_object (&image);
          return;
        }

      if (open_as_layers)
        {
          GList *new_layers;

          new_layers = file_open_layers (shell->display->pika, context,
                                         PIKA_PROGRESS (shell->display),
                                         image, FALSE,
                                         file, PIKA_RUN_INTERACTIVE, NULL,
                                         &status, &error);

          if (new_layers)
            {
              gint x      = 0;
              gint y      = 0;
              gint width  = pika_image_get_width  (image);
              gint height = pika_image_get_height (image);

              if (pika_display_get_image (shell->display))
                {
                  pika_display_shell_untransform_viewport (
                    shell,
                    ! pika_display_shell_get_infinite_canvas (shell),
                    &x, &y, &width, &height);
                }

              pika_image_add_layers (image, new_layers,
                                     PIKA_IMAGE_ACTIVE_PARENT, -1,
                                     x, y, width, height,
                                     _("Drop layers"));

              g_list_free (new_layers);
            }
          else if (status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS)
            {
              warn = TRUE;
            }
        }
      else if (pika_display_get_image (shell->display))
        {
          /*  open any subsequent images in a new display  */
          PikaImage *new_image;

          new_image = file_open_with_display (shell->display->pika, context,
                                              NULL,
                                              file, FALSE,
                                              G_OBJECT (pika_widget_get_monitor (widget)),
                                              &status, &error);

          if (! new_image && status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS)
            warn = TRUE;
        }
      else
        {
          /*  open the first image in the empty display  */
          image = file_open_with_display (shell->display->pika, context,
                                          PIKA_PROGRESS (shell->display),
                                          file, FALSE,
                                          G_OBJECT (pika_widget_get_monitor (widget)),
                                          &status, &error);

          if (image)
            {
              g_object_ref (image);
            }
          else if (status != PIKA_PDB_CANCEL && status != PIKA_PDB_SUCCESS)
            {
              warn = TRUE;
            }
        }

      /* Something above might have run a few rounds of the main loop. Check
       * that shell->display is still there, otherwise ignore this as the app
       * is being torn down for quitting.
       */
      if (warn && shell->display)
        {
          pika_message (shell->display->pika, G_OBJECT (shell->display),
                        PIKA_MESSAGE_ERROR,
                        _("Opening '%s' failed:\n\n%s"),
                        pika_file_get_utf8_name (file), error->message);
          g_clear_error (&error);
        }

      g_object_unref (file);
    }

  if (image)
    pika_display_shell_dnd_flush (shell, image);

  g_clear_object (&image);
}

static void
pika_display_shell_drop_component (GtkWidget       *widget,
                                   gint             x,
                                   gint             y,
                                   PikaImage       *image,
                                   PikaChannelType  component,
                                   gpointer         data)
{
  PikaDisplayShell *shell      = PIKA_DISPLAY_SHELL (data);
  PikaImage        *dest_image = pika_display_get_image (shell->display);
  PikaChannel      *channel;
  PikaItem         *new_item;
  const gchar      *desc;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! dest_image)
    {
      dest_image = pika_image_new_from_component (image->pika,
                                                  image, component);
      pika_create_display (dest_image->pika, dest_image, PIKA_UNIT_PIXEL, 1.0,
                           G_OBJECT (pika_widget_get_monitor (widget)));
      g_object_unref (dest_image);

      return;
    }

  channel = pika_channel_new_from_component (image, component, NULL, NULL);

  new_item = pika_item_convert (PIKA_ITEM (channel),
                                dest_image, PIKA_TYPE_LAYER);
  g_object_unref (channel);

  if (new_item)
    {
      PikaLayer *new_layer = PIKA_LAYER (new_item);

      pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component,
                           NULL, NULL, &desc, NULL);
      pika_object_take_name (PIKA_OBJECT (new_layer),
                             g_strdup_printf (_("%s Channel Copy"), desc));

      pika_image_undo_group_start (dest_image, PIKA_UNDO_GROUP_EDIT_PASTE,
                                   _("Drop New Layer"));

      pika_display_shell_dnd_position_item (shell, image, new_item);

      pika_image_add_layer (dest_image, new_layer,
                            PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

      pika_image_undo_group_end (dest_image);

      pika_display_shell_dnd_flush (shell, dest_image);
    }
}

static void
pika_display_shell_drop_pixbuf (GtkWidget *widget,
                                gint       x,
                                gint       y,
                                GdkPixbuf *pixbuf,
                                gpointer   data)
{
  PikaDisplayShell *shell = PIKA_DISPLAY_SHELL (data);
  PikaImage        *image = pika_display_get_image (shell->display);
  PikaLayer        *new_layer;
  gboolean          has_alpha = FALSE;

  PIKA_LOG (DND, NULL);

  if (shell->display->pika->busy)
    return;

  if (! image)
    {
      image = pika_image_new_from_pixbuf (shell->display->pika, pixbuf,
                                          _("Dropped Buffer"));
      pika_create_display (image->pika, image, PIKA_UNIT_PIXEL, 1.0,
                           G_OBJECT (pika_widget_get_monitor (widget)));
      g_object_unref (image);

      return;
    }

  if (gdk_pixbuf_get_n_channels (pixbuf) == 2 ||
      gdk_pixbuf_get_n_channels (pixbuf) == 4)
    {
      has_alpha = TRUE;
    }

  new_layer =
    pika_layer_new_from_pixbuf (pixbuf, image,
                                pika_image_get_layer_format (image, has_alpha),
                                _("Dropped Buffer"),
                                PIKA_OPACITY_OPAQUE,
                                pika_image_get_default_new_layer_mode (image));

  if (new_layer)
    {
      PikaItem *new_item = PIKA_ITEM (new_layer);

      pika_image_undo_group_start (image, PIKA_UNDO_GROUP_EDIT_PASTE,
                                   _("Drop New Layer"));

      pika_display_shell_dnd_position_item (shell, image, new_item);

      pika_image_add_layer (image, new_layer,
                            PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE);

      pika_image_undo_group_end (image);

      pika_display_shell_dnd_flush (shell, image);
    }
}
