/* WirePlumber
 *
 * Copyright © 2020 Collabora Ltd.
 *    @author Julian Bouzas <julian.bouzas@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include "../common/test-log.h"

static void
test_spa_pod_basic (void)
{
  /* None */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_none ();
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_none (pod));
    g_assert_false (wp_spa_pod_is_id (pod));
    g_assert_cmpstr ("Spa:None", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_none ();
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Boolean */
  {
    g_autoptr (WpSpaPod) copy = NULL;
    g_assert_null (copy);

    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_boolean (TRUE);
      g_assert_nonnull (pod);
      g_assert_true (wp_spa_pod_is_boolean (pod));
      gboolean value = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (pod, &value));
      g_assert_true (value);
      g_assert_cmpstr ("Spa:Bool", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
      g_assert_true (wp_spa_pod_set_boolean (pod, FALSE));
      g_assert_true (wp_spa_pod_get_boolean (pod, &value));
      g_assert_false (value);

      copy = wp_spa_pod_copy (pod);
    }

    g_assert_nonnull (copy);
    g_assert_true (wp_spa_pod_is_boolean (copy));
    gboolean value = FALSE;
    g_assert_true (wp_spa_pod_get_boolean (copy, &value));
    g_assert_false (value);
    g_assert_cmpstr ("Spa:Bool", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (copy)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_boolean (TRUE);
    g_assert_true (wp_spa_pod_set_pod (copy, other));
    g_assert_true (wp_spa_pod_get_boolean (copy, &value));
    g_assert_true (value);
    g_assert_true (wp_spa_pod_equal (copy, other));
  }

  /* Id */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_id (5);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_id (pod));
    guint32 value = 0;
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 5);
    g_assert_cmpstr ("Spa:Id", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_id (pod, 10));
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 10);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_id (20);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_id (pod, &value));
    g_assert_cmpuint (value, ==, 20);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Int */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_int (-12);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_int (pod));
    gint32 value = 0;
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpint (value, ==, -12);
    g_assert_cmpstr ("Spa:Int", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_int (pod, 9999));
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpint (value, ==, 9999);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_int (1000);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_int (pod, &value));
    g_assert_cmpuint (value, ==, 1000);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Long */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_long (LONG_MAX);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_long (pod));
    gint64 value = 0;
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpint (value, ==, LONG_MAX);
    g_assert_cmpstr ("Spa:Long", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_long (pod, LONG_MIN));
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpuint (value, ==, LONG_MIN);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_long (0);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_long (pod, &value));
    g_assert_cmpuint (value, ==, 0);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Float */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_float (3.14);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_float (pod));
    float value = 0;
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 3.14, 0.001);
    g_assert_cmpstr ("Spa:Float", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_float (pod, 1.0));
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 1.0, 0.001);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_float (-3.14);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_float (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, -3.14, 0.001);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Double */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_double (2.718281828);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_double (pod));
    double value = 0;
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 2.718281828, 0.0000000001);
    g_assert_cmpstr ("Spa:Double", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_double (pod, 2.0));
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 2.0, 0.0000000001);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_double (3.0);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_double (pod, &value));
    g_assert_cmpfloat_with_epsilon (value, 3.0, 0.0000000001);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* String */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_string ("WirePlumber");
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_string (pod));
    const char *value = NULL;
    g_assert_true (wp_spa_pod_get_string (pod, &value));
    g_assert_nonnull (value);
    g_assert_cmpstr (value, ==, "WirePlumber");
    g_assert_cmpstr ("Spa:String", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_string ("Other");
    g_assert_nonnull (other);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_string (pod, &value));
    g_assert_nonnull (value);
    g_assert_cmpstr (value, ==, "Other");
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Bytes */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_bytes ("bytes", 5);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_bytes (pod));
    gconstpointer value = NULL;
    guint32 len = 0;
    g_assert_true (wp_spa_pod_get_bytes (pod, &value, &len));
    g_assert_nonnull (value);
    g_assert_cmpmem (value, len, "bytes", 5);
    g_assert_cmpuint (len, ==, 5);
    g_assert_cmpstr ("Spa:Bytes", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_bytes ("pod", 3);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_bytes (pod, &value, &len));
    g_assert_nonnull (value);
    g_assert_cmpmem (value, len, "pod", 3);
    g_assert_cmpuint (len, ==, 3);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Pointer */
  {
    gint i = 3;
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_pointer ("Spa:Pointer:Buffer", &i);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_pointer (pod));
    gconstpointer p = NULL;
    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
    g_assert_nonnull (p);
    g_assert_true (p == &i);
    g_assert_cmpint (*(gint *)p, ==, 3);
    g_assert_cmpstr ("Spa:Pointer:Buffer", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    float f = 1.1;
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_pointer ("Spa:Pointer:Meta", &f);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_pointer (pod, &p));
    g_assert_nonnull (p);
    g_assert_true (p == &f);
    g_assert_cmpfloat_with_epsilon (*(float *)p, 1.1, 0.01);
    g_assert_cmpstr ("Spa:Pointer:Meta", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Fd */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_fd (4);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_fd (pod));
    gint64 value = 0;
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpint (value, ==, 4);
    g_assert_cmpstr ("Spa:Fd", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_fd (pod, 1));
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpuint (value, ==, 1);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_fd (10);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_fd (pod, &value));
    g_assert_cmpuint (value, ==, 10);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Rectangle */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_rectangle (1920, 1080);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_rectangle (pod));
    guint32 width = 0;
    guint32 height = 0;
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 1920);
    g_assert_cmpint (height, ==, 1080);
    g_assert_cmpstr ("Spa:Rectangle", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_rectangle (pod, 640, 480));
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 640);
    g_assert_cmpint (height, ==, 480);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_rectangle (200, 100);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_rectangle (pod, &width, &height));
    g_assert_cmpint (width, ==, 200);
    g_assert_cmpint (height, ==, 100);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }

  /* Fraction */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_fraction (16, 9);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_fraction (pod));
    guint32 num = 0;
    guint32 denom = 0;
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 16);
    g_assert_cmpint (denom, ==, 9);
    g_assert_cmpstr ("Spa:Fraction", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_true (wp_spa_pod_set_fraction (pod, 4, 3));
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 4);
    g_assert_cmpint (denom, ==, 3);
    g_autoptr (WpSpaPod) other = wp_spa_pod_new_fraction (2, 1);
    g_assert_true (wp_spa_pod_set_pod (pod, other));
    g_assert_true (wp_spa_pod_get_fraction (pod, &num, &denom));
    g_assert_cmpint (num, ==, 2);
    g_assert_cmpint (denom, ==, 1);
    g_assert_true (wp_spa_pod_equal (pod, other));
  }
}

