/* 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
 *
 * pikadatafactory.c
 * Copyright (C) 2001-2018 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 <stdlib.h>

#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "libpikaconfig/pikaconfig.h"

#include "core-types.h"

#include "pika.h"
#include "pika-utils.h"
#include "pikaasyncset.h"
#include "pikacancelable.h"
#include "pikacontext.h"
#include "pikadata.h"
#include "pikadatafactory.h"
#include "pikalist.h"
#include "pikauncancelablewaitable.h"
#include "pikawaitable.h"

#include "pika-intl.h"


enum
{
  PROP_0,
  PROP_PIKA,
  PROP_DATA_TYPE,
  PROP_PATH_PROPERTY_NAME,
  PROP_WRITABLE_PROPERTY_NAME,
  PROP_EXT_PROPERTY_NAME,
  PROP_NEW_FUNC,
  PROP_GET_STANDARD_FUNC,
  PROP_UNIQUE_NAMES
};


struct _PikaDataFactoryPrivate
{
  Pika                    *pika;

  GType                    data_type;
  PikaContainer           *container;
  PikaContainer           *container_obsolete;
  gboolean                 unique_names;

  gchar                   *path_property_name;
  gchar                   *writable_property_name;
  gchar                   *ext_property_name;

  PikaDataNewFunc          data_new_func;
  PikaDataGetStandardFunc  data_get_standard_func;

  PikaAsyncSet            *async_set;
};

#define GET_PRIVATE(obj) (((PikaDataFactory *) (obj))->priv)


static void       pika_data_factory_constructed         (GObject             *object);
static void       pika_data_factory_set_property        (GObject             *object,
                                                         guint                property_id,
                                                         const GValue        *value,
                                                         GParamSpec          *pspec);
static void       pika_data_factory_get_property        (GObject             *object,
                                                         guint                property_id,
                                                         GValue              *value,
                                                         GParamSpec          *pspec);
static void       pika_data_factory_finalize            (GObject             *object);

static gint64     pika_data_factory_get_memsize         (PikaObject          *object,
                                                         gint64              *gui_size);

static void       pika_data_factory_real_data_save      (PikaDataFactory     *factory);
static void       pika_data_factory_real_data_cancel    (PikaDataFactory     *factory);
static PikaData * pika_data_factory_real_data_duplicate (PikaDataFactory     *factory,
                                                         PikaData            *data);
static gboolean   pika_data_factory_real_data_delete    (PikaDataFactory     *factory,
                                                         PikaData            *data,
                                                         gboolean             delete_from_disk,
                                                         GError             **error);

static void       pika_data_factory_path_notify         (GObject             *object,
                                                         const GParamSpec    *pspec,
                                                         PikaDataFactory     *factory);
static GFile    * pika_data_factory_get_save_dir        (PikaDataFactory     *factory,
                                                         GError             **error);


G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (PikaDataFactory, pika_data_factory,
                                     PIKA_TYPE_OBJECT)

#define parent_class pika_data_factory_parent_class


static void
pika_data_factory_class_init (PikaDataFactoryClass *klass)
{
  GObjectClass    *object_class      = G_OBJECT_CLASS (klass);
  PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass);

  object_class->constructed      = pika_data_factory_constructed;
  object_class->set_property     = pika_data_factory_set_property;
  object_class->get_property     = pika_data_factory_get_property;
  object_class->finalize         = pika_data_factory_finalize;

  pika_object_class->get_memsize = pika_data_factory_get_memsize;

  klass->data_init               = NULL;
  klass->data_refresh            = NULL;
  klass->data_save               = pika_data_factory_real_data_save;
  klass->data_cancel             = pika_data_factory_real_data_cancel;
  klass->data_duplicate          = pika_data_factory_real_data_duplicate;
  klass->data_delete             = pika_data_factory_real_data_delete;

  g_object_class_install_property (object_class, PROP_PIKA,
                                   g_param_spec_object ("pika", NULL, NULL,
                                                        PIKA_TYPE_PIKA,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_DATA_TYPE,
                                   g_param_spec_gtype ("data-type", NULL, NULL,
                                                       PIKA_TYPE_DATA,
                                                       PIKA_PARAM_READWRITE |
                                                       G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_PATH_PROPERTY_NAME,
                                   g_param_spec_string ("path-property-name",
                                                        NULL, NULL,
                                                        NULL,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_WRITABLE_PROPERTY_NAME,
                                   g_param_spec_string ("writable-property-name",
                                                        NULL, NULL,
                                                        NULL,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_EXT_PROPERTY_NAME,
                                   g_param_spec_string ("ext-property-name",
                                                        NULL, NULL,
                                                        NULL,
                                                        PIKA_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_NEW_FUNC,
                                   g_param_spec_pointer ("new-func",
                                                         NULL, NULL,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_GET_STANDARD_FUNC,
                                   g_param_spec_pointer ("get-standard-func",
                                                         NULL, NULL,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class, PROP_UNIQUE_NAMES,
                                   g_param_spec_boolean ("unique-names",
                                                         NULL, NULL,
                                                         TRUE,
                                                         PIKA_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT_ONLY));

}

static void
pika_data_factory_init (PikaDataFactory *factory)
{
  factory->priv = pika_data_factory_get_instance_private (factory);

  factory->priv->async_set = pika_async_set_new ();
}

static void
pika_data_factory_constructed (GObject *object)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (object);

  G_OBJECT_CLASS (parent_class)->constructed (object);

  pika_assert (PIKA_IS_PIKA (priv->pika));
  pika_assert (g_type_is_a (priv->data_type, PIKA_TYPE_DATA));
  pika_assert (PIKA_DATA_FACTORY_GET_CLASS (object)->data_init != NULL);
  pika_assert (PIKA_DATA_FACTORY_GET_CLASS (object)->data_refresh != NULL);

  /* Passing along the "unique names" property to the data container. */
  priv->container = pika_list_new (priv->data_type, priv->unique_names);
  pika_list_set_sort_func (PIKA_LIST (priv->container),
                           (GCompareFunc) pika_data_compare);

  priv->container_obsolete = pika_list_new (priv->data_type, TRUE);
  pika_list_set_sort_func (PIKA_LIST (priv->container_obsolete),
                           (GCompareFunc) pika_data_compare);
}

