/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
/* Copyright (C) 2005 Carlos Garnacho
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 *
 * Authors: Carlos Garnacho Parro  <carlosg@gnome.org>
 */

#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <glib-object.h>
#include <glib.h>
#include "oobs-session.h"
#include "oobs-session-private.h"
#include "oobs-object.h"
#include "utils.h"

#define OOBS_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), OOBS_TYPE_SESSION, OobsSessionPrivate))
#define PLATFORMS_PATH OOBS_DBUS_PATH_PREFIX "/Platform"
#define PLATFORMS_INTERFACE OOBS_DBUS_METHOD_PREFIX ".Platform"

typedef struct _OobsSessionPrivate OobsSessionPrivate;

struct _OobsSessionPrivate
{
  DBusConnection *connection;
  DBusError       dbus_error;

  GList    *session_objects;
  gboolean  is_authenticated;

  gchar    *platform;
  GList    *supported_platforms;
};

static void oobs_session_class_init (OobsSessionClass *class);
static void oobs_session_init       (OobsSession      *session);
static void oobs_session_finalize   (GObject         *object);

static void oobs_session_set_property (GObject      *object,
				       guint         prop_id,
				       const GValue *value,
				       GParamSpec   *pspec);
static void oobs_session_get_property (GObject      *object,
				       guint         prop_id,
				       GValue       *value,
				       GParamSpec   *pspec);
enum
{
  PROP_0,
  PROP_PLATFORM
};

G_DEFINE_TYPE (OobsSession, oobs_session, G_TYPE_OBJECT);

static void
oobs_session_class_init (OobsSessionClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->set_property = oobs_session_set_property;
  object_class->get_property = oobs_session_get_property;
  object_class->finalize     = oobs_session_finalize;

  g_object_class_install_property (object_class,
				   PROP_PLATFORM,
				   g_param_spec_string ("platform",
							"Platform",
							"Name of the platform the session is running on",
							NULL,
							G_PARAM_READWRITE));
  g_type_class_add_private (object_class,
			    sizeof (OobsSessionPrivate));
}

static void
oobs_session_init (OobsSession *session)
{
  OobsSessionPrivate *priv;

  g_return_if_fail (OOBS_IS_SESSION (session));
  priv = OOBS_SESSION_GET_PRIVATE (session);

  dbus_error_init (&priv->dbus_error);
  priv->connection = dbus_bus_get (DBUS_BUS_SYSTEM, &priv->dbus_error);

  if (dbus_error_is_set (&priv->dbus_error))
    g_warning (priv->dbus_error.message);
  else
    dbus_connection_setup_with_g_main (priv->connection, NULL);

  priv->session_objects  = NULL;
  priv->is_authenticated = FALSE;
  session->_priv = priv;
}

static void
unregister_object_node (OobsSessionPrivate *priv, GList *node)
{
  priv->session_objects = g_list_remove_link (priv->session_objects, node);

  /* FIXME: This sucks a bit, there must be a way for getting the OobsObjects notified
   * of the session finalizing after the actual finalize (unlike g_object_weak_ref)
   */
  g_object_set   (G_OBJECT (node->data),
		  "session", NULL,
		  NULL);
  g_object_unref (G_OBJECT (node->data));
  g_list_free_1  (node);
}

static void
unregister_objects_list (OobsSessionPrivate *priv)
{
  while (priv->session_objects)
    unregister_object_node (priv, priv->session_objects);
}

static void
oobs_session_finalize (GObject *object)
{
  OobsSession *session;
  OobsSessionPrivate *priv;

  g_return_if_fail (OOBS_IS_SESSION (object));

  session = OOBS_SESSION (object);
  priv    = session->_priv;

  if (priv)
    unregister_objects_list (priv);

  if (G_OBJECT_CLASS (oobs_session_parent_class)->finalize)
    (* G_OBJECT_CLASS (oobs_session_parent_class)->finalize) (object);
}

