/* 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-1997 Spencer Kimball and Peter Mattis
 *
 * pikafilteredcontainer.c
 * Copyright (C) 2008 Aurimas Juška <aurisj@svn.gnome.org>
 *               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 <gio/gio.h>

#include "libpikabase/pikabase.h"

#include "core-types.h"

#include "pikafilteredcontainer.h"


enum
{
  PROP_0,
  PROP_SRC_CONTAINER,
  PROP_FILTER_FUNC,
  PROP_FILTER_DATA
};


static void     pika_filtered_container_constructed     (GObject               *object);
static void     pika_filtered_container_dispose         (GObject               *object);
static void     pika_filtered_container_finalize        (GObject               *object);
static void     pika_filtered_container_set_property    (GObject               *object,
                                                         guint                  property_id,
                                                         const GValue          *value,
                                                         GParamSpec            *pspec);
static void     pika_filtered_container_get_property    (GObject               *object,
                                                         guint                  property_id,
                                                         GValue                *value,
                                                         GParamSpec            *pspec);

static void     pika_filtered_container_real_src_add    (PikaFilteredContainer *filtered_container,
                                                         PikaObject            *object);
static void     pika_filtered_container_real_src_remove (PikaFilteredContainer *filtered_container,
                                                         PikaObject            *object);
static void     pika_filtered_container_real_src_freeze (PikaFilteredContainer *filtered_container);
static void     pika_filtered_container_real_src_thaw   (PikaFilteredContainer *filtered_container);

static gboolean pika_filtered_container_object_matches  (PikaFilteredContainer *filtered_container,
                                                         PikaObject            *object);
static void     pika_filtered_container_src_add         (PikaContainer         *src_container,
                                                         PikaObject            *obj,
                                                         PikaFilteredContainer *filtered_container);
static void     pika_filtered_container_src_remove      (PikaContainer         *src_container,
                                                         PikaObject            *obj,
                                                         PikaFilteredContainer *filtered_container);
static void     pika_filtered_container_src_freeze      (PikaContainer         *src_container,
                                                         PikaFilteredContainer *filtered_container);
static void     pika_filtered_container_src_thaw        (PikaContainer         *src_container,
                                                         PikaFilteredContainer *filtered_container);


G_DEFINE_TYPE (PikaFilteredContainer, pika_filtered_container, PIKA_TYPE_LIST)

#define parent_class pika_filtered_container_parent_class


static void
pika_filtered_container_class_init (PikaFilteredContainerClass *klass)
{
  GObjectClass               *g_object_class = G_OBJECT_CLASS (klass);
  PikaFilteredContainerClass *filtered_class = PIKA_FILTERED_CONTAINER_CLASS (klass);

  g_object_class->constructed  = pika_filtered_container_constructed;
  g_object_class->dispose      = pika_filtered_container_dispose;
  g_object_class->finalize     = pika_filtered_container_finalize;
  g_object_class->set_property = pika_filtered_container_set_property;
  g_object_class->get_property = pika_filtered_container_get_property;

  filtered_class->src_add      = pika_filtered_container_real_src_add;
  filtered_class->src_remove   = pika_filtered_container_real_src_remove;
  filtered_class->src_freeze   = pika_filtered_container_real_src_freeze;
  filtered_class->src_thaw     = pika_filtered_container_real_src_thaw;

  g_object_class_install_property (g_object_class, PROP_SRC_CONTAINER,
                                   g_param_spec_object ("src-container",
                                                        NULL, NULL,
                                                        PIKA_TYPE_CONTAINER,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (g_object_class, PROP_FILTER_FUNC,
                                   g_param_spec_pointer ("filter-func",
                                                         NULL, NULL,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (g_object_class, PROP_FILTER_DATA,
                                   g_param_spec_pointer ("filter-data",
                                                         NULL, NULL,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));
}

static void
pika_filtered_container_init (PikaFilteredContainer *filtered_container)
{
}

static void
pika_filtered_container_constructed (GObject *object)
{
  PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (object);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika_assert (PIKA_IS_CONTAINER (filtered_container->src_container));

  if (! pika_container_frozen (filtered_container->src_container))
    {
      /*  a freeze/thaw can't hurt on a newly created container because
       *  we can't have any views yet. This way we get away without
       *  having a virtual function for initializing the container.
       */
      pika_filtered_container_src_freeze (filtered_container->src_container,
                                          filtered_container);
      pika_filtered_container_src_thaw (filtered_container->src_container,
                                        filtered_container);
    }
}