static void
pika_data_factory_set_property (GObject      *object,
                                guint         property_id,
                                const GValue *value,
                                GParamSpec   *pspec)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (object);

  switch (property_id)
    {
    case PROP_PIKA:
      priv->pika = g_value_get_object (value); /* don't ref */
      break;

    case PROP_DATA_TYPE:
      priv->data_type = g_value_get_gtype (value);
      break;

    case PROP_PATH_PROPERTY_NAME:
      priv->path_property_name = g_value_dup_string (value);
      break;

    case PROP_WRITABLE_PROPERTY_NAME:
      priv->writable_property_name = g_value_dup_string (value);
      break;

    case PROP_EXT_PROPERTY_NAME:
      priv->ext_property_name = g_value_dup_string (value);
      break;

    case PROP_NEW_FUNC:
      priv->data_new_func = g_value_get_pointer (value);
      break;

    case PROP_GET_STANDARD_FUNC:
      priv->data_get_standard_func = g_value_get_pointer (value);
      break;

    case PROP_UNIQUE_NAMES:
      priv->unique_names = g_value_get_boolean (value);
      break;

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

static void
pika_data_factory_get_property (GObject    *object,
                                guint       property_id,
                                GValue     *value,
                                GParamSpec *pspec)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (object);

  switch (property_id)
    {
    case PROP_PIKA:
      g_value_set_object (value, priv->pika);
      break;

    case PROP_DATA_TYPE:
      g_value_set_gtype (value, priv->data_type);
      break;

    case PROP_PATH_PROPERTY_NAME:
      g_value_set_string (value, priv->path_property_name);
      break;

    case PROP_WRITABLE_PROPERTY_NAME:
      g_value_set_string (value, priv->writable_property_name);
      break;

    case PROP_EXT_PROPERTY_NAME:
      g_value_set_string (value, priv->ext_property_name);
      break;

    case PROP_NEW_FUNC:
      g_value_set_pointer (value, priv->data_new_func);
      break;

    case PROP_GET_STANDARD_FUNC:
      g_value_set_pointer (value, priv->data_get_standard_func);
      break;

    case PROP_UNIQUE_NAMES:
      g_value_set_boolean (value, priv->unique_names);
      break;

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

static void
pika_data_factory_finalize (GObject *object)
{
  PikaDataFactory        *factory = PIKA_DATA_FACTORY (object);
  PikaDataFactoryPrivate *priv    = GET_PRIVATE (object);

  if (priv->async_set)
    {
      pika_data_factory_data_cancel (factory);

      g_clear_object (&priv->async_set);
    }

  g_clear_object (&priv->container);
  g_clear_object (&priv->container_obsolete);

  g_clear_pointer (&priv->path_property_name,     g_free);
  g_clear_pointer (&priv->writable_property_name, g_free);
  g_clear_pointer (&priv->ext_property_name,      g_free);

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

static gint64
pika_data_factory_get_memsize (PikaObject *object,
                               gint64     *gui_size)
{
  PikaDataFactoryPrivate *priv    = GET_PRIVATE (object);
  gint64                  memsize = 0;

  memsize += pika_object_get_memsize (PIKA_OBJECT (priv->container),
                                      gui_size);
  memsize += pika_object_get_memsize (PIKA_OBJECT (priv->container_obsolete),
                                      gui_size);

  return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

static void
pika_data_factory_real_data_save (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  GList                  *dirty = NULL;
  GList                  *list;
  GFile                  *writable_dir;
  GError                 *error = NULL;

  for (list = PIKA_LIST (priv->container)->queue->head;
       list;
       list = g_list_next (list))
    {
      PikaData *data = list->data;

      if (pika_data_is_dirty (data) &&
          pika_data_is_writable (data))
        {
          dirty = g_list_prepend (dirty, data);
        }
    }

  if (! dirty)
    return;

  writable_dir = pika_data_factory_get_save_dir (factory, &error);

  if (! writable_dir)
    {
      pika_message (priv->pika, NULL, PIKA_MESSAGE_ERROR,
                    _("Failed to save data:\n\n%s"),
                    error->message);
      g_clear_error (&error);

      g_list_free (dirty);

      return;
    }

  for (list = dirty; list; list = g_list_next (list))
    {
      PikaData *data  = list->data;
      GError   *error = NULL;

      if (pika_data_get_image (data))
        continue;

      if (! pika_data_get_file (data))
        pika_data_create_filename (data, writable_dir);

      if (factory->priv->pika->be_verbose)
        {
          GFile *file = pika_data_get_file (data);

          if (file)
            g_print ("Writing dirty data '%s'\n",
                     pika_file_get_utf8_name (file));
        }

      if (! pika_data_save (data, &error))
        {
          /*  check if there actually was an error (no error
           *  means the data class does not implement save)
           */
          if (error)
            {
              pika_message (priv->pika, NULL, PIKA_MESSAGE_ERROR,
                            _("Failed to save data:\n\n%s"),
                            error->message);
              g_clear_error (&error);
            }
        }
    }

  g_object_unref (writable_dir);

  g_list_free (dirty);
}

static void
pika_data_factory_real_data_cancel (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);

  pika_cancelable_cancel (PIKA_CANCELABLE (priv->async_set));
  pika_waitable_wait     (PIKA_WAITABLE   (priv->async_set));
}

static PikaData *
pika_data_factory_real_data_duplicate (PikaDataFactory *factory,
                                       PikaData        *data)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  PikaData               *new_data;

  new_data = pika_data_duplicate (data);

  if (new_data)
    {
      const gchar *name = pika_object_get_name (data);
      gchar       *ext;
      gint         copy_len;
      gint         number;
      gchar       *new_name;
      GError      *error = NULL;

      ext      = strrchr (name, '#');
      copy_len = strlen (_("copy"));

      if ((strlen (name) >= copy_len                                 &&
           strcmp (&name[strlen (name) - copy_len], _("copy")) == 0) ||
          (ext && (number = atoi (ext + 1)) > 0                      &&
           ((gint) (log10 (number) + 1)) == strlen (ext + 1)))
        {
          /* don't have redundant "copy"s */
          new_name = g_strdup (name);
        }
      else
        {
          new_name = g_strdup_printf (_("%s copy"), name);
        }

      pika_object_take_name (PIKA_OBJECT (new_data), new_name);

      if (! pika_data_factory_data_save_single (factory, new_data, &error))
        g_critical ("%s: data saving failed: %s", G_STRFUNC, error->message);

      pika_container_add (priv->container, PIKA_OBJECT (new_data));
      g_object_unref (new_data);
      g_clear_error (&error);
    }

  return new_data;
}

static gboolean
pika_data_factory_real_data_delete (PikaDataFactory  *factory,
                                    PikaData         *data,
                                    gboolean          delete_from_disk,
                                    GError          **error)
{
  if (delete_from_disk && pika_data_get_file (data))
    return pika_data_delete_from_disk (data, error);

  return TRUE;
}


/*  public functions  */

void
pika_data_factory_data_init (PikaDataFactory *factory,
                             PikaContext     *context,
                             gboolean         no_data)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  gchar                  *signal_name;

  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  /*  Always freeze() and thaw() the container around initialization,
   *  even if no_data, the thaw() will implicitly make PikaContext
   *  create the standard data that serves as fallback.
   */
  pika_container_freeze (priv->container);

  if (! no_data)
    {
      if (priv->pika->be_verbose)
        {
          const gchar *name = pika_object_get_name (factory);

          g_print ("Loading '%s' data\n", name ? name : "???");
        }

      PIKA_DATA_FACTORY_GET_CLASS (factory)->data_init (factory, context);
    }

  pika_container_thaw (priv->container);

  signal_name = g_strdup_printf ("notify::%s", priv->path_property_name);
  g_signal_connect_object (priv->pika->config, signal_name,
                           G_CALLBACK (pika_data_factory_path_notify),
                           factory, 0);
  g_free (signal_name);

  signal_name = g_strdup_printf ("notify::%s", priv->ext_property_name);
  g_signal_connect_object (priv->pika->extension_manager, signal_name,
                           G_CALLBACK (pika_data_factory_path_notify),
                           factory, 0);
  g_free (signal_name);
}

static void
pika_data_factory_clean_cb (PikaDataFactory *factory,
                            PikaData        *data,
                            gpointer         user_data)
{
  if (pika_data_is_dirty (data))
    pika_data_clean (data);
}

void
pika_data_factory_data_clean (PikaDataFactory *factory)
{
  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));

  pika_data_factory_data_foreach (factory, TRUE,
                                  pika_data_factory_clean_cb, NULL);
}

