/*
 * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.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.
 */

#include "kshelpers.h"

/* This plugin is from the era of Windows XP and uses APIs that have been
 * deprecated since then. Let's pretend we're Windows XP too so that Windows
 * lets us use that deprecated API. */
#undef NTDDI_VERSION
#undef _WIN32_WINNT
#define NTDDI_VERSION NTDDI_WINXP
#define _WIN32_WINNT _WIN32_WINNT_WINXP

#include <ksmedia.h>
#include <setupapi.h>
#include <gst/gst.h>

GST_DEBUG_CATEGORY_EXTERN (gst_ks_debug);
#define GST_CAT_DEFAULT gst_ks_debug

#ifndef STATIC_KSPROPSETID_Wave_Queued
#define STATIC_KSPROPSETID_Wave_Queued \
    0x16a15b10L, 0x16f0, 0x11d0, { 0xa1, 0x95, 0x00, 0x20, 0xaf, 0xd1, 0x56, 0xe4 }
DEFINE_GUIDSTRUCT ("16a15b10-16f0-11d0-a195-0020afd156e4",
    KSPROPSETID_Wave_Queued);
#endif

gboolean
ks_is_valid_handle (HANDLE h)
{
  return (h != INVALID_HANDLE_VALUE && h != NULL);
}

GList *
ks_enumerate_devices (const GUID * devtype, const GUID * direction_category)
{
  GList *result = NULL;
  HDEVINFO devinfo;
  gint i;

  devinfo = SetupDiGetClassDevsW (devtype, NULL, NULL,
      DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
  if (!ks_is_valid_handle (devinfo))
    return NULL;                /* no devices */

  for (i = 0;; i++) {
    BOOL success;
    SP_DEVICE_INTERFACE_DATA if_data = { 0, };
    SP_DEVICE_INTERFACE_DATA if_alias_data = { 0, };
    SP_DEVICE_INTERFACE_DETAIL_DATA_W *if_detail_data;
    DWORD if_detail_data_size;
    SP_DEVINFO_DATA devinfo_data = { 0, };
    DWORD req_size;

    if_data.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);

    success = SetupDiEnumDeviceInterfaces (devinfo, NULL, devtype, i, &if_data);
    if (!success)               /* all devices enumerated? */
      break;

    if_alias_data.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA);
    success =
        SetupDiGetDeviceInterfaceAlias (devinfo, &if_data, direction_category,
        &if_alias_data);
    if (!success)
      continue;

    if_detail_data_size = (MAX_PATH - 1) * sizeof (gunichar2);
    if_detail_data = g_malloc0 (if_detail_data_size);
    if_detail_data->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA_W);

    devinfo_data.cbSize = sizeof (SP_DEVINFO_DATA);

    success = SetupDiGetDeviceInterfaceDetailW (devinfo, &if_data,
        if_detail_data, if_detail_data_size, &req_size, &devinfo_data);
    if (success) {
      KsDeviceEntry *entry;
      WCHAR buf[512];

      entry = g_new0 (KsDeviceEntry, 1);
      entry->index = i;
      entry->path =
          g_utf16_to_utf8 (if_detail_data->DevicePath, -1, NULL, NULL, NULL);

      if (SetupDiGetDeviceRegistryPropertyW (devinfo, &devinfo_data,
              SPDRP_FRIENDLYNAME, NULL, (BYTE *) buf, sizeof (buf), NULL)) {
        entry->name = g_utf16_to_utf8 (buf, -1, NULL, NULL, NULL);
      }

      if (entry->name == NULL) {
        if (SetupDiGetDeviceRegistryPropertyW (devinfo, &devinfo_data,
                SPDRP_DEVICEDESC, NULL, (BYTE *) buf, sizeof (buf), NULL)) {
          entry->name = g_utf16_to_utf8 (buf, -1, NULL, NULL, NULL);
        }
      }

      if (entry->name != NULL)
        result = g_list_prepend (result, entry);
      else
        ks_device_entry_free (entry);
    }

    g_free (if_detail_data);
  }

  SetupDiDestroyDeviceInfoList (devinfo);

  return g_list_reverse (result);
}

void
ks_device_entry_free (KsDeviceEntry * entry)
{
  if (entry == NULL)
    return;

  g_free (entry->path);
  g_free (entry->name);

  g_free (entry);
}

