/* GStreamer
 * Copyright (C) 2018 Joshua M. Doe <oss@nvl.army.mil>
 *
 * dshowdeviceprovider.cpp: DirectShow device probing
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

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

#include "gstdshow.h"
#include "gstdshowvideosrc.h"
#include "dshowdeviceprovider.h"

#include <gst/gst.h>

#include <windows.h>

GST_DEBUG_CATEGORY_EXTERN (dshowsrcwrapper_debug);
#define GST_CAT_DEFAULT dshowsrcwrapper_debug


static GstDevice *gst_dshow_device_new (guint id,
    const gchar * device_name, GstCaps * caps, const gchar * device_path,
    GstDshowDeviceType type);

G_DEFINE_TYPE (GstDshowDeviceProvider, gst_dshow_device_provider,
    GST_TYPE_DEVICE_PROVIDER);

static void gst_dshow_device_provider_dispose (GObject * gobject);

static GList *gst_dshow_device_provider_probe (GstDeviceProvider * provider);
static gboolean gst_dshow_device_provider_start (GstDeviceProvider * provider);
static void gst_dshow_device_provider_stop (GstDeviceProvider * provider);

static void
gst_dshow_device_provider_class_init (GstDshowDeviceProviderClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);

  gobject_class->dispose = gst_dshow_device_provider_dispose;

  dm_class->probe = gst_dshow_device_provider_probe;
  dm_class->start = gst_dshow_device_provider_start;
  dm_class->stop = gst_dshow_device_provider_stop;

  gst_device_provider_class_set_static_metadata (dm_class,
      "DirectShow Device Provider", "Source/Audio/Video",
      "List and provide DirectShow source devices",
      "Руслан Ижбулатов <lrn1986@gmail.com>");
}

static void
gst_dshow_device_provider_init (GstDshowDeviceProvider * self)
{
  CoInitializeEx (NULL, COINIT_MULTITHREADED);
}

static void
gst_dshow_device_provider_dispose (GObject * gobject)
{
  CoUninitialize ();

  G_OBJECT_CLASS (gst_dshow_device_provider_parent_class)->dispose (gobject);
}

static GstDevice *
new_video_source (const DshowDeviceEntry * info)
{
  g_assert (info && info->device != NULL);

  return gst_dshow_device_new (info->device_index, info->device_name,
      info->caps, info->device, GST_DSHOW_DEVICE_TYPE_VIDEO_SOURCE);
}

static GList *
gst_dshow_device_provider_probe (GstDeviceProvider * provider)
{
  /*GstDshowDeviceProvider *self = GST_DSHOW_DEVICE_PROVIDER (provider); */
  GList *devices, *cur;
  GList *result;

  result = NULL;

  devices = gst_dshow_enumerate_devices (&CLSID_VideoInputDeviceCategory, TRUE);
  if (devices == NULL)
    return result;

  /* TODO: try and sort camera first like ksvideosrc? */

  for (cur = devices; cur != NULL; cur = cur->next) {
    GstDevice *source;
    DshowDeviceEntry *entry = (DshowDeviceEntry *) cur->data;

    source = new_video_source (entry);
    if (source)
      result = g_list_prepend (result, gst_object_ref_sink (source));
  }

  result = g_list_reverse (result);

  gst_dshow_device_list_free (devices);

  return result;
}

static gboolean
gst_dshow_device_provider_start (GstDeviceProvider * provider)
{
  GList *devs;
  GList *dev;
  GstDshowDeviceProvider *self = GST_DSHOW_DEVICE_PROVIDER (provider);

  devs = gst_dshow_device_provider_probe (provider);
  for (dev = devs; dev; dev = dev->next) {
    if (dev->data)
      gst_device_provider_device_add (provider, (GstDevice *) dev->data);
  }
  g_list_free_full (devs, gst_object_unref);

  return TRUE;
}

static void
gst_dshow_device_provider_stop (GstDeviceProvider * provider)
{

}

enum
{
  PROP_0,
  PROP_DEVICE,
  PROP_DEVICE_NAME,
  PROP_DEVICE_INDEX
};

G_DEFINE_TYPE (GstDshowDevice, gst_dshow_device, GST_TYPE_DEVICE);