void
pika_data_factory_data_refresh (PikaDataFactory *factory,
                                PikaContext     *context)
{
  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));
  g_return_if_fail (PIKA_IS_CONTEXT (context));

  PIKA_DATA_FACTORY_GET_CLASS (factory)->data_refresh (factory, context);
}

void
pika_data_factory_data_save (PikaDataFactory *factory)
{
  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));

  if (! pika_container_is_empty (factory->priv->container))
    PIKA_DATA_FACTORY_GET_CLASS (factory)->data_save (factory);
}

static void
pika_data_factory_data_free_foreach (PikaDataFactory *factory,
                                     PikaData        *data,
                                     gpointer         user_data)
{
  pika_container_remove (factory->priv->container, PIKA_OBJECT (data));
}

void
pika_data_factory_data_free (PikaDataFactory *factory)
{
  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));

  pika_data_factory_data_cancel (factory);

  if (! pika_container_is_empty (factory->priv->container))
    {
      pika_container_freeze (factory->priv->container);

      pika_data_factory_data_foreach (factory, TRUE,
                                      pika_data_factory_data_free_foreach,
                                      NULL);

      pika_container_thaw (factory->priv->container);
    }
}

PikaAsyncSet *
pika_data_factory_get_async_set (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  return factory->priv->async_set;
}