void
ks_device_list_free (GList * devices)
{
  GList *cur;

  for (cur = devices; cur != NULL; cur = cur->next)
    ks_device_entry_free (cur->data);

  g_list_free (devices);
}

static gboolean
ks_sync_device_io_control (HANDLE device, gulong io_control_code,
    gpointer in_buffer, gulong in_buffer_size, gpointer out_buffer,
    gulong out_buffer_size, gulong * bytes_returned, gulong * error)
{
  OVERLAPPED overlapped = { 0, };
  BOOL success;

  overlapped.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);

  success = DeviceIoControl (device, io_control_code, in_buffer,
      in_buffer_size, out_buffer, out_buffer_size, bytes_returned, &overlapped);
  if (!success) {
    DWORD err;

    if ((err = GetLastError ()) == ERROR_IO_PENDING) {
      success = GetOverlappedResult (device, &overlapped, bytes_returned, TRUE);
      if (!success)
        err = GetLastError ();
    }

    if (error != NULL)
      *error = err;
  }

  CloseHandle (overlapped.hEvent);

  return success ? TRUE : FALSE;
}

gboolean
ks_filter_get_pin_property (HANDLE filter_handle, gulong pin_id,
    GUID prop_set, gulong prop_id, gpointer value, gulong value_size,
    gulong * error)
{
  KSP_PIN prop;
  DWORD bytes_returned = 0;

  memset (&prop, 0, sizeof (KSP_PIN));

  prop.PinId = pin_id;
  prop.Property.Set = prop_set;
  prop.Property.Id = prop_id;
  prop.Property.Flags = KSPROPERTY_TYPE_GET;

  return ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY, &prop,
      sizeof (prop), value, value_size, &bytes_returned, error);
}

gboolean
ks_filter_get_pin_property_multi (HANDLE filter_handle, gulong pin_id,
    GUID prop_set, gulong prop_id, KSMULTIPLE_ITEM ** items, gulong * error)
{
  KSP_PIN prop;
  DWORD items_size = 0, bytes_written = 0;
  gulong err;
  gboolean ret;

  memset (&prop, 0, sizeof (KSP_PIN));
  *items = NULL;

  prop.PinId = pin_id;
  prop.Property.Set = prop_set;
  prop.Property.Id = prop_id;
  prop.Property.Flags = KSPROPERTY_TYPE_GET;

  ret = ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY,
      &prop.Property, sizeof (prop), NULL, 0, &items_size, &err);
  if (!ret && err != ERROR_INSUFFICIENT_BUFFER && err != ERROR_MORE_DATA)
    goto ioctl_failed;

  *items = g_malloc0 (items_size);

  ret = ks_sync_device_io_control (filter_handle, IOCTL_KS_PROPERTY, &prop,
      sizeof (prop), *items, items_size, &bytes_written, &err);
  if (!ret)
    goto ioctl_failed;

  return ret;

ioctl_failed:
  if (error != NULL)
    *error = err;

  g_free (*items);
  *items = NULL;

  return FALSE;
}

gboolean
ks_object_query_property (HANDLE handle, GUID prop_set, gulong prop_id,
    gulong prop_flags, gpointer * value, gulong * value_size, gulong * error)
{
  KSPROPERTY prop;
  DWORD req_value_size = 0, bytes_written = 0;
  gulong err;
  gboolean ret;

  memset (&prop, 0, sizeof (KSPROPERTY));
  *value = NULL;

  prop.Set = prop_set;
  prop.Id = prop_id;
  prop.Flags = prop_flags;

  if (value_size == NULL || *value_size == 0) {
    ret = ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY,
        &prop, sizeof (prop), NULL, 0, &req_value_size, &err);
    if (!ret && err != ERROR_INSUFFICIENT_BUFFER && err != ERROR_MORE_DATA)
      goto ioctl_failed;
  } else {
    req_value_size = *value_size;
  }

  *value = g_malloc0 (req_value_size);

  ret = ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY, &prop,
      sizeof (prop), *value, req_value_size, &bytes_written, &err);
  if (!ret)
    goto ioctl_failed;

  if (value_size != NULL)
    *value_size = bytes_written;

  return ret;

