#include <gio/gio.h>

static gchar *opt_name         = NULL;
static gchar *opt_object_path  = NULL;
static gchar *opt_interface    = NULL;
static gboolean opt_system_bus = FALSE;
static gboolean opt_no_auto_start = FALSE;
static gboolean opt_no_properties = FALSE;

static GOptionEntry opt_entries[] =
{
  { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name of the remote object to watch", NULL },
  { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "Object path of the remote object", NULL },
  { "interface", 'i', 0, G_OPTION_ARG_STRING, &opt_interface, "D-Bus interface of remote object", NULL },
  { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
  { "no-auto-start", 'a', 0, G_OPTION_ARG_NONE, &opt_no_auto_start, "Don't instruct the bus to launch an owner for the name", NULL},
  { "no-properties", 'p', 0, G_OPTION_ARG_NONE, &opt_no_properties, "Do not load properties", NULL},
  G_OPTION_ENTRY_NULL
};

static GMainLoop *loop = NULL;

static void
print_properties (GDBusProxy *proxy)
{
  gchar **property_names;
  guint n;

  g_print ("    properties:\n");

  property_names = g_dbus_proxy_get_cached_property_names (proxy);
  for (n = 0; property_names != NULL && property_names[n] != NULL; n++)
    {
      const gchar *key = property_names[n];
      GVariant *value;
      gchar *value_str;
      value = g_dbus_proxy_get_cached_property (proxy, key);
      value_str = g_variant_print (value, TRUE);
      g_print ("      %s -> %s\n", key, value_str);
      g_variant_unref (value);
      g_free (value_str);
    }
  g_strfreev (property_names);
}

static void
on_properties_changed (GDBusProxy          *proxy,
                       GVariant            *changed_properties,
                       const gchar* const  *invalidated_properties,
                       gpointer             user_data)
{
  /* Note that we are guaranteed that changed_properties and
   * invalidated_properties are never NULL
   */

  if (g_variant_n_children (changed_properties) > 0)
    {
      GVariantIter *iter;
      const gchar *key;
      GVariant *value;

      g_print (" *** Properties Changed:\n");
      g_variant_get (changed_properties,
                     "a{sv}",
                     &iter);
      while (g_variant_iter_loop (iter, "{&sv}", &key, &value))
        {
          gchar *value_str;
          value_str = g_variant_print (value, TRUE);
          g_print ("      %s -> %s\n", key, value_str);
          g_free (value_str);
        }
      g_variant_iter_free (iter);
    }

  if (g_strv_length ((GStrv) invalidated_properties) > 0)
    {
      guint n;
      g_print (" *** Properties Invalidated:\n");
      for (n = 0; invalidated_properties[n] != NULL; n++)
        {
          const gchar *key = invalidated_properties[n];
          g_print ("      %s\n", key);
        }
    }
}

static void
on_signal (GDBusProxy *proxy,
           gchar      *sender_name,
           gchar      *signal_name,
           GVariant   *parameters,
           gpointer    user_data)
{
  gchar *parameters_str;

  parameters_str = g_variant_print (parameters, TRUE);
  g_print (" *** Received Signal: %s: %s\n",
           signal_name,
           parameters_str);
  g_free (parameters_str);
}

static void
print_proxy (GDBusProxy *proxy)
{
  gchar *name_owner;

  name_owner = g_dbus_proxy_get_name_owner (proxy);
  if (name_owner != NULL)
    {
      g_print ("+++ Proxy object points to remote object owned by %s\n"
               "    bus:          %s\n"
               "    name:         %s\n"
               "    object path:  %s\n"
               "    interface:    %s\n",
               name_owner,
               opt_system_bus ? "System Bus" : "Session Bus",
               opt_name,
               opt_object_path,
               opt_interface);
      print_properties (proxy);
    }
  else
    {
      g_print ("--- Proxy object is inert - there is no name owner for the name\n"
               "    bus:          %s\n"
               "    name:         %s\n"
               "    object path:  %s\n"
               "    interface:    %s\n",
               opt_system_bus ? "System Bus" : "Session Bus",
               opt_name,
               opt_object_path,
               opt_interface);
    }
  g_free (name_owner);
}

static void
on_name_owner_notify (GObject    *object,
                      GParamSpec *pspec,
                      gpointer    user_data)
{
  GDBusProxy *proxy = G_DBUS_PROXY (object);
  print_proxy (proxy);
}

int
main (int argc, char *argv[])
{
  GOptionContext *opt_context;
  GError *error;
  GDBusProxyFlags flags;
  GDBusProxy *proxy;

  loop = NULL;
  proxy = NULL;

  opt_context = g_option_context_new ("g_bus_watch_proxy() example");
  g_option_context_set_summary (opt_context,
                                "Example: to watch the object of gdbus-example-server, use:\n"
                                "\n"
                                "  ./gdbus-example-watch-proxy -n org.gtk.GDBus.TestServer  \\\n"
                                "                              -o /org/gtk/GDBus/TestObject \\\n"
                                "                              -i org.gtk.GDBus.TestInterface");
  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
  error = NULL;
  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
    {
      g_printerr ("Error parsing options: %s\n", error->message);
      goto out;
    }
  if (opt_name == NULL || opt_object_path == NULL || opt_interface == NULL)
    {
      g_printerr ("Incorrect usage, try --help.\n");
      goto out;
    }

  flags = G_DBUS_PROXY_FLAGS_NONE;
  if (opt_no_properties)
    flags |= G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES;
  if (opt_no_auto_start)
    flags |= G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START;

  loop = g_main_loop_new (NULL, FALSE);

  error = NULL;
  proxy = g_dbus_proxy_new_for_bus_sync (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
                                         flags,
                                         NULL, /* GDBusInterfaceInfo */
                                         opt_name,
                                         opt_object_path,
                                         opt_interface,
                                         NULL, /* GCancellable */
                                         &error);
  if (proxy == NULL)
    {
      g_printerr ("Error creating proxy: %s\n", error->message);
      g_error_free (error);
      goto out;
    }

  g_signal_connect (proxy,
                    "g-properties-changed",
                    G_CALLBACK (on_properties_changed),
                    NULL);
  g_signal_connect (proxy,
                    "g-signal",
                    G_CALLBACK (on_signal),
                    NULL);
  g_signal_connect (proxy,
                    "notify::g-name-owner",
                    G_CALLBACK (on_name_owner_notify),
                    NULL);
  print_proxy (proxy);

  g_main_loop_run (loop);

 out:
  if (proxy != NULL)
    g_object_unref (proxy);
  if (loop != NULL)
    g_main_loop_unref (loop);
  g_option_context_free (opt_context);
  g_free (opt_name);
  g_free (opt_object_path);
  g_free (opt_interface);

  return 0;
}
