/**
 * server-v0
 *   A really simply D-Bus server, intended as the initial example
 *   in a D-Bus server tutorial.
 *
 *   Copyright (c) 2013 Samuel A. Rebelsky.  All rights reserved.
 *
 *   This file is part of The Glimmer Guide to Writing a D-Bus Server.
 *
 *   The Glimmer Guide to Writing a D-Bus Server is free software: you 
 *   can redistribute it and/or modify it under the terms of the GNU 
 *   General Public License as published by the Free Software Foundation, 
 *   either version 3 of the License, or (at your option) any later version.
 *
 *   The Glimmer Guide to Writing a D-Bus Server 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */


// +-------+-----------------------------------------------------------
// | Notes |
// +-------+

/*

 o This server provides a single object that provides no methods.

 */


// +---------+---------------------------------------------------------
// | Headers |
// +---------+

#include <glib.h>
#include <gio/gio.h>
#include <stdlib.h>
#include <stdio.h>


// +--------------------------+----------------------------------------
// | Function Predeclarations |
// +--------------------------+


// +---------+---------------------------------------------------------
// | Globals |
// +---------+

/**
 *  The service name on the bus.
 */
static const gchar service[] = "edu.grinnell.glimmer.guide.SampleServer0";

/**
 * The object we publish on the bus.
 */
static const gchar object_name[] = "/edu/grinnell/glimmer/guide/SampleObject0";

/**
 * Introspection data for the one object, in the internal form.
 * (This will get filled in later.)
 */
static GDBusNodeInfo *introspection_data = NULL;

/**
 * Introspection data for the one object in XML form (somewhat human
 * readable, somewaht computer readable).
 */
static const gchar introspection_xml[] =
  "<node>"
  "  <interface name='edu.grinnell.glimmer.guide.SampleInterface0'>"
  "  </interface>"
  "</node>";


// +------------------+------------------------------------------------
// | Object Callbacks |
// +------------------+

/**
 * Handle a request for a property.  (Parameters should be obvious.)
 */
static GVariant *
handle_get_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GError          **error,
                     gpointer          user_data)
{
  // Print an optional log message
#ifdef VERBOSE
  fprintf (stderr, 
           "handle_get_property (%p,\"%s\",\"%s\",\"%s\",\"%s\",(error),%p)\n",
           connection, sender, object_path, interface_name, property_name, 
           user_data);
#endif

  // We currently don't have any properties, so this should be an
  // error.
  g_set_error (error,
               G_IO_ERROR,
               G_IO_ERROR_FAILED,
               "Invalid property '%s'",
               property_name);
  // And we're done
  return NULL;
} // handle_get_property

/** 
 * Handle a call to a method.  (Parameters should be obvious.)
 */
static void
handle_method_call (GDBusConnection       *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
#ifdef VERBOSE
  gchar *paramstr = g_variant_print (parameters, TRUE);
  fprintf (stderr, 
           "handle_method_call (%p,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",(invocation),%p)\n",
           connection, sender, object_path, interface_name, method_name, 
           paramstr, user_data);
  g_free (paramstr);
#endif

  // Method one: HelloWorld
  if (g_strcmp0 (method_name, "HelloWorld") == 0)
    {
      GVariant *result = g_variant_new ("(s)", "Hello World");
      g_dbus_method_invocation_return_value (invocation, result);
    } // if it's HelloWorld

  // Default: No such mehotd
  else
    {
      g_dbus_method_invocation_return_error (invocation,
                                             G_IO_ERROR,
                                             G_IO_ERROR_INVALID_ARGUMENT,
                                             "Invalid method: '%s'",
                                             method_name);
    } // if it's an unknown method
} // handle_method_call

/**
 * Handle a request to set a property.
 */
static gboolean
handle_set_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GVariant         *value,
                     GError          **error,
                     gpointer          user_data)
{
  // Print an optional log message
#ifdef VERBOSE
  gchar *valstr = g_variant_print (value, TRUE);
  fprintf (stderr, 
           "handle_set_property (%p,\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",(error),%p)\n",
           connection, sender, object_path, interface_name, property_name, 
           valstr, user_data);
  g_free (valstr);
#endif

  g_set_error (error,
               G_IO_ERROR,
               G_IO_ERROR_FAILED,
               "No such property: '%s'",
               property_name);
  return 0;
} // handle_set_property


// +---------------+---------------------------------------------------
// | Bus Callbacks |
// +---------------+

/**
 * What to do when the bus gets acquired.
 */
static void
on_bus_acquired (GDBusConnection *connection,
                 const gchar     *name,
                 gpointer        user_data)
{
  static GDBusInterfaceVTable interface_vtable =
    {
      handle_method_call,
      handle_get_property,
      handle_set_property
    };

  guint registration_id;
  GError *error = NULL;

  // A bit of (optional) notification
#ifdef VERBOSE
  fprintf (stderr, "on_bus_acquired (%p, \"%s\", %p)\n", 
           connection, name, user_data);
#endif

  registration_id = 
    g_dbus_connection_register_object (connection,
                                       object_name,
                                       introspection_data->interfaces[0],
                                       &interface_vtable,
                                       NULL,    // Optional user data
                                       NULL,    // Func. for freeing user data
                                       &error);

} // on_bus_acquired

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
  // A bit of (optional) notification
#ifdef VERBOSE
  fprintf (stderr, "on_name_acquired (%p, \"%s\", %p)\n", 
           connection, name, user_data);
#endif
} // on_name_acquired

static void
on_name_lost (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
  // A bit of (optional) notification
#ifdef VERBOSE
  fprintf (stderr, "on_name_lost (%p, \"%s\", %p)\n", 
           connection, name, user_data);
#endif
  // Things seem to have gone badly wrong, so give up
  exit (1);
} // on_name_lost


// +------+------------------------------------------------------------
// | Main |
// +------+

int
main (int argc, char *argv[])
{
  guint owner_id;
  GMainLoop *loop;

  // Set up glib.
  g_type_init ();

  // Build an internal representation of the interface
  introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);

  // Request the name on the bus
  owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
                             service,
                             G_BUS_NAME_OWNER_FLAGS_NONE,
                             on_bus_acquired,
                             on_name_acquired,
                             on_name_lost,
                             NULL,
                             NULL);

  // Start the main loop
  loop = g_main_loop_new (NULL, FALSE);
  g_main_loop_run (loop);

  // Tell the bus that we're done with the name
  g_bus_unown_name (owner_id);

  // Clean up after ourselves
  g_dbus_node_info_unref (introspection_data);

  // And we're done
  return 0;
} // main