gboolean
pika_data_factory_data_wait (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv;
  PikaWaitable           *waitable;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE);

  priv = GET_PRIVATE (factory);

  /* don't allow cancellation for now */
  waitable = pika_uncancelable_waitable_new (PIKA_WAITABLE (priv->async_set));

  pika_wait (priv->pika, waitable,
             _("Loading fonts (this may take a while...)"));

  g_object_unref (waitable);

  return TRUE;
}

void
pika_data_factory_data_cancel (PikaDataFactory *factory)
{
  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));

  PIKA_DATA_FACTORY_GET_CLASS (factory)->data_cancel (factory);
}

gboolean
pika_data_factory_has_data_new_func (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE);

  return factory->priv->data_new_func != NULL;
}

PikaData *
pika_data_factory_data_new (PikaDataFactory *factory,
                            PikaContext     *context,
                            const gchar     *name)
{
  PikaDataFactoryPrivate *priv;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
  g_return_val_if_fail (name != NULL, NULL);
  g_return_val_if_fail (*name != '\0', NULL);

  priv = GET_PRIVATE (factory);

  if (priv->data_new_func)
    {
      PikaData *data = priv->data_new_func (context, name);

      if (data)
        {
          GError *error = NULL;

          if (! pika_data_factory_data_save_single (factory, data, &error))
            g_critical ("%s: data saving failed: %s", G_STRFUNC, error->message);

          pika_container_add (priv->container, PIKA_OBJECT (data));
          g_object_unref (data);
          g_clear_error (&error);

          return data;
        }

      g_warning ("%s: PikaDataFactory::data_new_func() returned NULL",
                 G_STRFUNC);
    }

  return NULL;
}