static void
test_spa_pod_choice (void)
{
  /* Static Enum */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_choice (
        "Enum", "i", 0, "i", 1, "i", 2, NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    g_assert_cmpstr ("Enum", ==,
        wp_spa_id_value_short_name (wp_spa_pod_get_choice_type (pod)));

    g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
    g_assert_nonnull (child);
    g_assert_cmpstr ("Spa:Int", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
    gint32 value = 1;
    g_assert_true (wp_spa_pod_get_int (child, &value));
    g_assert_cmpint (value, ==, 0);
    g_assert_true (wp_spa_pod_set_int (child, 3));
    g_assert_true (wp_spa_pod_get_int (child, &value));
    g_assert_cmpint (value, ==, 3);
  }

  /* Static None */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_choice ("None", "s",
        "default value", NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    {
      g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
      g_assert_nonnull (child);
      g_assert_cmpstr ("Spa:String", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
      const char *value = NULL;
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_nonnull (value);
      g_assert_cmpstr ("default value", ==, value);
      g_autoptr (WpSpaPod) str_pod = wp_spa_pod_new_string ("new value");
      g_assert_true (wp_spa_pod_set_pod (child, str_pod));
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_cmpstr ("new value", ==, value);
    }

    {
      g_autoptr (WpSpaPod) child = wp_spa_pod_get_choice_child (pod);
      g_assert_nonnull (child);
      g_assert_cmpstr ("Spa:String", ==,
          wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
      const char *value = NULL;
      g_assert_true (wp_spa_pod_get_string (child, &value));
      g_assert_nonnull (value);
      g_assert_cmpstr ("new value", ==, value);
    }
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice ("Enum");
    wp_spa_pod_builder_add (b, "i", 0, NULL);
    wp_spa_pod_builder_add (b, "i", 1, NULL);
    wp_spa_pod_builder_add (b, "i", 2, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_choice (pod));
    g_assert_cmpstr ("Spa:Pod:Choice", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* It is not possible to use the parser to get the contents of a choice, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_choice` API in the SPA library */
}

static void
test_spa_pod_array (void)
{
  /* Dynamic */
  {
    WpSpaPodBuilder *b = wp_spa_pod_builder_new_array ();
    wp_spa_pod_builder_add (b, "b", FALSE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    wp_spa_pod_builder_add (b, "b", FALSE, NULL);
    wp_spa_pod_builder_add (b, "b", TRUE, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_array (pod));
    g_assert_cmpstr ("Spa:Array", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
    wp_spa_pod_builder_unref (b);
    g_assert_true (wp_spa_pod_is_array (pod));

    g_autoptr (WpSpaPod) child = wp_spa_pod_get_array_child (pod);
    g_assert_nonnull (child);
    g_assert_cmpstr ("Spa:Bool", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (child)));
    gboolean value = TRUE;
    g_assert_true (wp_spa_pod_get_boolean (child, &value));
    g_assert_false (value);
  }

  /* It is not possible to use the parser to get the contents of an array, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_array` API in the SPA library. */
}

static void
test_spa_pod_object (void)
{
  /* Static */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Props", "Props",
        "mute", "b", FALSE,
        "volume", "f", 0.5,
        "frequency", "i", 440,
        "device", "s", "device-name",
        "deviceFd", "h", 5,
        "id-01000000", "b", TRUE,
        NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_object (pod));
    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    const char *id_name;
    gboolean mute = TRUE;
    float vol = 0.0;
    gint32 frequency;
    const char *device;
    gint64 device_fd;
    gboolean custom = FALSE;
    g_assert_true (wp_spa_pod_get_object (pod,
        &id_name,
        "mute", "b", &mute,
        "volume", "f", &vol,
        "frequency", "i", &frequency,
        "device", "s", &device,
        "deviceFd", "h", &device_fd,
        "id-01000000", "b", &custom,
        NULL));
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
    g_assert_cmpfloat_with_epsilon (vol, 0.5, 0.01);
    g_assert_cmpint (frequency, ==, 440);
    g_assert_cmpstr (device, ==, "device-name");
    g_assert_cmpint (device_fd, ==, 5);
    g_assert_true (custom);
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
        "Spa:Pod:Object:Param:Props", "Props");
    wp_spa_pod_builder_add_property (b, "mute");
    wp_spa_pod_builder_add_boolean (b, FALSE);
    wp_spa_pod_builder_add_property (b, "volume");
    wp_spa_pod_builder_add_float (b, 0.5);
    wp_spa_pod_builder_add_property (b, "frequency");
    wp_spa_pod_builder_add_int (b, 440);
    wp_spa_pod_builder_add_property (b, "device");
    wp_spa_pod_builder_add_string (b, "device-name");
    wp_spa_pod_builder_add_property (b, "deviceFd");
    wp_spa_pod_builder_add_fd (b, 5);
    wp_spa_pod_builder_add_property (b, "id-01000000");
    wp_spa_pod_builder_add_boolean (b, TRUE);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_object (pod));
    g_assert_cmpstr ("Spa:Pod:Object:Param:Props", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    const char *id_name;
    gboolean mute = TRUE;
    float vol = 0.0;
    gint32 frequency;
    const char *device;
    gint64 device_fd;
    gboolean custom = FALSE;
    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_object (pod, &id_name);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_parser_get (p, "mute", "b", &mute, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "volume", "f", &vol, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "frequency", "i", &frequency, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "device", "s", &device, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "deviceFd", "h", &device_fd, NULL));
    g_assert_true (wp_spa_pod_parser_get (p, "id-01000000", "b", &custom, NULL));
    wp_spa_pod_parser_end (p);
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
    g_assert_cmpfloat_with_epsilon (vol, 0.5, 0.01);
    g_assert_cmpint (frequency, ==, 440);
    g_assert_cmpstr (device, ==, "device-name");
    g_assert_cmpint (device_fd, ==, 5);
    g_assert_true (custom);
  }
}

static void
test_spa_pod_struct (void)
{
  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_struct ();
    wp_spa_pod_builder_add_boolean (b, TRUE);
    wp_spa_pod_builder_add_id (b, 2);
    wp_spa_pod_builder_add_int (b, 8);
    wp_spa_pod_builder_add_long (b, 64);
    wp_spa_pod_builder_add_float (b, 3.14);
    wp_spa_pod_builder_add_double (b, 2.718281828);
    wp_spa_pod_builder_add_string (b, "WirePlumber");
    wp_spa_pod_builder_add_bytes (b, "bytes", 5);
    wp_spa_pod_builder_add_pointer (b, "Spa:Pointer:Buffer", b);
    wp_spa_pod_builder_add_fd (b, 4);
    wp_spa_pod_builder_add_rectangle (b, 1920, 1080);
    wp_spa_pod_builder_add_fraction (b, 16, 9);
    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_int (35254);
      wp_spa_pod_builder_add_pod (b, pod);
    }
    {
      g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:Props", "Props",
        "mute", "b", FALSE,
        NULL);
      wp_spa_pod_builder_add (b, "P", pod, NULL);
    }
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_struct (pod));
    g_assert_cmpstr ("Spa:Pod:Struct", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));

    g_autoptr (WpSpaPodParser) p = wp_spa_pod_parser_new_struct (pod);
    g_assert_nonnull (pod);

    gboolean value_boolean;
    g_assert_true (wp_spa_pod_parser_get_boolean (p, &value_boolean));
    g_assert_true (value_boolean);

    guint32 value_id;
    g_assert_true (wp_spa_pod_parser_get_id (p, &value_id));
    g_assert_cmpuint (value_id, ==, 2);

    gint32 value_int;
    g_assert_true (wp_spa_pod_parser_get_int (p, &value_int));
    g_assert_cmpint (value_int, ==, 8);

    gint64 value_long;
    g_assert_true (wp_spa_pod_parser_get_long (p, &value_long));
    g_assert_cmpint (value_long, ==, 64);

    float value_float;
    g_assert_true (wp_spa_pod_parser_get_float (p, &value_float));
    g_assert_cmpfloat_with_epsilon (value_float, 3.14, 0.001);

    double value_double;
    g_assert_true (wp_spa_pod_parser_get_double (p, &value_double));
    g_assert_cmpfloat_with_epsilon (value_double, 2.718281828, 0.0000000001);

    const char *value_string;
    g_assert_true (wp_spa_pod_parser_get_string (p, &value_string));
    g_assert_cmpstr (value_string, ==, "WirePlumber");

    gconstpointer value_bytes;
    guint32 len_bytes;
    g_assert_true (wp_spa_pod_parser_get_bytes (p, &value_bytes, &len_bytes));
    g_assert_cmpmem (value_bytes, len_bytes, "bytes", 5);
    g_assert_cmpuint (len_bytes, ==, 5);

    gconstpointer value_pointer;
    g_assert_true (wp_spa_pod_parser_get_pointer (p, &value_pointer));
    g_assert_nonnull (value_pointer);
    g_assert_true (value_pointer == b);

    gint64 value_fd;
    g_assert_true (wp_spa_pod_parser_get_fd (p, &value_fd));
    g_assert_cmpint (value_fd, ==, 4);

    guint32 value_width;
    guint32 value_height;
    g_assert_true (wp_spa_pod_parser_get_rectangle (p, &value_width, &value_height));
    g_assert_cmpuint (value_width, ==, 1920);
    g_assert_cmpuint (value_height, ==, 1080);

    guint32 value_num;
    guint32 value_denom;
    g_assert_true (wp_spa_pod_parser_get_fraction (p, &value_num, &value_denom));
    g_assert_cmpuint (value_num, ==, 16);
    g_assert_cmpuint (value_denom, ==, 9);

    g_autoptr (WpSpaPod) value_pod = wp_spa_pod_parser_get_pod (p);
    g_assert_nonnull (value_pod);
    gint value_pod_int;
    g_assert_true (wp_spa_pod_get_int (value_pod, &value_pod_int));
    g_assert_cmpint (value_pod_int, ==, 35254);

    g_autoptr (WpSpaPod) value_object = NULL;
    g_assert_true (wp_spa_pod_parser_get (p, "P", &value_object, NULL));
    g_assert_nonnull (value_object);
    const char *id_name;
    gboolean mute = TRUE;

    g_assert_true (wp_spa_pod_get_object (value_object,
        &id_name,
        "mute", "b", &mute,
        NULL));
    g_assert_cmpstr (id_name, ==, "Props");
    g_assert_false (mute);
  }
}

