/*
 * Copyright © 2017 Endless Mobile, Inc.
 * Copyright (C) 2022 Igalia S.L.
 *
 * SPDX-License-Identifier: LGPL-2.0+
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <https://www.gnu.org/licenses/>.
 *
 * Authors:
 *  - Philip Withnall <withnall@endlessm.com>
 */

#include "config.h"

#include <gio/gio.h>
#include <glib-object.h>
#include <glib.h>
#include <libglnx.h>

#include "test-mock-gio.h"

/**
 * SECTION:mock-gio
 * @title: Mock GIO volume interfaces
 * @short_description: Mock implementations of GIO volume, mount and drive
 *    interfaces
 * @stability: Unstable
 * @include: tests/test-mock-gio.h
 *
 * A set of classes implementing GIO interfaces for volumes, mounts, drives
 * and volume monitoring, which return mock data to the caller when used. These
 * are designed for use in unit tests, to mock up removable drives when testing
 * code which monitors such drives being added and removed and then queries
 * properties of them.
 *
 * By returning mock drive locations to the caller, for example, the contents of
 * a removable drive may be mocked up using temporary files.
 *
 * Currently, all the mock data returned by these classes to callers is static,
 * set at construction time.
 *
 * Since: 2017.8
 */

/* Mock volume monitor class. This returns a static set of data to the caller,
 * which it was initialised with. */
struct _OstreeMockVolumeMonitor
{
  GVolumeMonitor parent_instance;

  GList *mounts;  /* (element-type OstreeMockMount) */
  GList *volumes; /* (element-type OstreeMockVolume) */
};

G_DEFINE_TYPE (OstreeMockVolumeMonitor, ostree_mock_volume_monitor, G_TYPE_VOLUME_MONITOR)

static GList *
ostree_mock_volume_monitor_get_mounts (GVolumeMonitor *monitor)
{
  OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor);
  return g_list_copy_deep (self->mounts, (GCopyFunc)g_object_ref, NULL);
}

static GList *
ostree_mock_volume_monitor_get_volumes (GVolumeMonitor *monitor)
{
  OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor);
  return g_list_copy_deep (self->volumes, (GCopyFunc)g_object_ref, NULL);
}

static void
ostree_mock_volume_monitor_init (OstreeMockVolumeMonitor *self)
{
  /* Nothing to see here. */
}

static void
ostree_mock_volume_monitor_dispose (GObject *object)
{
  OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (object);

  g_list_free_full (self->volumes, g_object_unref);
  self->volumes = NULL;

  g_list_free_full (self->mounts, g_object_unref);
  self->mounts = NULL;

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

static void
ostree_mock_volume_monitor_class_init (OstreeMockVolumeMonitorClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass);

  object_class->dispose = ostree_mock_volume_monitor_dispose;

  monitor_class->get_mounts = ostree_mock_volume_monitor_get_mounts;
  monitor_class->get_volumes = ostree_mock_volume_monitor_get_volumes;
}

/**
 * ostree_mock_volume_monitor_new:
 * @mounts: (element-type GMount) (transfer none): list of current #GMounts
 * @volumes: (element-type GVolume) (transfer none): list of current #GVolumes
 *
 * Create a new mock #GVolumeMonitor which will return the given static lists of
 * #GMounts and #GVolumes to any caller of g_volume_monitor_get_mounts() or
 * g_volume_monitor_get_volumes().
 *
 * Typically, the elements of @mounts will be #OstreeMockMount objects and the
 * elements of @volumes will be #OstreeMockVolume objects; but this does not
 * have to be the case.
 *
 * Returns: (transfer full): a new #GVolumeMonitor object
 * Since: 2017.8
 */
GVolumeMonitor *
ostree_mock_volume_monitor_new (GList *mounts, GList *volumes)
{
  g_autoptr (OstreeMockVolumeMonitor) monitor = NULL;

  monitor = g_object_new (OSTREE_TYPE_MOCK_VOLUME_MONITOR, NULL);
  monitor->mounts = g_list_copy_deep (mounts, (GCopyFunc)g_object_ref, NULL);
  monitor->volumes = g_list_copy_deep (volumes, (GCopyFunc)g_object_ref, NULL);

  return (GVolumeMonitor *)g_steal_pointer (&monitor);
}

/* Mock volume class. This returns a static set of data to the caller, which it
 * was initialised with. */
struct _OstreeMockVolume
{
  GObject parent_instance;

  gchar *name;
  GDrive *drive; /* (owned) (nullable) */
  GMount *mount; /* (owned) (nullable) */
};

static void ostree_mock_volume_iface_init (GVolumeIface *iface);

G_DEFINE_TYPE_WITH_CODE (OstreeMockVolume, ostree_mock_volume, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, ostree_mock_volume_iface_init))

static gchar *
ostree_mock_volume_get_name (GVolume *volume)
{
  OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume);
  return g_strdup (self->name);
}

static GDrive *
ostree_mock_volume_get_drive (GVolume *volume)
{
  OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume);
  return (self->drive != NULL) ? g_object_ref (self->drive) : NULL;
}

static GMount *
ostree_mock_volume_get_mount (GVolume *volume)
{
  OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume);
  return (self->mount != NULL) ? g_object_ref (self->mount) : NULL;
}

static void
ostree_mock_volume_init (OstreeMockVolume *self)
{
  /* Nothing to see here. */
}

