/* 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
 *
 * pikaasyncset.c
 * Copyright (C) 2018 Ell
 *
 * 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 "libpikabase/pikabase.h"

#include "core-types.h"

#include "pikaasync.h"
#include "pikaasyncset.h"
#include "pikacancelable.h"
#include "pikawaitable.h"


enum
{
  PROP_0,
  PROP_EMPTY
};


struct _PikaAsyncSetPrivate
{
  GHashTable *asyncs;
};


/*  local function prototypes  */

static void       pika_async_set_waitable_iface_init   (PikaWaitableInterface   *iface);

static void       pika_async_set_cancelable_iface_init (PikaCancelableInterface *iface);

static void       pika_async_set_dispose               (GObject                 *object);
static void       pika_async_set_finalize              (GObject                 *object);
static void       pika_async_set_set_property          (GObject                 *object,
                                                        guint                    property_id,
                                                        const GValue            *value,
                                                        GParamSpec              *pspec);
static void       pika_async_set_get_property          (GObject                 *object,
                                                        guint                    property_id,
                                                        GValue                  *value,
                                                        GParamSpec              *pspec);

static void       pika_async_set_wait                  (PikaWaitable            *waitable);
static gboolean   pika_async_set_try_wait              (PikaWaitable            *waitable);
static gboolean   pika_async_set_wait_until            (PikaWaitable            *waitable,
                                                        gint64                   end_time);

static void       pika_async_set_cancel                (PikaCancelable          *cancelable);

static void       pika_async_set_async_callback        (PikaAsync               *async,
                                                        PikaAsyncSet            *async_set);


G_DEFINE_TYPE_WITH_CODE (PikaAsyncSet, pika_async_set, G_TYPE_OBJECT,
                         G_ADD_PRIVATE (PikaAsyncSet)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_WAITABLE,
                                                pika_async_set_waitable_iface_init)
                         G_IMPLEMENT_INTERFACE (PIKA_TYPE_CANCELABLE,
                                                pika_async_set_cancelable_iface_init))

#define parent_class pika_async_set_parent_class


/*  private functions  */


static void
pika_async_set_class_init (PikaAsyncSetClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose      = pika_async_set_dispose;
  object_class->finalize     = pika_async_set_finalize;
  object_class->set_property = pika_async_set_set_property;
  object_class->get_property = pika_async_set_get_property;

  g_object_class_install_property (object_class, PROP_EMPTY,
                                   g_param_spec_boolean ("empty",
                                                         NULL, NULL,
                                                         FALSE,
                                                         PIKA_PARAM_READABLE));
}

static void
pika_async_set_waitable_iface_init (PikaWaitableInterface *iface)
{
  iface->wait       = pika_async_set_wait;
  iface->try_wait   = pika_async_set_try_wait;
  iface->wait_until = pika_async_set_wait_until;
}

static void
pika_async_set_cancelable_iface_init (PikaCancelableInterface *iface)
{
  iface->cancel = pika_async_set_cancel;
}

static void
pika_async_set_init (PikaAsyncSet *async_set)
{
  async_set->priv = pika_async_set_get_instance_private (async_set);

  async_set->priv->asyncs = g_hash_table_new (NULL, NULL);
}

static void
pika_async_set_dispose (GObject *object)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (object);

  pika_async_set_clear (async_set);

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

static void
pika_async_set_finalize (GObject *object)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (object);

  g_hash_table_unref (async_set->priv->asyncs);

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

static void
pika_async_set_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  switch (property_id)
    {
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
pika_async_set_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (object);

  switch (property_id)
    {
    case PROP_EMPTY:
      g_value_set_boolean (value, pika_async_set_is_empty (async_set));
      break;

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

static void
pika_async_set_wait (PikaWaitable *waitable)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (waitable);

  while (! pika_async_set_is_empty (async_set))
    {
      PikaAsync      *async;
      GHashTableIter  iter;

      g_hash_table_iter_init (&iter, async_set->priv->asyncs);

      g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);

      pika_waitable_wait (PIKA_WAITABLE (async));
    }
}