static void
test_spa_pod_sequence (void)
{
  /* Static */
  {
    g_autoptr (WpSpaPod) pod = wp_spa_pod_new_sequence (0,
        10, "Properties", "l", G_GINT64_CONSTANT (9999), NULL);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_sequence (pod));
    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* Dynamic */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_sequence (0);
    wp_spa_pod_builder_add_control (b, 10, "Properties");
    wp_spa_pod_builder_add_long (b, G_GINT64_CONSTANT (9999));
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);
    g_assert_true (wp_spa_pod_is_sequence (pod));
    g_assert_cmpstr ("Spa:Pod:Sequence", ==,
        wp_spa_type_name (wp_spa_pod_get_spa_type (pod)));
  }

  /* It is not possible to use the parser to get the contents of a sequence, you
   * need to use the iterator API to achieve that. This is because there is no
   * `spa_pod_parser_get_sequence` API in the SPA library. */
}

static void
choice_foreach (const GValue *item, gpointer data)
{
  gint32 *total = data;
  const gint32 *value = g_value_get_pointer (item);
  *total += *value;
}

static void
array_foreach (const GValue *item, gpointer data)
{
  gint32 *total = data;
  const gint32 *value = g_value_get_pointer (item);
  *total += *value;
}

static void
object_foreach (const GValue *item, gpointer data)
{
  guint32 *total_props = data;
  WpSpaPod *prop = g_value_get_boxed (item);
  g_assert_true (wp_spa_pod_is_property (prop));
  *total_props += 1;
}