PikaData *
pika_data_factory_data_get_standard (PikaDataFactory *factory,
                                     PikaContext     *context)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);
  g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);

  if (factory->priv->data_get_standard_func)
    return factory->priv->data_get_standard_func (context);

  return NULL;
}

PikaData *
pika_data_factory_data_duplicate (PikaDataFactory *factory,
                                  PikaData        *data)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);
  g_return_val_if_fail (PIKA_IS_DATA (data), NULL);

  return PIKA_DATA_FACTORY_GET_CLASS (factory)->data_duplicate (factory, data);
}

gboolean
pika_data_factory_data_delete (PikaDataFactory  *factory,
                               PikaData         *data,
                               gboolean          delete_from_disk,
                               GError          **error)
{
  PikaDataFactoryPrivate *priv;
  gboolean                retval = TRUE;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE);
  g_return_val_if_fail (PIKA_IS_DATA (data), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  priv = GET_PRIVATE (factory);

  if (pika_container_have (priv->container, PIKA_OBJECT (data)))
    {
      g_object_ref (data);

      pika_container_remove (priv->container, PIKA_OBJECT (data));

      retval = PIKA_DATA_FACTORY_GET_CLASS (factory)->data_delete (factory, data,
                                                                   delete_from_disk,
                                                                   error);

      g_object_unref (data);
    }

  return retval;
}

gboolean
pika_data_factory_data_save_single (PikaDataFactory  *factory,
                                    PikaData         *data,
                                    GError          **error)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), FALSE);
  g_return_val_if_fail (PIKA_IS_DATA (data), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (! pika_data_is_dirty (data) || pika_data_get_image (data))
    return TRUE;

  if (! pika_data_get_file (data))
    {
      GFile  *writable_dir;
      GError *my_error = NULL;

      writable_dir = pika_data_factory_get_save_dir (factory, &my_error);

      if (! writable_dir)
        {
          g_set_error (error, PIKA_DATA_ERROR, 0,
                       _("Failed to save data:\n\n%s"),
                       my_error->message);
          g_clear_error (&my_error);

          return FALSE;
        }

      pika_data_create_filename (data, writable_dir);

      g_object_unref (writable_dir);
    }

  if (! pika_data_is_writable (data))
    return FALSE;

  if (factory->priv->pika->be_verbose)
    {
      GFile *file = pika_data_get_file (data);

      if (file)
        g_print ("Writing dirty data '%s'\n",
                 pika_file_get_utf8_name (file));
    }

  if (! pika_data_save (data, error))
    {
      /*  check if there actually was an error (no error
       *  means the data class does not implement save)
       */
      if (! error)
        g_set_error (error, PIKA_DATA_ERROR, 0,
                     _("Failed to save data:\n\n%s"),
                     "Data class does not implement saving");

      return FALSE;
    }

  return TRUE;
}

void
pika_data_factory_data_foreach (PikaDataFactory     *factory,
                                gboolean             skip_internal,
                                PikaDataForeachFunc  callback,
                                gpointer             user_data)
{
  GList *list;

  g_return_if_fail (PIKA_IS_DATA_FACTORY (factory));
  g_return_if_fail (callback != NULL);

  list = PIKA_LIST (factory->priv->container)->queue->head;

  while (list)
    {
      GList *next = g_list_next (list);

      if (! (skip_internal && pika_data_is_internal (list->data)))
        callback (factory, list->data, user_data);

      list = next;
    }
}