static void
oobs_session_set_property (GObject      *object,
			   guint         prop_id,
			   const GValue *value,
			   GParamSpec   *pspec)
{
  OobsSession *session;
  OobsSessionPrivate *priv;

  g_return_if_fail (OOBS_IS_SESSION (object));

  session = OOBS_SESSION (object);
  priv    = session->_priv;

  switch (prop_id)
    {
    case PROP_PLATFORM:
      oobs_session_set_platform (session, g_value_get_string (value));
      break;
    }
}

static void
oobs_session_get_property (GObject      *object,
			   guint         prop_id,
			   GValue       *value,
			   GParamSpec   *pspec)
{
  OobsSessionPrivate *priv;

  g_return_if_fail (OOBS_IS_SESSION (object));

  priv = OOBS_SESSION (object)->_priv;

  switch (prop_id)
    {
    case PROP_PLATFORM:
      g_value_set_string (value, priv->platform);
      break;
    }
}

/**
 * oobs_session_get:
 * 
 * Returns the #OobsSession singleton, which represents
 * the session with the system tools backends.
 * 
 * Return Value: the singleton #OobSession object.
 **/
OobsSession*
oobs_session_get (void)
{
  static OobsSession *session = NULL;

  if (!session)
    session = g_object_new (OOBS_TYPE_SESSION, NULL);

  return session;
}

/**
 * oobs_session_commit:
 * @session: an #OobsSession
 * 
 * Commits inmediately all the changes to the configuration
 * objects that have been requested through this #OobsSession.
 * Note that it will stop if it finds any error.
 *
 * Return Value: An #OobsResult representing the error.
 **/
OobsResult
oobs_session_commit (OobsSession *session)
{
  OobsSessionPrivate *priv;
  GList              *node;
  OobsObject         *object;
  OobsResult          result = OOBS_RESULT_OK;

  g_return_val_if_fail (session != NULL, OOBS_RESULT_ERROR);
  g_return_val_if_fail (OOBS_IS_SESSION (session), OOBS_RESULT_ERROR);

  priv = session->_priv;
  node = priv->session_objects;

  while (node && (result == OOBS_RESULT_OK))
    {
      object = OOBS_OBJECT (node->data);
      result = oobs_object_commit (object);

      node = node->next;
    }

  return result;
}

/**
 * oobs_session_get_connected:
 * @session: An #OobsSession
 * 
 * Returns whether the connection with the backends is established.
 * 
 * Return Value: #TRUE if there's connection with the backends.
 **/
gboolean
oobs_session_get_connected (OobsSession *session)
{
  OobsSessionPrivate *priv;

  g_return_val_if_fail (OOBS_IS_SESSION (session), FALSE);

  priv = session->_priv;
  return (priv->connection != NULL);
}

/**
 * oobs_session_get_platform:
 * @session: An #OobsSession.
 * @platform: location to store the current platform, or #NULL. This
 *            string is of internal use, and must not be freed or modified.
 * 
 * Retrieves the platform your system has been identified with, or
 * #NULL in case your platform is not recognized or other error happens.
 * 
 * Return Value: An #OobsResult representing the error.
 **/
OobsResult
oobs_session_get_platform (OobsSession  *session,
			   gchar       **platform)
{
  OobsSessionPrivate *priv;
  DBusMessage *message, *reply;
  DBusMessageIter iter;
  OobsResult result;
  const gchar *str;

  g_return_val_if_fail (OOBS_IS_SESSION (session), OOBS_RESULT_ERROR);

  priv = session->_priv;
  g_return_val_if_fail (priv->connection != NULL, OOBS_RESULT_ERROR);

  message = dbus_message_new_method_call (OOBS_DBUS_DESTINATION,
					  PLATFORMS_PATH,
					  PLATFORMS_INTERFACE,
					  "getPlatform");

  reply = dbus_connection_send_with_reply_and_block (priv->connection,
						     message, -1, &priv->dbus_error);
  dbus_message_unref (message);

  if (dbus_error_is_set (&priv->dbus_error))
    {
      if (dbus_error_has_name (&priv->dbus_error, DBUS_ERROR_ACCESS_DENIED))
	result = OOBS_RESULT_ACCESS_DENIED;
      else
	result = OOBS_RESULT_ERROR;

      dbus_error_free (&priv->dbus_error);

      if (platform)
	*platform = NULL;

      return result;
    }

  dbus_message_iter_init (reply, &iter);
  str = utils_get_string (&iter);
  priv->platform = (str) ? g_strdup (str) : NULL;

  if (platform)
    *platform = priv->platform;

  dbus_message_unref (reply);
  return (priv->platform) ? OOBS_RESULT_OK : OOBS_RESULT_NO_PLATFORM;
}