static void
ostree_mock_volume_dispose (GObject *object)
{
  OstreeMockVolume *self = OSTREE_MOCK_VOLUME (object);

  g_clear_pointer (&self->name, g_free);
  g_clear_object (&self->drive);
  g_clear_object (&self->mount);

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

static void
ostree_mock_volume_class_init (OstreeMockVolumeClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = ostree_mock_volume_dispose;
}

static void
ostree_mock_volume_iface_init (GVolumeIface *iface)
{
  iface->get_name = ostree_mock_volume_get_name;
  iface->get_drive = ostree_mock_volume_get_drive;
  iface->get_mount = ostree_mock_volume_get_mount;
}

/**
 * ostree_mock_volume_new:
 * @name: volume name
 * @drive: (transfer none) (nullable): drive for the volume, or %NULL if none
 *    should be associated
 * @mount: (transfer none) (nullable): mount for the volume, or %NULL if it’s
 *    not mounted
 *
 * Create a new mock #GVolume which will return the given static @name, @drive
 * and @mount to any caller of its getter methods. There is currently no
 * provision for changing these values dynamically. There is also currently no
 * provision for mocking the other getters of #GVolume.
 *
 * Typically, @drive will be an #OstreeMockDrive object and @mount will be an
 * #OstreeMockMount object; but this does not have to be the case.
 *
 * Returns: (transfer full): a new #GVolume object
 * Since: 2017.8
 */
OstreeMockVolume *
ostree_mock_volume_new (const gchar *name, GDrive *drive, GMount *mount)
{
  g_autoptr (OstreeMockVolume) volume = NULL;

  volume = g_object_new (OSTREE_TYPE_MOCK_VOLUME, NULL);
  volume->name = g_strdup (name);
  volume->drive = (drive != NULL) ? g_object_ref (drive) : NULL;
  volume->mount = (mount != NULL) ? g_object_ref (mount) : NULL;

  return g_steal_pointer (&volume);
}

/* Mock drive class. This returns a static set of data to the caller, which it
 * was initialised with. */
struct _OstreeMockDrive
{
  GObject parent_instance;

  gboolean is_removable;
};

static void ostree_mock_drive_iface_init (GDriveIface *iface);

G_DEFINE_TYPE_WITH_CODE (OstreeMockDrive, ostree_mock_drive, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_DRIVE, ostree_mock_drive_iface_init))

#if GLIB_CHECK_VERSION(2, 50, 0)
static gboolean
ostree_mock_drive_is_removable (GDrive *drive)
{
  OstreeMockDrive *self = OSTREE_MOCK_DRIVE (drive);
  return self->is_removable;
}
#endif

static void
ostree_mock_drive_init (OstreeMockDrive *self)
{
  /* Nothing to see here. */
}

static void
ostree_mock_drive_class_init (OstreeMockDriveClass *klass)
{
  /* Nothing to see here. */
}

static void
ostree_mock_drive_iface_init (GDriveIface *iface)
{
#if GLIB_CHECK_VERSION(2, 50, 0)
  iface->is_removable = ostree_mock_drive_is_removable;
#endif
}

/**
 * ostree_mock_drive_new:
 * @is_removable: %TRUE if the drive is removable; %FALSE otherwise
 *
 * Create a new mock #GDrive which will return the given static @is_removable to
 * any caller of its getter methods. There is currently no provision for mocking
 * the other getters of #GDrive.
 *
 * Returns: (transfer full): a new #GDrive object
 * Since: 2017.8
 */
OstreeMockDrive *
ostree_mock_drive_new (gboolean is_removable)
{
  g_autoptr (OstreeMockDrive) drive = NULL;

  drive = g_object_new (OSTREE_TYPE_MOCK_DRIVE, NULL);
  drive->is_removable = is_removable;

  return g_steal_pointer (&drive);
}

/* Mock mount class. This returns a static set of data to the caller, which it
 * was initialised with. */
struct _OstreeMockMount
{
  GObject parent_instance;

  gchar *name; /* (owned) */
  GFile *root; /* (owned) */
};

static void ostree_mock_mount_iface_init (GMountIface *iface);

G_DEFINE_TYPE_WITH_CODE (OstreeMockMount, ostree_mock_mount, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_MOUNT, ostree_mock_mount_iface_init))

static gchar *
ostree_mock_mount_get_name (GMount *mount)
{
  OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount);
  return g_strdup (self->name);
}

static GFile *
ostree_mock_mount_get_root (GMount *mount)
{
  OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount);
  return g_object_ref (self->root);
}

static void
ostree_mock_mount_init (OstreeMockMount *self)
{
  /* Nothing to see here. */
}

static void
ostree_mock_mount_dispose (GObject *object)
{
  OstreeMockMount *self = OSTREE_MOCK_MOUNT (object);

  g_clear_pointer (&self->name, g_free);
  g_clear_object (&self->root);

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

static void
ostree_mock_mount_class_init (OstreeMockMountClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->dispose = ostree_mock_mount_dispose;
}

static void
ostree_mock_mount_iface_init (GMountIface *iface)
{
  iface->get_name = ostree_mock_mount_get_name;
  iface->get_root = ostree_mock_mount_get_root;
}

/**
 * ostree_mock_mount_new:
 * @name: mount name
 * @root: (transfer none): root path for the mounted file system
 *
 * Create a new mock #GMount which will return the given static @name and @root
 * to any caller of its getter methods. There is currently no provision for
 * mocking the other getters of #GMount.
 *
 * Typically, @root will point to a temporary directory where a mocked file
 * system is present; but this does not have to be the case.
 *
 * Returns: (transfer full): a new #GMount object
 * Since: 2017.8
 */
OstreeMockMount *
ostree_mock_mount_new (const gchar *name, GFile *root)
{
  g_autoptr (OstreeMockMount) mount = NULL;

  mount = g_object_new (OSTREE_TYPE_MOCK_MOUNT, NULL);
  mount->name = g_strdup (name);
  mount->root = g_object_ref (root);

  return g_steal_pointer (&mount);
}