static void
pika_filtered_container_dispose (GObject *object)
{
  PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (object);

  if (filtered_container->src_container)
    {
      g_signal_handlers_disconnect_by_func (filtered_container->src_container,
                                            pika_filtered_container_src_add,
                                            filtered_container);
      g_signal_handlers_disconnect_by_func (filtered_container->src_container,
                                            pika_filtered_container_src_remove,
                                            filtered_container);
      g_signal_handlers_disconnect_by_func (filtered_container->src_container,
                                            pika_filtered_container_src_freeze,
                                            filtered_container);
      g_signal_handlers_disconnect_by_func (filtered_container->src_container,
                                            pika_filtered_container_src_thaw,
                                            filtered_container);
    }

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

static void
pika_filtered_container_finalize (GObject *object)
{
  PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (object);

  g_clear_object (&filtered_container->src_container);

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

static void
pika_filtered_container_set_property (GObject      *object,
                                      guint         property_id,
                                      const GValue *value,
                                      GParamSpec   *pspec)
{
  PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (object);

  switch (property_id)
    {
    case PROP_SRC_CONTAINER:
      filtered_container->src_container = g_value_dup_object (value);

      g_signal_connect (filtered_container->src_container, "add",
                        G_CALLBACK (pika_filtered_container_src_add),
                        filtered_container);
      g_signal_connect (filtered_container->src_container, "remove",
                        G_CALLBACK (pika_filtered_container_src_remove),
                        filtered_container);
      g_signal_connect (filtered_container->src_container, "freeze",
                        G_CALLBACK (pika_filtered_container_src_freeze),
                        filtered_container);
      g_signal_connect (filtered_container->src_container, "thaw",
                        G_CALLBACK (pika_filtered_container_src_thaw),
                        filtered_container);
      break;

    case PROP_FILTER_FUNC:
      filtered_container->filter_func = g_value_get_pointer (value);
      break;

    case PROP_FILTER_DATA:
      filtered_container->filter_data = g_value_get_pointer (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_filtered_container_get_property (GObject    *object,
                                      guint       property_id,
                                      GValue     *value,
                                      GParamSpec *pspec)
{
  PikaFilteredContainer *filtered_container = PIKA_FILTERED_CONTAINER (object);

  switch (property_id)
    {
    case PROP_SRC_CONTAINER:
      g_value_set_object (value, filtered_container->src_container);
      break;

    case PROP_FILTER_FUNC:
      g_value_set_pointer (value, filtered_container->filter_func);
      break;

    case PROP_FILTER_DATA:
      g_value_set_pointer (value, filtered_container->filter_data);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_filtered_container_real_src_add (PikaFilteredContainer *filtered_container,
                                      PikaObject            *object)
{
  if (pika_filtered_container_object_matches (filtered_container, object))
    {
      pika_container_add (PIKA_CONTAINER (filtered_container), object);
    }
}

static void
pika_filtered_container_real_src_remove (PikaFilteredContainer *filtered_container,
                                         PikaObject            *object)
{
  if (pika_filtered_container_object_matches (filtered_container, object))
    {
      pika_container_remove (PIKA_CONTAINER (filtered_container), object);
    }
}

static void
pika_filtered_container_real_src_freeze (PikaFilteredContainer *filtered_container)
{
  pika_container_clear (PIKA_CONTAINER (filtered_container));
}

static void
pika_filtered_container_real_src_thaw (PikaFilteredContainer *filtered_container)
{
  GList *list;

  for (list = PIKA_LIST (filtered_container->src_container)->queue->head;
       list;
       list = g_list_next (list))
    {
      PikaObject *object = list->data;

      if (pika_filtered_container_object_matches (filtered_container, object))
        {
          pika_container_add (PIKA_CONTAINER (filtered_container), object);
        }
    }
}

/**
 * pika_filtered_container_new:
 * @src_container: container to be filtered.
 *
 * Creates a new #PikaFilteredContainer object which creates filtered
 * data view of #PikaTagged objects. It filters @src_container for objects
 * containing all of the filtering tags. Synchronization with @src_container
 * data is performed automatically.
 *
 * Returns: a new #PikaFilteredContainer object.
 **/
PikaContainer *
pika_filtered_container_new (PikaContainer        *src_container,
                             PikaObjectFilterFunc  filter_func,
                             gpointer              filter_data)
{
  GType        children_type;
  GCompareFunc sort_func;

  g_return_val_if_fail (PIKA_IS_LIST (src_container), NULL);

  children_type = pika_container_get_children_type (src_container);
  sort_func     = pika_list_get_sort_func (PIKA_LIST (src_container));

  return g_object_new (PIKA_TYPE_FILTERED_CONTAINER,
                       "sort-func",     sort_func,
                       "children-type", children_type,
                       "policy",        PIKA_CONTAINER_POLICY_WEAK,
                       "unique-names",  FALSE,
                       "src-container", src_container,
                       "filter-func",   filter_func,
                       "filter-data",   filter_data,
                       NULL);
}

static gboolean
pika_filtered_container_object_matches (PikaFilteredContainer *filtered_container,
                                        PikaObject            *object)
{
  return (! filtered_container->filter_func ||
          filtered_container->filter_func (object,
                                           filtered_container->filter_data));
}

static void
pika_filtered_container_src_add (PikaContainer         *src_container,
                                 PikaObject            *object,
                                 PikaFilteredContainer *filtered_container)
{
  if (! pika_container_frozen (filtered_container->src_container))
    {
      PIKA_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_add (filtered_container,
                                                                       object);
    }
}

static void
pika_filtered_container_src_remove (PikaContainer         *src_container,
                                    PikaObject            *object,
                                    PikaFilteredContainer *filtered_container)
{
  if (! pika_container_frozen (filtered_container->src_container))
    {
      PIKA_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_remove (filtered_container,
                                                                          object);
    }
}

static void
pika_filtered_container_src_freeze (PikaContainer         *src_container,
                                    PikaFilteredContainer *filtered_container)
{
  pika_container_freeze (PIKA_CONTAINER (filtered_container));

  PIKA_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_freeze (filtered_container);
}

static void
pika_filtered_container_src_thaw (PikaContainer         *src_container,
                                  PikaFilteredContainer *filtered_container)
{
  PIKA_FILTERED_CONTAINER_GET_CLASS (filtered_container)->src_thaw (filtered_container);

  pika_container_thaw (PIKA_CONTAINER (filtered_container));
}