static gboolean
pika_async_set_try_wait (PikaWaitable *waitable)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (waitable);

  while (! pika_async_set_is_empty (async_set))
    {
      PikaAsync      *async;
      GHashTableIter  iter;

      g_hash_table_iter_init (&iter, async_set->priv->asyncs);

      g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);

      if (! pika_waitable_try_wait (PIKA_WAITABLE (async)))
        return FALSE;
    }

  return TRUE;
}

static gboolean
pika_async_set_wait_until (PikaWaitable *waitable,
                           gint64        end_time)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (waitable);

  while (! pika_async_set_is_empty (async_set))
    {
      PikaAsync      *async;
      GHashTableIter  iter;

      g_hash_table_iter_init (&iter, async_set->priv->asyncs);

      g_hash_table_iter_next (&iter, (gpointer *) &async, NULL);

      if (! pika_waitable_wait_until (PIKA_WAITABLE (async), end_time))
        return FALSE;
    }

  return TRUE;
}

static void
pika_async_set_cancel (PikaCancelable *cancelable)
{
  PikaAsyncSet *async_set = PIKA_ASYNC_SET (cancelable);
  GList        *list;

  list = g_hash_table_get_keys (async_set->priv->asyncs);

  g_list_foreach (list, (GFunc) g_object_ref, NULL);

  g_list_foreach (list, (GFunc) pika_cancelable_cancel, NULL);

  g_list_free_full (list, g_object_unref);
}

static void
pika_async_set_async_callback (PikaAsync    *async,
                               PikaAsyncSet *async_set)
{
  g_hash_table_remove (async_set->priv->asyncs, async);

  if (pika_async_set_is_empty (async_set))
    g_object_notify (G_OBJECT (async_set), "empty");
}


/*  public functions  */


PikaAsyncSet *
pika_async_set_new (void)
{
  return g_object_new (PIKA_TYPE_ASYNC_SET,
                       NULL);
}

void
pika_async_set_add (PikaAsyncSet *async_set,
                    PikaAsync    *async)
{
  g_return_if_fail (PIKA_IS_ASYNC_SET (async_set));
  g_return_if_fail (PIKA_IS_ASYNC (async));

  if (g_hash_table_add (async_set->priv->asyncs, async))
    {
      if (g_hash_table_size (async_set->priv->asyncs) == 1)
        g_object_notify (G_OBJECT (async_set), "empty");

      pika_async_add_callback (
        async,
        (PikaAsyncCallback) pika_async_set_async_callback,
        async_set);
    }
}

void
pika_async_set_remove (PikaAsyncSet *async_set,
                       PikaAsync    *async)
{
  g_return_if_fail (PIKA_IS_ASYNC_SET (async_set));
  g_return_if_fail (PIKA_IS_ASYNC (async));

  if (g_hash_table_remove (async_set->priv->asyncs, async))
    {
      pika_async_remove_callback (
        async,
        (PikaAsyncCallback) pika_async_set_async_callback,
        async_set);

      if (g_hash_table_size (async_set->priv->asyncs) == 0)
        g_object_notify (G_OBJECT (async_set), "empty");
    }
}

void
pika_async_set_clear (PikaAsyncSet *async_set)
{
  PikaAsync      *async;
  GHashTableIter  iter;

  g_return_if_fail (PIKA_IS_ASYNC_SET (async_set));

  if (pika_async_set_is_empty (async_set))
    return;

  g_hash_table_iter_init (&iter, async_set->priv->asyncs);

  while (g_hash_table_iter_next (&iter, (gpointer *) &async, NULL))
    {
      pika_async_remove_callback (
        async,
        (PikaAsyncCallback) pika_async_set_async_callback,
        async_set);
    }

  g_hash_table_remove_all (async_set->priv->asyncs);

  g_object_notify (G_OBJECT (async_set), "empty");
}

gboolean
pika_async_set_is_empty (PikaAsyncSet *async_set)
{
  g_return_val_if_fail (PIKA_IS_ASYNC_SET (async_set), FALSE);

  return g_hash_table_size (async_set->priv->asyncs) == 0;
}
