/*
 * Copyright (C) 2019 Mathieu Duponchelle <mathieu@centricular.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Simple device provider example.
 *
 * Usage:
 *
 * GST_PLUGIN_PATH=$GST_PLUGIN_PATH:/path/to/libexample_device_provider.so/folder gst-device-monitor-1.0 -f
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gst/gst.h>

#define NEW_DEVICE_INTERVAL 1   /* seconds */

#define EXAMPLE_TYPE_DEVICE_PROVIDER example_device_provider_get_type()
#define EXAMPLE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EXAMPLE_TYPE_DEVICE_PROVIDER,ExampleDeviceProvider))

typedef struct _ExampleDeviceProvider ExampleDeviceProvider;
typedef struct _ExampleDeviceProviderClass ExampleDeviceProviderClass;

struct _ExampleDeviceProviderClass
{
  GstDeviceProviderClass parent_class;
};

/**
 * Our device provider instance.
 *
 * @factory: the videotestsrc factory
 * @patterns: When started, the list of videotestsrc pattern
 *            (as strings) to iterate through when adding new devices,
 *            eg "smpte", "snow", ...
 * @timeout_id: When started, we will add a new device every
 *            %NEW_DEVICE_INTERVAL seconds
 */
struct _ExampleDeviceProvider
{
  GstDeviceProvider parent;
  GstElementFactory *factory;
  GList *patterns;
  guint timeout_id;
};

static GType example_device_provider_get_type (void);

G_DEFINE_TYPE (ExampleDeviceProvider, example_device_provider,
    GST_TYPE_DEVICE_PROVIDER);

#define EXAMPLE_TYPE_DEVICE example_device_get_type()
#define EXAMPLE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),EXAMPLE_TYPE_DEVICE,ExampleDevice))

typedef struct _ExampleDevice ExampleDevice;
typedef struct _ExampleDeviceClass ExampleDeviceClass;

struct _ExampleDeviceClass
{
  GstDeviceClass parent_class;
};

/* Our example device, it simply exposes a videotestsrc with a specific
 * pattern.
 */
struct _ExampleDevice
{
  GstDevice parent;

  gchar *pattern;
  GstElementFactory *factory;
};

static GType example_device_get_type (void);

G_DEFINE_TYPE (ExampleDevice, example_device, GST_TYPE_DEVICE);

static void
example_device_init (ExampleDevice * self)
{
}

static void
example_device_finalize (GObject * object)
{
  ExampleDevice *self = EXAMPLE_DEVICE (object);

  g_free (self->pattern);

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

static void
example_device_dispose (GObject * object)
{
  ExampleDevice *self = EXAMPLE_DEVICE (object);

  gst_object_replace ((GstObject **) & self->factory, NULL);

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

static GstElement *
example_device_create_element (GstDevice * device, const gchar * name)
{
  ExampleDevice *self = EXAMPLE_DEVICE (device);
  GstElement *ret;

  ret = gst_element_factory_create (self->factory, name);

  gst_util_set_object_arg (G_OBJECT (ret), "pattern", self->pattern);

  return ret;
}

static void
example_device_class_init (ExampleDeviceClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstDeviceClass *gst_device_class = GST_DEVICE_CLASS (klass);

  gobject_class->finalize = GST_DEBUG_FUNCPTR (example_device_finalize);
  gobject_class->dispose = GST_DEBUG_FUNCPTR (example_device_dispose);

  gst_device_class->create_element =
      GST_DEBUG_FUNCPTR (example_device_create_element);
}

static GstDevice *
example_device_new (GstElementFactory * factory, const gchar * pattern)
{
  GstDevice *ret;
  gchar *display_name;
  GstCaps *caps;
  const GList *templates;

  templates = gst_element_factory_get_static_pad_templates (factory);
  caps = gst_static_pad_template_get_caps ((GstStaticPadTemplate *)
      templates->data);

  display_name = g_strdup_printf ("example-device-%s", pattern);

  ret = GST_DEVICE (g_object_new (EXAMPLE_TYPE_DEVICE,
          "display-name", display_name,
          "device-class", "Video/Source", "caps", caps, NULL));

  g_free (display_name);
  gst_caps_unref (caps);

  EXAMPLE_DEVICE (ret)->pattern = g_strdup (pattern);
  EXAMPLE_DEVICE (ret)->factory =
      GST_ELEMENT_FACTORY (gst_object_ref (factory));

  return ret;
}

static void
example_device_provider_init (ExampleDeviceProvider * self)
{
  self->factory = gst_element_factory_find ("videotestsrc");

  /* Ensure we can introspect the factory */
  gst_object_unref (gst_plugin_feature_load (GST_PLUGIN_FEATURE
          (self->factory)));

}

/* Called when gst_device_provider_get_devices() is called on a provider that
 * hasn't been started, or doesn't implement #GstDeviceProvider.start().
 *
 * In that case, let's return a single example device, with a snow pattern.
 */
static GList *
example_device_provider_probe (GstDeviceProvider * provider)
{
  ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);
  GList *ret = NULL;

  ret = g_list_prepend (ret, example_device_new (self->factory, "snow"));

  return ret;
}

static gboolean
example_device_provider_next_device (ExampleDeviceProvider * self)
{
  GstDevice *device;
  gboolean ret = G_SOURCE_CONTINUE;

  if (!self->patterns)
    goto no_more_patterns;

  device = example_device_new (self->factory, (gchar *) self->patterns->data);
  gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device);
  g_free (self->patterns->data);
  self->patterns = g_list_delete_link (self->patterns, self->patterns);

done:
  return ret;

no_more_patterns:
  GST_DEBUG_OBJECT (self, "Went through all videotestsrc patterns!");
  ret = G_SOURCE_REMOVE;
  goto done;
}