ioctl_failed:
  if (error != NULL)
    *error = err;

  g_free (*value);
  *value = NULL;

  if (value_size != NULL)
    *value_size = 0;

  return FALSE;
}

gboolean
ks_object_get_property (HANDLE handle, GUID prop_set, gulong prop_id,
    gpointer * value, gulong * value_size, gulong * error)
{
  return ks_object_query_property (handle, prop_set, prop_id,
      KSPROPERTY_TYPE_GET, value, value_size, error);
}

gboolean
ks_object_set_property (HANDLE handle, GUID prop_set, gulong prop_id,
    gpointer value, gulong value_size, gulong * error)
{
  KSPROPERTY prop;
  DWORD bytes_returned;

  memset (&prop, 0, sizeof (KSPROPERTY));
  prop.Set = prop_set;
  prop.Id = prop_id;
  prop.Flags = KSPROPERTY_TYPE_SET;

  return ks_sync_device_io_control (handle, IOCTL_KS_PROPERTY, &prop,
      sizeof (prop), value, value_size, &bytes_returned, error);
}

gboolean
ks_object_get_supported_property_sets (HANDLE handle, GUID ** propsets,
    gulong * len)
{
  gulong size = 0;
  gulong error;

  *propsets = NULL;
  *len = 0;

  if (ks_object_query_property (handle, GUID_NULL, 0,
          KSPROPERTY_TYPE_SETSUPPORT, (void *) propsets, &size, &error)) {
    if (size % sizeof (GUID) == 0) {
      *len = size / sizeof (GUID);
      return TRUE;
    }
  }

  g_free (*propsets);
  *propsets = NULL;
  *len = 0;
  return FALSE;
}

gboolean
ks_object_set_connection_state (HANDLE handle, KSSTATE state, gulong * error)
{
  return ks_object_set_property (handle, KSPROPSETID_Connection,
      KSPROPERTY_CONNECTION_STATE, &state, sizeof (state), error);
}

gchar *
ks_guid_to_string (const GUID * guid)
{
  return g_strdup_printf ("{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
      (guint) guid->Data1, (guint) guid->Data2, (guint) guid->Data3,
      (guint) guid->Data4[0], (guint) guid->Data4[1], (guint) guid->Data4[2],
      (guint) guid->Data4[3], (guint) guid->Data4[4], (guint) guid->Data4[5],
      (guint) guid->Data4[6], (guint) guid->Data4[7]);
}

const gchar *
ks_state_to_string (KSSTATE state)
{
  switch (state) {
    case KSSTATE_STOP:
      return "KSSTATE_STOP";
    case KSSTATE_ACQUIRE:
      return "KSSTATE_ACQUIRE";
    case KSSTATE_PAUSE:
      return "KSSTATE_PAUSE";
    case KSSTATE_RUN:
      return "KSSTATE_RUN";
    default:
      g_assert_not_reached ();
  }

  return "UNKNOWN";
}