/**
 * oobs_session_set_platform:
 * @session: An #OobsSession.
 * @platform: A string defining the platform. see
 *            oobs_session_get_platforms_list() to know where to get this string.
 * 
 * Identifies your platform as the one set in @platform. This is only necessary if
 * your platform could not be guessed (and thus oobs_session_get_platform() would
 * return #OOBS_RESULT_NO_PLATFORM in this case).
 * 
 * Return Value: An #OobsResult representing the error.
 **/
OobsResult
oobs_session_set_platform (OobsSession *session,
			   const gchar *platform)
{
  OobsSessionPrivate *priv;
  DBusMessage *message;
  DBusMessageIter iter;
  DBusError error;
  OobsResult result;

  g_return_val_if_fail (OOBS_IS_SESSION (session), OOBS_RESULT_ERROR);
  g_return_val_if_fail (platform != NULL, OOBS_RESULT_ERROR);

  priv = session->_priv;
  g_return_val_if_fail (priv->connection != NULL, OOBS_RESULT_ERROR);
  dbus_error_init (&error);

  priv->platform = g_strdup (platform);
  g_object_notify (G_OBJECT (session), "platform");

  message = dbus_message_new_method_call (OOBS_DBUS_DESTINATION,
					  PLATFORMS_PATH,
					  PLATFORMS_INTERFACE,
					  "setPlatform");
  dbus_message_iter_init_append (message, &iter);
  utils_append_string (&iter, priv->platform);

  dbus_connection_send_with_reply_and_block (priv->connection, message, -1, &error);

  if (dbus_error_is_set (&error))
    {
      if (dbus_error_has_name (&error, DBUS_ERROR_NO_REPLY))
	result = OOBS_RESULT_OK;
      if (dbus_error_has_name (&error, DBUS_ERROR_ACCESS_DENIED))
	result = OOBS_RESULT_ACCESS_DENIED;
      else
	result = OOBS_RESULT_ERROR;

      dbus_error_free (&error);
    }
  else
    result = OOBS_RESULT_OK;

  return result;
}

static OobsResult
get_supported_platforms (OobsSession *session, GList **list)
{
  OobsSessionPrivate *priv;
  DBusMessage *message, *reply;
  DBusMessageIter list_iter, iter;
  OobsPlatform *platform;
  OobsResult result;
  GList *platforms = NULL;
  const gchar *str;

  priv = session->_priv;
  g_return_val_if_fail (priv->connection != NULL, OOBS_RESULT_ERROR);

  message = dbus_message_new_method_call (OOBS_DBUS_DESTINATION,
					  PLATFORMS_PATH,
					  PLATFORMS_INTERFACE,
					  "getPlatformList");

  reply = dbus_connection_send_with_reply_and_block (priv->connection,
						     message, -1, &priv->dbus_error);
  dbus_message_unref (message);

  if (dbus_error_is_set (&priv->dbus_error))
    {
      if (dbus_error_has_name (&priv->dbus_error, DBUS_ERROR_ACCESS_DENIED))
	result = OOBS_RESULT_ACCESS_DENIED;
      else
	result = OOBS_RESULT_ERROR;

      dbus_error_free (&priv->dbus_error);
      *list = NULL;
      return result;
    }

  dbus_message_iter_init (reply, &list_iter);
  dbus_message_iter_recurse (&list_iter, &list_iter);

  while (dbus_message_iter_get_arg_type (&list_iter) == DBUS_TYPE_STRUCT)
    {
      platform = g_new0 (OobsPlatform, 1);
      dbus_message_iter_recurse (&list_iter, &iter);

      str = utils_get_string (&iter);
      platform->name = g_strdup (str);
      dbus_message_iter_next (&iter);

      str = utils_get_string (&iter);
      platform->version = g_strdup (str);
      dbus_message_iter_next (&iter);
	  
      str = utils_get_string (&iter);
      platform->codename = g_strdup (str);
      dbus_message_iter_next (&iter);

      str = utils_get_string (&iter);
      platform->id = g_strdup (str);

      platforms = g_list_prepend (platforms, platform);
      dbus_message_iter_next (&list_iter);
    }

  *list = g_list_reverse (platforms);
  dbus_message_unref (reply);

  return OOBS_RESULT_OK;
}

