/* 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
 *
 * pikaitem-exclusive.c
 * Copyright (C) 2011 Michael Natterer <mitch@gimp.org>
 *
 * 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 "core-types.h"

#include "pikacontext.h"
#include "pikadrawable.h"
#include "pikaimage.h"
#include "pikaimage-undo.h"
#include "pikaimage-undo-push.h"
#include "pikaitem.h"
#include "pikaitem-exclusive.h"
#include "pikaitemstack.h"
#include "pikaitemtree.h"
#include "pikaundostack.h"

#include "pika-intl.h"


static GList * pika_item_exclusive_get_ancestry (PikaItem              *item);
static void    pika_item_exclusive_get_lists    (PikaItem              *item,
                                                 PikaItemIsEnabledFunc  is_enabled,
                                                 PikaItemCanSetFunc     can_set,
                                                 PikaItemIsPropLocked   is_prop_locked,
                                                 gboolean               only_selected,
                                                 GList                **on,
                                                 GList                **off);


/*  public functions  */

void
pika_item_toggle_exclusive_visible (PikaItem    *item,
                                    gboolean     only_selected,
                                    PikaContext *context)
{
  pika_item_toggle_exclusive (item,
                              (PikaItemIsEnabledFunc) pika_item_is_visible,
                              (PikaItemSetFunc)       pika_item_set_visible,
                              NULL,
                              (PikaItemIsPropLocked)  pika_item_get_lock_visibility,
                              (PikaItemUndoPush)      pika_image_undo_push_item_visibility,
                              _("Set Item Exclusive Visibility"),
                              PIKA_UNDO_GROUP_ITEM_VISIBILITY,
                              only_selected, context);
}

void
pika_item_toggle_exclusive (PikaItem               *item,
                            PikaItemIsEnabledFunc   is_enabled,
                            PikaItemSetFunc         set_prop,
                            PikaItemCanSetFunc      can_set,
                            PikaItemIsPropLocked    is_prop_locked,
                            PikaItemUndoPush        undo_push,
                            const gchar            *undo_desc,
                            PikaUndoType            group_undo_type,
                            gboolean                only_selected,
                            PikaContext            *context)
{
  GList *ancestry;
  GList *on;
  GList *off;
  GList *list;

  g_return_if_fail (PIKA_IS_ITEM (item));
  g_return_if_fail (pika_item_is_attached (item));
  g_return_if_fail (undo_desc != NULL);
  g_return_if_fail (context == NULL || PIKA_IS_CONTEXT (context));

  ancestry = pika_item_exclusive_get_ancestry (item);
  pika_item_exclusive_get_lists (item, is_enabled, can_set, is_prop_locked,
                                 only_selected, &on, &off);

  if (on || off || (! is_enabled (item) && (can_set == NULL || can_set (item))))
    {
      PikaImage *image = pika_item_get_image (item);
      PikaUndo  *undo;
      gboolean   push_undo = TRUE;

      undo = pika_image_undo_can_compress (image, PIKA_TYPE_UNDO_STACK,
                                           group_undo_type);

      /* Use the undo description as the undo object key, as we should
       * assume that a same undo description means the same exclusive
       * action.
       */
      if (undo && (g_object_get_data (G_OBJECT (undo), undo_desc) ==
                   (gpointer) item))
        push_undo = FALSE;

      if (push_undo)
        {
          if (pika_image_undo_group_start (image,
                                           group_undo_type,
                                           undo_desc))
            {
              undo = pika_image_undo_can_compress (image, PIKA_TYPE_UNDO_STACK,
                                                   group_undo_type);

              if (undo)
                g_object_set_data (G_OBJECT (undo), undo_desc, (gpointer) item);
            }

          for (list = ancestry; list; list = g_list_next (list))
            undo_push (image, NULL, list->data);

          for (list = on; list; list = g_list_next (list))
            undo_push (image, NULL, list->data);

          for (list = off; list; list = g_list_next (list))
            undo_push (image, NULL, list->data);

          pika_image_undo_group_end (image);
        }
      else if (context)
        {
          pika_undo_refresh_preview (undo, context);
        }

      for (list = ancestry; list; list = g_list_next (list))
        set_prop (list->data, TRUE, FALSE);

      if (on)
        {
          for (list = on; list; list = g_list_next (list))
            set_prop (list->data, FALSE, FALSE);
        }
      else if (off)
        {
          for (list = off; list; list = g_list_next (list))
            set_prop (list->data, TRUE, FALSE);
        }

      g_list_free (on);
      g_list_free (off);
    }

  g_list_free (ancestry);
}


/*  private functions  */

static GList *
pika_item_exclusive_get_ancestry (PikaItem *item)
{
  PikaViewable *parent;
  GList        *ancestry = NULL;

  for (parent = PIKA_VIEWABLE (item);
       parent;
       parent = pika_viewable_get_parent (parent))
    {
      ancestry = g_list_prepend (ancestry, parent);
    }

  return ancestry;
}

static void
pika_item_exclusive_get_lists (PikaItem              *item,
                               PikaItemIsEnabledFunc  is_enabled,
                               PikaItemCanSetFunc     can_set,
                               PikaItemIsPropLocked   is_prop_locked,
                               gboolean               only_selected,
                               GList                 **on,
                               GList                 **off)
{
  PikaImage    *image = NULL;
  PikaItemTree *tree;
  GList        *items;
  GList        *list;

  *on  = NULL;
  *off = NULL;

  tree = pika_item_get_tree (item);

  items = pika_item_stack_get_item_list (PIKA_ITEM_STACK (tree->container));

  if (only_selected)
    image = pika_item_get_image (item);

  for (list = items; list; list = g_list_next (list))
    {
      PikaItem *other = list->data;

      if (other != item                                                                &&
          /* Don't include item with visibility locks. */
          (is_prop_locked == NULL || ! is_prop_locked (other))                         &&
          /* Don't include item which can be changed. */
          (can_set == NULL || can_set (other))                                         &&
          /* Do we care only about selected drawables? */
          (! only_selected  || pika_image_is_selected_drawable (image,
                                                                PIKA_DRAWABLE (other))) &&
          /* We are only interested in same level items unless
           * @only_selected is TRUE. */
          (only_selected ||
           pika_viewable_get_parent (PIKA_VIEWABLE (other)) ==
           pika_viewable_get_parent (PIKA_VIEWABLE (item))))
        {
          if (is_enabled (other))
            *on = g_list_prepend (*on, other);
          else
            *off = g_list_prepend (*off, other);
        }
    }

  g_list_free (items);
}