#define CHECK_OPTIONS_FLAG(flag) \
  if (flags & KSSTREAM_HEADER_OPTIONSF_##flag)\
  {\
    if (str->len > 0)\
      g_string_append (str, "|");\
    g_string_append (str, G_STRINGIFY (flag));\
    flags &= ~KSSTREAM_HEADER_OPTIONSF_##flag;\
  }

gchar *
ks_options_flags_to_string (gulong flags)
{
  gchar *ret;
  GString *str;

  str = g_string_sized_new (128);

  CHECK_OPTIONS_FLAG (SPLICEPOINT);
  CHECK_OPTIONS_FLAG (PREROLL);
  CHECK_OPTIONS_FLAG (DATADISCONTINUITY);
  CHECK_OPTIONS_FLAG (TYPECHANGED);
  CHECK_OPTIONS_FLAG (TIMEVALID);
  CHECK_OPTIONS_FLAG (TIMEDISCONTINUITY);
  CHECK_OPTIONS_FLAG (FLUSHONPAUSE);
  CHECK_OPTIONS_FLAG (DURATIONVALID);
  CHECK_OPTIONS_FLAG (ENDOFSTREAM);
  CHECK_OPTIONS_FLAG (BUFFEREDTRANSFER);
  CHECK_OPTIONS_FLAG (VRAM_DATA_TRANSFER);
  CHECK_OPTIONS_FLAG (LOOPEDDATA);

  if (flags != 0)
    g_string_append_printf (str, "|0x%08x", (guint) flags);

  ret = str->str;
  g_string_free (str, FALSE);

  return ret;
}

typedef struct
{
  const GUID guid;
  const gchar *name;
} KsPropertySetMapping;

#ifndef STATIC_KSPROPSETID_GM
#define STATIC_KSPROPSETID_GM \
    0xAF627536, 0xE719, 0x11D2, { 0x8A, 0x1D, 0x00, 0x60, 0x97, 0xD2, 0xDF, 0x5D }
#endif
#ifndef STATIC_KSPROPSETID_Jack
#define STATIC_KSPROPSETID_Jack \
    0x4509F757, 0x2D46, 0x4637, { 0x8E, 0x62, 0xCE, 0x7D, 0xB9, 0x44, 0xF5, 0x7B }
#endif

#ifndef STATIC_PROPSETID_VIDCAP_SELECTOR
#define STATIC_PROPSETID_VIDCAP_SELECTOR \
    0x1ABDAECA, 0x68B6, 0x4F83, { 0x93, 0x71, 0xB4, 0x13, 0x90, 0x7C, 0x7B, 0x9F }
#endif
#ifndef STATIC_PROPSETID_EXT_DEVICE
#define STATIC_PROPSETID_EXT_DEVICE \
    0xB5730A90, 0x1A2C, 0x11cf, { 0x8c, 0x23, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14 }
#endif
#ifndef STATIC_PROPSETID_EXT_TRANSPORT
#define STATIC_PROPSETID_EXT_TRANSPORT \
    0xA03CD5F0, 0x3045, 0x11cf, { 0x8c, 0x44, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14 }
#endif
#ifndef STATIC_PROPSETID_TIMECODE_READER
#define STATIC_PROPSETID_TIMECODE_READER \
    0x9B496CE1, 0x811B, 0x11cf, { 0x8C, 0x77, 0x00, 0xAA, 0x00, 0x6B, 0x68, 0x14 }
#endif

/* GCC warns about this, but it seems to be correct and MSVC doesn't warn about
 * it. XXX: Check again after the toolchain is updated:
 * https://gitlab.freedesktop.org/gstreamer/cerbero/merge_requests/69 */
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wmissing-braces"
#endif
static const KsPropertySetMapping known_property_sets[] = {
  {{STATIC_KSPROPSETID_General}, "General"},
  {{STATIC_KSPROPSETID_MediaSeeking}, "MediaSeeking"},
  {{STATIC_KSPROPSETID_Topology}, "Topology"},
  {{STATIC_KSPROPSETID_GM}, "GM"},
  {{STATIC_KSPROPSETID_Pin}, "Pin"},
  {{STATIC_KSPROPSETID_Quality}, "Quality"},
  {{STATIC_KSPROPSETID_Connection}, "Connection"},
  {{STATIC_KSPROPSETID_MemoryTransport}, "MemoryTransport"},
  {{STATIC_KSPROPSETID_StreamAllocator}, "StreamAllocator"},
  {{STATIC_KSPROPSETID_StreamInterface}, "StreamInterface"},
  {{STATIC_KSPROPSETID_Stream}, "Stream"},
  {{STATIC_KSPROPSETID_Clock}, "Clock"},

  {{STATIC_KSPROPSETID_DirectSound3DListener}, "DirectSound3DListener"},
  {{STATIC_KSPROPSETID_DirectSound3DBuffer}, "DirectSound3DBuffer"},
  {{STATIC_KSPROPSETID_Hrtf3d}, "Hrtf3d"},
  {{STATIC_KSPROPSETID_Itd3d}, "Itd3d"},
  {{STATIC_KSPROPSETID_Bibliographic}, "Bibliographic"},
  {{STATIC_KSPROPSETID_TopologyNode}, "TopologyNode"},
  {{STATIC_KSPROPSETID_RtAudio}, "RtAudio"},
  {{STATIC_KSPROPSETID_DrmAudioStream}, "DrmAudioStream"},
  {{STATIC_KSPROPSETID_Audio}, "Audio"},
  {{STATIC_KSPROPSETID_Acoustic_Echo_Cancel}, "Acoustic_Echo_Cancel"},
  {{STATIC_KSPROPSETID_Wave_Queued}, "Wave_Queued"},
  {{STATIC_KSPROPSETID_Wave}, "Wave"},
  {{STATIC_KSPROPSETID_WaveTable}, "WaveTable"},
  {{STATIC_KSPROPSETID_Cyclic}, "Cyclic"},
  {{STATIC_KSPROPSETID_Sysaudio}, "Sysaudio"},
  {{STATIC_KSPROPSETID_Sysaudio_Pin}, "Sysaudio_Pin"},
  {{STATIC_KSPROPSETID_AudioGfx}, "AudioGfx"},
  {{STATIC_KSPROPSETID_Linear}, "Linear"},
  {{STATIC_KSPROPSETID_Mpeg2Vid}, "Mpeg2Vid"},
  {{STATIC_KSPROPSETID_AC3}, "AC3"},
  {{STATIC_KSPROPSETID_AudioDecoderOut}, "AudioDecoderOut"},
  {{STATIC_KSPROPSETID_DvdSubPic}, "DvdSubPic"},
  {{STATIC_KSPROPSETID_CopyProt}, "CopyProt"},
  {{STATIC_KSPROPSETID_VBICAP_PROPERTIES}, "VBICAP_PROPERTIES"},
  {{STATIC_KSPROPSETID_VBICodecFiltering}, "VBICodecFiltering"},
  {{STATIC_KSPROPSETID_VramCapture}, "VramCapture"},
  {{STATIC_KSPROPSETID_OverlayUpdate}, "OverlayUpdate"},
  {{STATIC_KSPROPSETID_VPConfig}, "VPConfig"},
  {{STATIC_KSPROPSETID_VPVBIConfig}, "VPVBIConfig"},
  {{STATIC_KSPROPSETID_TSRateChange}, "TSRateChange"},
  {{STATIC_KSPROPSETID_Jack}, "Jack"},

  {{STATIC_PROPSETID_ALLOCATOR_CONTROL}, "ALLOCATOR_CONTROL"},
  {{STATIC_PROPSETID_VIDCAP_VIDEOPROCAMP}, "VIDCAP_VIDEOPROCAMP"},
  {{STATIC_PROPSETID_VIDCAP_SELECTOR}, "VIDCAP_SELECTOR"},
  {{STATIC_PROPSETID_TUNER}, "TUNER"},
  {{STATIC_PROPSETID_VIDCAP_VIDEOENCODER}, "VIDCAP_VIDEOENCODER"},
  {{STATIC_PROPSETID_VIDCAP_VIDEODECODER}, "VIDCAP_VIDEODECODER"},
  {{STATIC_PROPSETID_VIDCAP_CAMERACONTROL}, "VIDCAP_CAMERACONTROL"},
  {{STATIC_PROPSETID_EXT_DEVICE}, "EXT_DEVICE"},
  {{STATIC_PROPSETID_EXT_TRANSPORT}, "EXT_TRANSPORT"},
  {{STATIC_PROPSETID_TIMECODE_READER}, "TIMECODE_READER"},
  {{STATIC_PROPSETID_VIDCAP_CROSSBAR}, "VIDCAP_CROSSBAR"},
  {{STATIC_PROPSETID_VIDCAP_TVAUDIO}, "VIDCAP_TVAUDIO"},
  {{STATIC_PROPSETID_VIDCAP_VIDEOCOMPRESSION}, "VIDCAP_VIDEOCOMPRESSION"},
  {{STATIC_PROPSETID_VIDCAP_VIDEOCONTROL}, "VIDCAP_VIDEOCONTROL"},
  {{STATIC_PROPSETID_VIDCAP_DROPPEDFRAMES}, "VIDCAP_DROPPEDFRAMES"},
};

gchar *
ks_property_set_to_string (const GUID * guid)
{
  guint i;

  for (i = 0;
      i < sizeof (known_property_sets) / sizeof (known_property_sets[0]); i++) {
    if (IsEqualGUID (guid, &known_property_sets[i].guid))
      return g_strdup_printf ("KSPROPSETID_%s", known_property_sets[i].name);
  }

  return ks_guid_to_string (guid);
}