static void
struct_foreach (const GValue *item, gpointer data)
{
  guint32 *total_fields = data;
  *total_fields += 1;
}

static void
sequence_foreach (const GValue *item, gpointer data)
{
  guint32 *offset_total = data;
  WpSpaPod *control = g_value_get_boxed (item);
  g_assert_true (wp_spa_pod_is_control (control));
  guint32 offset = 0;
  g_assert_true (wp_spa_pod_get_control (control, &offset, NULL, NULL));
  *offset_total += offset;
}

static void
test_spa_pod_iterator (void)
{
  /* Choice */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_choice ("Enum");
    wp_spa_pod_builder_add (b, "i", 0, NULL);
    wp_spa_pod_builder_add (b, "i", 1, NULL);
    wp_spa_pod_builder_add (b, "i", 2, NULL);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 0);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 1);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 2);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    gint32 total = 0;
    g_assert_true (wp_iterator_foreach (it, choice_foreach, &total));
    g_assert_cmpint (total, ==, 3);

  }

  /* Array */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_array ();
    wp_spa_pod_builder_add_int (b, 1);
    wp_spa_pod_builder_add_int (b, 2);
    wp_spa_pod_builder_add_int (b, 3);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 1);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 2);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      gpointer p = g_value_get_pointer (&next);
      g_assert_nonnull (p);
      g_assert_cmpint (*(gint *)p, ==, 3);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    gint total = 0;
    g_assert_true (wp_iterator_foreach (it, array_foreach, &total));
    g_assert_cmpint (total, ==, 6);
  }

  /* Object */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_object (
        "Spa:Pod:Object:Param:Props", "Props");
    wp_spa_pod_builder_add_property (b, "mute");
    wp_spa_pod_builder_add_boolean (b, FALSE);
    wp_spa_pod_builder_add_property (b, "device");
    wp_spa_pod_builder_add_string (b, "device-name");
    wp_spa_pod_builder_add_property (b, "id-01000000");
    wp_spa_pod_builder_add_boolean (b, TRUE);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "mute");
      gboolean b = TRUE;
      g_assert_true (wp_spa_pod_get_boolean (value, &b));
      g_assert_false (b);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "device");
      const char *s = NULL;
      g_assert_true (wp_spa_pod_get_string (value, &s));
      g_assert_cmpstr (s, ==, "device-name");
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_property (p));
      const char *key = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_property (p, &key, &value));
      g_assert_cmpstr (key, ==, "id-01000000");
      gboolean b = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (value, &b));
      g_assert_true (b);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 total_props = 0;
    g_assert_true (wp_iterator_foreach (it, object_foreach, &total_props));
    g_assert_cmpuint (total_props, ==, 3);
  }

  /* Struct */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_struct ();
    wp_spa_pod_builder_add_boolean (b, TRUE);
    wp_spa_pod_builder_add_id (b, 2);
    wp_spa_pod_builder_add_int (b, 8);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      gboolean v = FALSE;
      g_assert_true (wp_spa_pod_get_boolean (p, &v));
      g_assert_true (v);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      guint32 v = 0;
      g_assert_true (wp_spa_pod_get_id (p, &v));
      g_assert_cmpuint (v, ==, 2);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      gint v = 0;
      g_assert_true (wp_spa_pod_get_int (p, &v));
      g_assert_cmpint (v, ==, 8);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 total_fields = 0;
    g_assert_true (wp_iterator_foreach (it, struct_foreach, &total_fields));
    g_assert_cmpuint (total_fields, ==, 3);
  }

  /* Sequence */
  {
    g_autoptr (WpSpaPodBuilder) b = wp_spa_pod_builder_new_sequence (0);
    wp_spa_pod_builder_add_control (b, 10, "Properties");
    wp_spa_pod_builder_add_float (b, 0.33);
    wp_spa_pod_builder_add_control (b, 40, "Properties");
    wp_spa_pod_builder_add_float (b, 0.66);
    g_autoptr (WpSpaPod) pod = wp_spa_pod_builder_end (b);
    g_assert_nonnull (pod);

    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_control (p));
      guint32 offset = 0;
      const char *type_name = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_control (p, &offset, &type_name, &value));
      g_assert_cmpuint (offset, ==, 10);
      g_assert_cmpstr (type_name, ==, "Properties");
      float f = 0;
      g_assert_true (wp_spa_pod_get_float (value, &f));
      g_assert_cmpfloat_with_epsilon (f, 0.33, 0.001);
      g_value_unset (&next);
    }

    {
      GValue next = G_VALUE_INIT;
      g_assert_true (wp_iterator_next (it, &next));
      WpSpaPod *p = g_value_get_boxed (&next);
      g_assert_nonnull (p);
      g_assert_true (wp_spa_pod_is_control (p));
      guint32 offset = 0;
      const char *type_name = NULL;
      g_autoptr (WpSpaPod) value = NULL;
      g_assert_true (wp_spa_pod_get_control (p, &offset, &type_name, &value));
      g_assert_cmpuint (offset, ==, 40);
      g_assert_cmpstr (type_name, ==, "Properties");
      float f = 0;
      g_assert_true (wp_spa_pod_get_float (value, &f));
      g_assert_cmpfloat_with_epsilon (f, 0.66, 0.001);
      g_value_unset (&next);
    }

    {
      g_assert_false (wp_iterator_next (it, NULL));
    }

    guint32 offset_total = 0;
    g_assert_true (wp_iterator_foreach (it, sequence_foreach, &offset_total));
    g_assert_cmpuint (offset_total, ==, 50);
  }
}

