#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <glib-object.h>

#include <json-glib/json-glib.h>
#include <json-glib/json-gobject.h>

#define TEST_TYPE_OBJECT                (test_object_get_type ())
#define TEST_OBJECT(obj)                (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_OBJECT, TestObject))
#define TEST_IS_OBJECT(obj)             (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_OBJECT))
#define TEST_OBJECT_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_OBJECT, TestObjectClass))
#define TEST_IS_OBJECT_CLASS(klass)     (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_OBJECT))
#define TEST_OBJECT_GET_CLASS(obj)      (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_OBJECT, TestObjectClass))

typedef struct _TestObject              TestObject;
typedef struct _TestObjectClass         TestObjectClass;

struct _TestObject
{
  GObject parent_instance;

  gint m_int;
  guint m_uint;
  gint8 m_schar;
  guchar m_uchar;
  glong m_long;
  gulong m_ulong;
  gint64 m_int64;
  guint64 m_uint64;
  gfloat m_float;
  gdouble m_double;
};

struct _TestObjectClass
{
  GObjectClass parent_class;
};

GType test_object_get_type (void);

/*** implementation ***/

enum
{
  PROP_0,

  PROP_MYINT,
  PROP_MYUINT,
  PROP_MYSCHAR,
  PROP_MYUCHAR,
  PROP_MYLONG,
  PROP_MYULONG,
  PROP_MYINT64,
  PROP_MYUINT64,
  PROP_MYFLOAT,
  PROP_MYDOUBLE,
};

G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT);

