/* 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
 *
 * pikadrawable-filters.c
 *
 * 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include <cairo.h>

#include "core-types.h"

#include "gegl/pikaapplicator.h"
#include "gegl/pika-gegl-apply-operation.h"
#include "gegl/pika-gegl-loops.h"

#include "pika.h"
#include "pika-utils.h"
#include "pikadrawable.h"
#include "pikadrawable-filters.h"
#include "pikadrawable-private.h"
#include "pikafilter.h"
#include "pikafilterstack.h"
#include "pikaimage.h"
#include "pikaimage-undo.h"
#include "pikalayer.h"
#include "pikaprogress.h"
#include "pikaprojection.h"


PikaContainer *
pika_drawable_get_filters (PikaDrawable *drawable)
{
  g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), NULL);

  return drawable->private->filter_stack;
}

gboolean
pika_drawable_has_filters (PikaDrawable *drawable)
{
  GList *list;

  g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);

  for (list = PIKA_LIST (drawable->private->filter_stack)->queue->head;
       list;
       list = g_list_next (list))
    {
      PikaFilter *filter = list->data;

      if (pika_filter_get_active (filter))
        return TRUE;
    }

  return FALSE;
}

void
pika_drawable_add_filter (PikaDrawable *drawable,
                          PikaFilter   *filter)
{
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
  g_return_if_fail (PIKA_IS_FILTER (filter));
  g_return_if_fail (pika_drawable_has_filter (drawable, filter) == FALSE);

  pika_container_add (drawable->private->filter_stack,
                      PIKA_OBJECT (filter));
}

void
pika_drawable_remove_filter (PikaDrawable *drawable,
                             PikaFilter   *filter)
{
  g_return_if_fail (PIKA_IS_DRAWABLE (drawable));
  g_return_if_fail (PIKA_IS_FILTER (filter));
  g_return_if_fail (pika_drawable_has_filter (drawable, filter) == TRUE);

  pika_container_remove (drawable->private->filter_stack,
                         PIKA_OBJECT (filter));
}

gboolean
pika_drawable_has_filter (PikaDrawable *drawable,
                          PikaFilter   *filter)
{
  g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);
  g_return_val_if_fail (PIKA_IS_FILTER (filter), FALSE);

  return pika_container_have (drawable->private->filter_stack,
                              PIKA_OBJECT (filter));
}

gboolean
pika_drawable_merge_filter (PikaDrawable *drawable,
                            PikaFilter   *filter,
                            PikaProgress *progress,
                            const gchar  *undo_desc,
                            const Babl   *format,
                            gboolean      clip,
                            gboolean      cancellable,
                            gboolean      update)
{
  PikaImage      *image;
  PikaApplicator *applicator;
  gboolean        applicator_cache         = FALSE;
  const Babl     *applicator_output_format = NULL;
  GeglBuffer     *buffer                   = NULL;
  GeglBuffer     *dest_buffer;
  GeglBuffer     *undo_buffer              = NULL;
  GeglRectangle   undo_rect;
  GeglBuffer     *cache                    = NULL;
  GeglRectangle  *rects                    = NULL;
  gint            n_rects                  = 0;
  GeglRectangle   rect;
  gboolean        success                  = TRUE;

  g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);
  g_return_val_if_fail (PIKA_IS_FILTER (filter), FALSE);
  g_return_val_if_fail (progress == NULL || PIKA_IS_PROGRESS (progress), FALSE);

  image       = pika_item_get_image (PIKA_ITEM (drawable));
  applicator  = pika_filter_get_applicator (filter);
  dest_buffer = pika_drawable_get_buffer (drawable);

  if (! format)
    format = pika_drawable_get_format (drawable);

  rect = gegl_node_get_bounding_box (pika_filter_get_node (filter));

  if (! clip && gegl_rectangle_equal (&rect,
                                      gegl_buffer_get_extent (dest_buffer)))
    {
      clip = TRUE;
    }

  if (clip)
    {
      if (! pika_item_mask_intersect (PIKA_ITEM (drawable),
                                      &rect.x, &rect.y,
                                      &rect.width, &rect.height))
        {
          return TRUE;
        }

      if (format != pika_drawable_get_format (drawable))
        {
          buffer = gegl_buffer_new (gegl_buffer_get_extent (dest_buffer),
                                    format);

          dest_buffer = buffer;
        }
    }
  else
    {
      buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height),
                                format);

      dest_buffer = g_object_new (GEGL_TYPE_BUFFER,
                                  "source",  buffer,
                                  "shift-x", -rect.x,
                                  "shift-y", -rect.y,
                                  NULL);
    }

  if (applicator)
    {
      const GeglRectangle *crop_rect;

      crop_rect = pika_applicator_get_crop (applicator);

      if (crop_rect && ! gegl_rectangle_intersect (&rect, &rect, crop_rect))
        return TRUE;

      /*  the cache and its valid rectangles are the region that
       *  has already been processed by this applicator.
       */
      cache = pika_applicator_get_cache_buffer (applicator,
                                                &rects, &n_rects);

      /*  skip the cache and output-format conversion while processing
       *  the remaining area, so that the result is written directly to
       *  the drawable's buffer.
       */
      applicator_cache         = pika_applicator_get_cache (applicator);
      applicator_output_format = pika_applicator_get_output_format (applicator);

      pika_applicator_set_cache (applicator, FALSE);
      if (applicator_output_format == format)
        pika_applicator_set_output_format (applicator, NULL);
    }

  if (! buffer)
    {
      gegl_rectangle_align_to_buffer (
        &undo_rect,
        &rect,
        pika_drawable_get_buffer (drawable),
        GEGL_RECTANGLE_ALIGNMENT_SUPERSET);

      undo_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
                                                     undo_rect.width,
                                                     undo_rect.height),
                                     pika_drawable_get_format (drawable));

      pika_gegl_buffer_copy (pika_drawable_get_buffer (drawable),
                             &undo_rect,
                             GEGL_ABYSS_NONE,
                             undo_buffer,
                             GEGL_RECTANGLE (0, 0, 0, 0));
    }

  pika_projection_stop_rendering (pika_image_get_projection (image));

  /* make sure we have a source node - this connects the filter stack to the
   * underlying source node
   */
  (void) pika_drawable_get_source_node (drawable);

  if (pika_gegl_apply_cached_operation (pika_drawable_get_buffer (drawable),
                                        progress, undo_desc,
                                        pika_filter_get_node (filter), FALSE,
                                        dest_buffer, &rect, FALSE,
                                        cache, rects, n_rects,
                                        cancellable))
    {
      /*  finished successfully  */

      if (clip)
        {
          if (buffer)
            {
              pika_drawable_set_buffer_full (drawable,
                                             TRUE, undo_desc,
                                             buffer, NULL,
                                             FALSE);
            }
          else
            {
              pika_drawable_push_undo (drawable, undo_desc, undo_buffer,
                                       undo_rect.x, undo_rect.y,
                                       undo_rect.width, undo_rect.height);
            }
        }
      else
        {
          PikaLayerMask *mask = NULL;
          gint           offset_x;
          gint           offset_y;

          pika_item_get_offset (PIKA_ITEM (drawable), &offset_x, &offset_y);

          if (PIKA_IS_LAYER (drawable))
            mask = pika_layer_get_mask (PIKA_LAYER (drawable));

          if (mask)
            {
              pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD,
                                           undo_desc);
            }

          pika_drawable_set_buffer_full (
            drawable, TRUE, undo_desc, buffer,
            GEGL_RECTANGLE (offset_x + rect.x, offset_y + rect.y, 0, 0),
            FALSE);

          if (mask)
            {
              pika_item_resize (PIKA_ITEM (mask),
                                pika_get_default_context (image->pika),
                                PIKA_FILL_TRANSPARENT,
                                rect.width, rect.height,
                                -rect.x, -rect.y);

              pika_image_undo_group_end (image);
            }
        }
    }
  else
    {
      /*  canceled by the user  */

      if (clip)
        {
          pika_gegl_buffer_copy (undo_buffer,
                                 GEGL_RECTANGLE (0, 0,
                                                 undo_rect.width,
                                                 undo_rect.height),
                                 GEGL_ABYSS_NONE,
                                 pika_drawable_get_buffer (drawable),
                                 &undo_rect);
        }

      success = FALSE;
    }

  if (clip)
    {
      g_clear_object (&undo_buffer);
      g_clear_object (&buffer);
    }
  else
    {
      g_object_unref (buffer);
      g_object_unref (dest_buffer);
    }

  if (cache)
    {
      g_object_unref (cache);
      g_free (rects);
    }

  if (applicator)
    {
      pika_applicator_set_cache (applicator, applicator_cache);
      pika_applicator_set_output_format (applicator, applicator_output_format);
    }

  if (update)
    {
      pika_drawable_update (drawable,
                            rect.x, rect.y,
                            rect.width, rect.height);
    }

  return success;
}