/* Start adding devices every %NEW_DEVICE_INTERVAL seconds.
 * We will stop once we have consumed all the available videotestsrc
 * patterns, or when our #GstDeviceProvider.stop() implementation is
 * called.
 */
static gboolean
example_device_provider_start (GstDeviceProvider * provider)
{
  ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);
  GType element_type;
  GTypeClass *element_class;
  GParamSpec *pspec;
  GEnumClass *value_class;
  guint i;

  g_assert (!self->timeout_id);

  element_type = gst_element_factory_get_element_type (self->factory);
  element_class = (GTypeClass *) g_type_class_ref (element_type);
  pspec =
      g_object_class_find_property ((GObjectClass *) element_class, "pattern");
  value_class = (GEnumClass *) g_type_class_ref (pspec->value_type);

  for (i = 0; i < value_class->n_values; i++) {
    GEnumValue *val = &value_class->values[i];

    self->patterns = g_list_append (self->patterns, g_strdup (val->value_nick));
  }

  g_type_class_unref (value_class);
  g_type_class_unref (element_class);

  self->timeout_id =
      g_timeout_add_seconds (NEW_DEVICE_INTERVAL,
      (GSourceFunc) example_device_provider_next_device, self);

  return TRUE;
}

/* Simply stop adding devices by removing our timeout. */
static void
example_device_provider_stop (GstDeviceProvider * provider)
{
  ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (provider);

  g_assert (self->timeout_id);

  if (self->patterns) {
    g_list_free_full (self->patterns, g_free);
    self->patterns = NULL;
  }

  g_source_remove (self->timeout_id);
  self->timeout_id = 0;
}

static void
example_device_provider_dispose (GObject * object)
{
  ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (object);

  gst_object_replace ((GstObject **) & self->factory, NULL);

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

static void
example_device_provider_finalize (GObject * object)
{
  ExampleDeviceProvider *self = EXAMPLE_DEVICE_PROVIDER (object);

  if (self->patterns)
    g_list_free_full (self->patterns, g_free);

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

static void
example_device_provider_class_init (ExampleDeviceProviderClass * klass)
{
  GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = GST_DEBUG_FUNCPTR (example_device_provider_dispose);
  gobject_class->finalize =
      GST_DEBUG_FUNCPTR (example_device_provider_finalize);

  dm_class->probe = GST_DEBUG_FUNCPTR (example_device_provider_probe);
  dm_class->start = GST_DEBUG_FUNCPTR (example_device_provider_start);
  dm_class->stop = GST_DEBUG_FUNCPTR (example_device_provider_stop);

  gst_device_provider_class_set_static_metadata (dm_class,
      "Example Device Provider", "Source/Video",
      "List and provides example source devices",
      "Mathieu Duponchelle <mathieu@centricular.com>");
}

static gboolean
plugin_init (GstPlugin * plugin)
{
  return gst_device_provider_register (plugin, "exampledeviceprovider",
      GST_RANK_PRIMARY, EXAMPLE_TYPE_DEVICE_PROVIDER);
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    example_device_provider,
    "Example device provider",
    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)