static void
test_object_set_property (GObject      *gobject,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  switch (prop_id)
    {
    case PROP_MYINT:
      TEST_OBJECT (gobject)->m_int = g_value_get_int (value);
      break;

    case PROP_MYUINT:
      TEST_OBJECT (gobject)->m_uint = g_value_get_uint (value);
      break;

    case PROP_MYSCHAR:
      TEST_OBJECT (gobject)->m_schar = g_value_get_schar (value);
      break;

    case PROP_MYUCHAR:
      TEST_OBJECT (gobject)->m_uchar = g_value_get_uchar (value);
      break;

    case PROP_MYLONG:
      TEST_OBJECT (gobject)->m_long = g_value_get_long (value);
      break;

    case PROP_MYULONG:
      TEST_OBJECT (gobject)->m_ulong = g_value_get_ulong (value);
      break;

    case PROP_MYINT64:
      TEST_OBJECT (gobject)->m_int64 = g_value_get_int64 (value);
      break;

    case PROP_MYUINT64:
      TEST_OBJECT (gobject)->m_uint64 = g_value_get_uint64 (value);
      break;

    case PROP_MYFLOAT:
      TEST_OBJECT (gobject)->m_float = g_value_get_float (value);
      break;

    case PROP_MYDOUBLE:
      TEST_OBJECT (gobject)->m_double = g_value_get_double (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
test_object_get_property (GObject    *gobject,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  switch (prop_id)
    {
    case PROP_MYINT:
      g_value_set_int (value, TEST_OBJECT (gobject)->m_int);
      break;

    case PROP_MYUINT:
      g_value_set_uint (value, TEST_OBJECT (gobject)->m_uint);
      break;

    case PROP_MYSCHAR:
      g_value_set_schar (value, TEST_OBJECT (gobject)->m_schar);
      break;

    case PROP_MYUCHAR:
      g_value_set_uchar (value, TEST_OBJECT (gobject)->m_uchar);
      break;

    case PROP_MYLONG:
      g_value_set_long (value, TEST_OBJECT (gobject)->m_long);
      break;

    case PROP_MYULONG:
      g_value_set_ulong (value, TEST_OBJECT (gobject)->m_ulong);
      break;

    case PROP_MYINT64:
      g_value_set_int64 (value, TEST_OBJECT (gobject)->m_int64);
      break;

    case PROP_MYUINT64:
      g_value_set_uint64 (value, TEST_OBJECT (gobject)->m_uint64);
      break;

    case PROP_MYFLOAT:
      g_value_set_float (value, TEST_OBJECT (gobject)->m_float);
      break;

    case PROP_MYDOUBLE:
      g_value_set_double (value, TEST_OBJECT (gobject)->m_double);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
test_object_class_init (TestObjectClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->set_property = test_object_set_property;
  gobject_class->get_property = test_object_get_property;

  g_object_class_install_property (gobject_class,
                                   PROP_MYINT,
                                   g_param_spec_int ("my-int", "Foo", "Foo",
                                                     -G_MAXINT, G_MAXINT, 1,
                                                     G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYUINT,
                                   g_param_spec_uint ("my-uint", "Fuu", "Fuu",
                                                      0, G_MAXUINT, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYSCHAR,
                                   g_param_spec_char ("my-schar", "schar", "schar",
                                                       0, G_MAXINT8, 1,
                                                       G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYUCHAR,
                                   g_param_spec_uchar ("my-uchar", "uchar", "uchar",
                                                      0, G_MAXUINT8, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYLONG,
                                   g_param_spec_long ("my-long", "long", "long",
                                                      0, G_MAXLONG, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYULONG,
                                   g_param_spec_ulong ("my-ulong", "ulong", "ulong",
                                                      0, G_MAXULONG, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYINT64,
                                   g_param_spec_int64 ("my-int64", "int64", "int64",
                                                      0, G_MAXINT64, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYUINT64,
                                   g_param_spec_uint64 ("my-uint64", "uint64", "uint64",
                                                      0, G_MAXUINT64, 1,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYFLOAT,
                                   g_param_spec_float ("my-float", "float", "float",
                                                      -G_MAXFLOAT, G_MAXFLOAT, 0.0f,
                                                      G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
                                   PROP_MYDOUBLE,
                                   g_param_spec_double ("my-double", "double", "double",
                                                      -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                      G_PARAM_READWRITE));
}

static void
test_object_init (TestObject *object)
{
  object->m_int = 0;
  object->m_uint = 0;
  object->m_schar = 0;
  object->m_uchar = 0;
  object->m_long = 0;
  object->m_ulong = 0;
  object->m_int64 = G_GINT64_CONSTANT(0);
  object->m_uint64 = G_GUINT64_CONSTANT(0);
  object->m_float = 0.0f;
  object->m_double = 0.0;
}

static const gchar *var_test =
"{\n"
"  \"my-int\" : 42,\n"
"  \"my-uint\" : 43,\n"
"  \"my-schar\" : 44,\n"
"  \"my-uchar\" : 45,\n"
"  \"my-long\" : 46,\n"
"  \"my-ulong\" : 47,\n"
"  \"my-int64\" : 48,\n"
"  \"my-uint64\" : 49,\n"
"  \"my-float\" : 5,\n"
"  \"my-double\" : 6\n"
"}";

static void
test_deserialize (void)
{
  TestObject *test;
  GObject *object;
  GError *error;
  gchar *str;

  error = NULL;
  object = json_gobject_from_data (TEST_TYPE_OBJECT, var_test, -1, &error);
  if (error)
    g_error ("*** Unable to parse buffer: %s\n", error->message);

  if (g_test_verbose ())
    g_print ("*** TestObject ***\n"
             " my-int: %s\n"
             " my-uint: %s\n"
             " my-schar: %s\n"
             " my-uchar: %s\n"
             " my-long: %s\n"
             " my-ulong: %s\n"
             " my-int64: %s\n"
             " my-uint64: %s\n"
             " my-float: %s\n"
             " my-double: %s\n",
             TEST_OBJECT (object)->m_int == 42     ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_uint == 42    ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_schar == 42   ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_uchar == 42   ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_long == 42    ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_ulong == 42   ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_int64 == 42   ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_uint64 == 42  ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_float == 5.0f ? "<true>" : "<false>",
             TEST_OBJECT (object)->m_double == 6.0 ? "<true>" : "<false>");

  g_assert_cmpint (TEST_OBJECT (object)->m_int, ==, 42);
  g_assert_cmpint (TEST_OBJECT (object)->m_uint, ==, 43);
  g_assert_cmpint (TEST_OBJECT (object)->m_schar, ==, 44);
  g_assert_cmpint (TEST_OBJECT (object)->m_uchar, ==, 45);
  g_assert_cmpint (TEST_OBJECT (object)->m_long, ==, 46);
  g_assert_cmpint (TEST_OBJECT (object)->m_ulong, ==, 47);
  g_assert_cmpint (TEST_OBJECT (object)->m_int64, ==, 48);
  g_assert_cmpint (TEST_OBJECT (object)->m_uint64, ==, 49);
  g_assert_cmpfloat (TEST_OBJECT (object)->m_float, ==, 5.0f);
  g_assert_cmpfloat (TEST_OBJECT (object)->m_double, ==, 6.0);

  g_object_unref (object);
}

static void
test_serialize (void)
{
  GObject *object;
  GError *error;
  gchar *data;
  gsize length;
  JsonGenerator *gen;
  JsonNode *root;

  error = NULL;
  object = json_gobject_from_data (TEST_TYPE_OBJECT, var_test, -1, &error);
  if (error)
    g_error ("*** Unable to parse buffer: %s\n", error->message);

  root = json_gobject_serialize (object);
  gen = g_object_new (JSON_TYPE_GENERATOR,
                      "root", root,
                      "pretty", TRUE,
                      "indent", 2,
                      NULL);
  data = json_generator_to_data (gen, &length);
  if (!data)
    g_error ("*** Unable to generate json\n");
  
  g_assert_cmpstr (data, ==, var_test);
  
  g_object_unref (gen);
  json_node_free (root);
  g_object_unref (object);
}

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

  g_test_add_func ("/deserialize/json-to-gobject", test_deserialize);
  g_test_add_func ("/deserialize/gobject-to-json", test_serialize);

  return g_test_run ();
}