/**
 * oobs_session_get_supported_platforms:
 * @session: An #OobsSession.
 * @platforms: return location for the list of platforms. It's a
 *             #GList of #OobsPlatform structs. You must free
 *             this list with g_list_free().
 * 
 * Retrieves the list of supported platforms, this is only necessary when
 + oobs_session_get_platform() has returned #OOBS_RESULT_NO_PLATFORM. To
 * specify a platform, you must use oobs_session_set_platform(), being
 * the platform string in that function the platform->id value inside
 * the #OobsPlatform struct.
 * 
 * Return Value: An #OobsResult representing the error.
 **/
OobsResult
oobs_session_get_supported_platforms (OobsSession  *session,
				      GList       **platforms)
{
  OobsSessionPrivate *priv;
  OobsResult result;

  /* it doesn't make any sense to call this function with platforms = NULL */
  g_return_val_if_fail (platforms != NULL, OOBS_RESULT_ERROR);
  g_return_val_if_fail (OOBS_IS_SESSION (session), OOBS_RESULT_ERROR);

  priv = session->_priv;

  if (!priv->supported_platforms)
    result = get_supported_platforms (session, &priv->supported_platforms);
  else
    {
      /* list is cached */
      result = OOBS_RESULT_OK;
    }

  *platforms = (priv->supported_platforms) ? g_list_copy (priv->supported_platforms) : NULL;
  return result;
}

/**
 * oobs_session_process_requests:
 * @session: An #OobsSession
 * 
 * Blocks until all pending asynchronous requests have been processed.
 **/
void
oobs_session_process_requests (OobsSession *session)
{
  OobsSessionPrivate *priv;

  g_return_if_fail (OOBS_IS_SESSION (session));

  priv = session->_priv;
  g_list_foreach (priv->session_objects, (GFunc) oobs_object_process_requests, NULL);
}

/* protected methods */
DBusConnection*
_oobs_session_get_connection_bus (OobsSession *session)
{
  OobsSessionPrivate *priv;

  g_return_val_if_fail (session != NULL, NULL);
  g_return_val_if_fail (OOBS_IS_SESSION (session), NULL);

  priv = session->_priv;
  return priv->connection;
}

void
_oobs_session_register_object (OobsSession *session, OobsObject *object)
{
  OobsSessionPrivate *priv;

  if (!session || !object)
    return;

  priv = session->_priv;
  priv->session_objects = g_list_prepend (priv->session_objects,
					  g_object_ref (object));
}

void
_oobs_session_unregister_object (OobsSession *session, OobsObject *object)
{
  OobsSessionPrivate *priv;
  GList              *node;
  gboolean            found;

  if (!session || !object)
    return;

  priv  = session->_priv;
  node  = priv->session_objects;
  found = FALSE;

  while (node && !found)
    {
      if (node->data == object)
        {
	  found = TRUE;
	  unregister_object_node (priv, node);
	}
      else
	node = node->next;
    }
}


syntax highlighted by Code2HTML, v. 0.9.1