static void
test_spa_pod_unique_owner (void)
{
  /* Create an object */
  WpSpaPod *pod = wp_spa_pod_new_object (
        "Spa:Pod:Object:Param:PropInfo", "PropInfo",
        "id", "K", "unknown",
        "name", "s", "prop-info-name",
        NULL);
  g_assert_nonnull (pod);
  g_assert_true (wp_spa_pod_is_unique_owner (pod));

  /* Get the first property using an iterator */
  GValue next = G_VALUE_INIT;
  g_autoptr (WpSpaPod) property = NULL;
  {
    g_autoptr (WpIterator) it = wp_spa_pod_new_iterator (pod);
    g_assert_nonnull (it);
    g_assert_true (wp_iterator_next (it, &next));
    property = g_value_dup_boxed (&next);
  }
  g_assert_nonnull (property);
  g_assert_true (wp_spa_pod_is_property (property));
  {
    g_autoptr (WpSpaPod) value = NULL;
    const char *key = NULL;
    g_assert_true (wp_spa_pod_get_property (property, &key, &value));
    g_assert_nonnull (key);
    g_assert_cmpstr (key, ==, "id");
    g_assert_nonnull (value);
    guint32 id = 0;
    g_assert_true (wp_spa_pod_get_id (value, &id));
    g_assert_cmpuint (id, ==, 1);
  }

  /* Own the data */
  g_assert_true (wp_spa_pod_is_unique_owner (pod));
  g_assert_false (wp_spa_pod_is_unique_owner (property));
  property = wp_spa_pod_ensure_unique_owner (property);
  g_assert_true (wp_spa_pod_is_unique_owner (pod));
  g_assert_true (wp_spa_pod_is_unique_owner (property));

  /* Destroy the object */
  wp_spa_pod_unref (pod);
  g_assert_true (wp_spa_pod_is_unique_owner (property));

  /* Make sure the property data is still valid */
  {
    g_autoptr (WpSpaPod) value = NULL;
    const char *key = NULL;
    g_assert_true (wp_spa_pod_get_property (property, &key, &value));
    g_assert_nonnull (key);
    g_assert_cmpstr (key, ==, "id");
    g_assert_nonnull (value);
    guint32 id = 0;
    g_assert_true (wp_spa_pod_get_id (value, &id));
    g_assert_cmpuint (id, ==, 1);
  }

  /* Destroy the property */
  g_value_unset (&next);
}