Pika *
pika_data_factory_get_pika (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  return factory->priv->pika;
}

GType
pika_data_factory_get_data_type (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), G_TYPE_NONE);

  return pika_container_get_children_type (factory->priv->container);
}

PikaContainer *
pika_data_factory_get_container (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  return factory->priv->container;
}

PikaContainer *
pika_data_factory_get_container_obsolete (PikaDataFactory *factory)
{
  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  return factory->priv->container_obsolete;
}

GList *
pika_data_factory_get_data_path (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  gchar                  *path = NULL;
  GList                  *list = NULL;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  g_object_get (priv->pika->config,
                priv->path_property_name, &path,
                NULL);

  if (path)
    {
      list = pika_config_path_expand_to_files (path, NULL);
      g_free (path);
    }

  return list;
}

GList *
pika_data_factory_get_data_path_writable (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  gchar                  *path = NULL;
  GList                  *list = NULL;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  g_object_get (priv->pika->config,
                priv->writable_property_name, &path,
                NULL);

  if (path)
    {
      list = pika_config_path_expand_to_files (path, NULL);
      g_free (path);
    }

  return list;
}

const GList *
pika_data_factory_get_data_path_ext (PikaDataFactory *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);
  GList                  *list = NULL;

  g_return_val_if_fail (PIKA_IS_DATA_FACTORY (factory), NULL);

  g_object_get (priv->pika->extension_manager,
                priv->ext_property_name, &list,
                NULL);

  return list;
}


/*  private functions  */

static void
pika_data_factory_path_notify (GObject          *object,
                               const GParamSpec *pspec,
                               PikaDataFactory  *factory)
{
  PikaDataFactoryPrivate *priv = GET_PRIVATE (factory);

  pika_set_busy (priv->pika);

  pika_data_factory_data_refresh (factory, pika_get_user_context (priv->pika));

  pika_unset_busy (priv->pika);
}

static GFile *
pika_data_factory_get_save_dir (PikaDataFactory  *factory,
                                GError          **error)
{
  GList *path;
  GList *writable_path;
  GFile *writable_dir = NULL;

  path          = pika_data_factory_get_data_path          (factory);
  writable_path = pika_data_factory_get_data_path_writable (factory);

  if (writable_path)
    {
      GList    *list;
      gboolean  found_any = FALSE;

      for (list = writable_path; list; list = g_list_next (list))
        {
          GList *found = g_list_find_custom (path, list->data,
                                             (GCompareFunc) pika_file_compare);
          if (found)
            {
              GFile *dir = found->data;

              found_any = TRUE;

              if (g_file_query_file_type (dir, G_FILE_QUERY_INFO_NONE,
                                          NULL) != G_FILE_TYPE_DIRECTORY)
                {
                  /*  error out only if this is the last chance  */
                  if (! list->next)
                    {
                      g_set_error (error, PIKA_DATA_ERROR, 0,
                                   _("You have a writable data folder "
                                     "configured (%s), but this folder does "
                                     "not exist. Please create the folder or "
                                     "fix your configuration in the "
                                     "Preferences dialog's 'Folders' section."),
                                   pika_file_get_utf8_name (dir));
                    }
                }
              else
                {
                  writable_dir = g_object_ref (dir);
                  break;
                }
            }
        }

      if (! writable_dir && ! found_any)
        {
          g_set_error (error, PIKA_DATA_ERROR, 0,
                       _("You have a writable data folder configured, but this "
                         "folder is not part of your data search path. You "
                         "probably edited the pikarc file manually, "
                         "please fix it in the Preferences dialog's 'Folders' "
                         "section."));
        }
    }
  else
    {
      g_set_error (error, PIKA_DATA_ERROR, 0,
                   _("You don't have any writable data folder configured."));
    }

  g_list_free_full (path,          (GDestroyNotify) g_object_unref);
  g_list_free_full (writable_path, (GDestroyNotify) g_object_unref);

  return writable_dir;
}