static void gst_dshow_device_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
static void gst_dshow_device_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_dshow_device_finalize (GObject * object);
static GstElement *gst_dshow_device_create_element (GstDevice * device,
    const gchar * name);

static void
gst_dshow_device_class_init (GstDshowDeviceClass * klass)
{
  GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  dev_class->create_element = gst_dshow_device_create_element;

  object_class->get_property = gst_dshow_device_get_property;
  object_class->set_property = gst_dshow_device_set_property;
  object_class->finalize = gst_dshow_device_finalize;

  g_object_class_install_property (object_class, PROP_DEVICE,
      g_param_spec_string ("device", "Device",
          "DirectShow device path (@..classID/name)", "",
          (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE |
              G_PARAM_CONSTRUCT_ONLY)));

  g_object_class_install_property (object_class, PROP_DEVICE_NAME,
      g_param_spec_string ("device-name", "Device name",
          "Human-readable name of the audio/video device", "",
          (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE |
              G_PARAM_CONSTRUCT_ONLY)));

  g_object_class_install_property (object_class, PROP_DEVICE_INDEX,
      g_param_spec_int ("device-index", "Device index",
          "Index of the enumerated audio/video device", 0, G_MAXINT, 0,
          (GParamFlags) (G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE |
              G_PARAM_CONSTRUCT_ONLY)));
}

static void
gst_dshow_device_init (GstDshowDevice * device)
{
}

static void
gst_dshow_device_finalize (GObject * object)
{
  GstDshowDevice *device = GST_DSHOW_DEVICE (object);

  g_free (device->device);
  g_free (device->device_name);

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

static GstElement *
gst_dshow_device_create_element (GstDevice * device, const gchar * name)
{
  GstDshowDevice *dev = GST_DSHOW_DEVICE (device);
  GstElement *elem;

  elem = gst_element_factory_make (dev->element, name);
  g_object_set (elem, "device", dev->device, NULL);
  g_object_set (elem, "device-name", dev->device_name, NULL);

  return elem;
}


static GstDevice *
gst_dshow_device_new (guint device_index, const gchar * device_name,
    GstCaps * caps, const gchar * device_path, GstDshowDeviceType type)
{
  GstDshowDevice *gstdev;
  const gchar *element = NULL;
  const gchar *klass = NULL;

  g_return_val_if_fail (device_name, NULL);
  g_return_val_if_fail (device_path, NULL);
  g_return_val_if_fail (caps, NULL);

  switch (type) {
    case GST_DSHOW_DEVICE_TYPE_VIDEO_SOURCE:
      element = "dshowvideosrc";
      klass = "Video/Source";
      break;
    case GST_DSHOW_DEVICE_TYPE_AUDIO_SOURCE:
      element = "dshowaudiosrc";
      klass = "Audio/Source";
      break;
    default:
      g_assert_not_reached ();
      break;
  }

  /* set props of parent device class */
  gstdev = (GstDshowDevice *) g_object_new (GST_TYPE_DSHOW_DEVICE,
      "display-name", device_name, "caps", caps, "device-class", klass, NULL);

  gstdev->type = type;
  gstdev->device = g_strdup (device_path);
  gstdev->device_name = g_strdup (device_name);
  gstdev->device_index = device_index;
  gstdev->element = element;

  return GST_DEVICE (gstdev);
}


static void
gst_dshow_device_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstDshowDevice *device;

  device = GST_DSHOW_DEVICE_CAST (object);

  switch (prop_id) {
    case PROP_DEVICE:
      /* G_PARAM_CONSTRUCT_ONLY */
      g_value_set_string (value, device->device);
      break;
    case PROP_DEVICE_NAME:
      /* G_PARAM_CONSTRUCT_ONLY */
      g_value_set_string (value, device->device_name);
      break;
    case PROP_DEVICE_INDEX:
      g_value_set_int (value, device->device_index);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}


static void
gst_dshow_device_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstDshowDevice *device;

  device = GST_DSHOW_DEVICE_CAST (object);

  switch (prop_id) {
    case PROP_DEVICE:
      device->device = g_value_dup_string (value);
      break;
    case PROP_DEVICE_NAME:
      device->device_name = g_value_dup_string (value);
      break;
    case PROP_DEVICE_INDEX:
      device->device_index = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}