static void
test_spa_pod_port_config (void)
{
  const gint32 rate = 48000;
  const gint32 channels = 2;

  /* Build the format to make sure the types exist */
  g_autoptr (WpSpaPodBuilder) builder = wp_spa_pod_builder_new_object (
     "Spa:Pod:Object:Param:Format", "Format");
  wp_spa_pod_builder_add (builder,
     "mediaType",    "K", "audio",
     "mediaSubtype", "K", "raw",
     "format",       "K", "S16LE",
     "rate",         "i", rate,
     "channels",     "i", channels,
     NULL);
  g_autoptr (WpSpaPodBuilder) position_builder = wp_spa_pod_builder_new_array ();
  for (gint i = 0; i < channels; i++)
    wp_spa_pod_builder_add_id (position_builder, 0);
  wp_spa_pod_builder_add_property (builder, "position");
  g_autoptr (WpSpaPod) position = wp_spa_pod_builder_end (position_builder);
  wp_spa_pod_builder_add_pod (builder, position);
  g_autoptr (WpSpaPod) format = wp_spa_pod_builder_end (builder);
  g_assert_nonnull (format);

  /* Build the port config to make sure the types exist */
  g_autoptr (WpSpaPod) pod = wp_spa_pod_new_object (
      "Spa:Pod:Object:Param:PortConfig", "PortConfig",
      "direction",  "K", "Input",
      "mode",       "K", "dsp",
      "monitor",    "b", FALSE,
      "control",    "b", FALSE,
      "format",     "P", format,
      NULL);
  g_assert_nonnull (pod);
}

int
main (int argc, char *argv[])
{
  g_test_init (&argc, &argv, NULL);
  g_log_set_writer_func (wp_log_writer_default, NULL, NULL);

  g_test_add_func ("/wp/spa-pod/basic", test_spa_pod_basic);
  g_test_add_func ("/wp/spa-pod/choice", test_spa_pod_choice);
  g_test_add_func ("/wp/spa-pod/array", test_spa_pod_array);
  g_test_add_func ("/wp/spa-pod/object", test_spa_pod_object);
  g_test_add_func ("/wp/spa-pod/struct", test_spa_pod_struct);
  g_test_add_func ("/wp/spa-pod/sequence", test_spa_pod_sequence);
  g_test_add_func ("/wp/spa-pod/iterator", test_spa_pod_iterator);
  g_test_add_func ("/wp/spa-pod/unique-owner", test_spa_pod_unique_owner);
  g_test_add_func ("/wp/spa-pod/port-config", test_spa_pod_port_config);

  return g_test_run ();
}
