/* -*- 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 <glib-object.h>
#include <string.h>

#include "oobs-object.h"
#include "oobs-object-private.h"
#include "oobs-list.h"
#include "oobs-list-private.h"
#include "oobs-servicesconfig.h"
#include "oobs-service.h"
#include "utils.h"

#define SERVICES_CONFIG_REMOTE_OBJECT "ServicesConfig"
#define OOBS_SERVICES_CONFIG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), OOBS_TYPE_SERVICES_CONFIG, OobsServicesConfigPrivate))

typedef struct _OobsServicesConfigPrivate OobsServicesConfigPrivate;

struct _OobsServicesConfigPrivate
{
  OobsList *services_list;
  GList *runlevels;
  OobsServicesRunlevel *default_runlevel;
};

static void oobs_services_config_class_init (OobsServicesConfigClass *class);
static void oobs_services_config_init       (OobsServicesConfig      *config);
static void oobs_services_config_finalize   (GObject                 *object);

static void oobs_services_config_update     (OobsObject   *object);
static void oobs_services_config_commit     (OobsObject   *object);


G_DEFINE_TYPE (OobsServicesConfig, oobs_services_config, OOBS_TYPE_OBJECT);

static void
oobs_services_config_class_init (OobsServicesConfigClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  OobsObjectClass *oobs_object_class = OOBS_OBJECT_CLASS (class);

  object_class->finalize    = oobs_services_config_finalize;
  oobs_object_class->commit = oobs_services_config_commit;
  oobs_object_class->update = oobs_services_config_update;

  g_type_class_add_private (object_class,
			    sizeof (OobsServicesConfigPrivate));
}

static void
oobs_services_config_init (OobsServicesConfig *config)
{
  OobsServicesConfigPrivate *priv;

  g_return_if_fail (OOBS_IS_SERVICES_CONFIG (config));

  priv = OOBS_SERVICES_CONFIG_GET_PRIVATE (config);

  priv->services_list = _oobs_list_new (OOBS_TYPE_SERVICE);
  config->_priv = priv;
}

static void
free_runlevel (OobsServicesRunlevel *runlevel)
{
  g_free (runlevel->name);
  g_free (runlevel);
}

static void
oobs_services_config_finalize (GObject *object)
{
  OobsServicesConfigPrivate *priv;

  g_return_if_fail (OOBS_IS_SERVICES_CONFIG (object));

  priv = OOBS_SERVICES_CONFIG (object)->_priv;

  if (priv)
    {
      if (priv->services_list)
	g_object_unref (priv->services_list);

      if (priv->runlevels)
	{
	  g_list_foreach (priv->runlevels, (GFunc) free_runlevel, NULL);
	  g_list_free (priv->runlevels);
	}

      priv->default_runlevel = NULL;
    }

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

static gint
runlevel_to_role (const gchar *runlevel)
{
  if (strcmp (runlevel, "0") == 0)
    return OOBS_RUNLEVEL_HALT;
  else if (strcmp (runlevel, "1") == 0)
    return OOBS_RUNLEVEL_MONOUSER;
  else if (strcmp (runlevel, "6") == 0)
    return OOBS_RUNLEVEL_REBOOT;

  /* fallback value */
  return OOBS_RUNLEVEL_MULTIUSER;
}

static void
create_runlevels_list_from_dbus_reply (OobsObject      *object,
				       DBusMessage     *reply,
				       DBusMessageIter  iter)
{
  OobsServicesConfigPrivate *priv;
  OobsServicesRunlevel *runlevel;
  gchar *name;

  priv = OOBS_SERVICES_CONFIG (object)->_priv;

  while (dbus_message_iter_get_arg_type (&iter) == DBUS_TYPE_STRING)
    {
      dbus_message_iter_get_basic (&iter, &name);

      runlevel = g_new0 (OobsServicesRunlevel, 1);
      runlevel->name = g_strdup (name);
      runlevel->role = runlevel_to_role (runlevel->name);

      priv->runlevels = g_list_prepend (priv->runlevels, runlevel);
      dbus_message_iter_next (&iter);
    }

  priv->runlevels = g_list_reverse (priv->runlevels);
}

static OobsServicesRunlevel*
get_runlevel (OobsServicesConfig *config,
	      const gchar        *runlevel)
{
  OobsServicesConfigPrivate *priv;
  OobsServicesRunlevel *rl;
  GList *list;

  priv = config->_priv;
  list = priv->runlevels;

  while (list)
    {
      rl = list->data;

      if (strcmp (rl->name, runlevel) == 0)
	return rl;

      list = list->next;
    }

  return NULL;
}

static void
create_service_runlevels_from_dbus_reply (OobsServicesConfig *config,
					  OobsService        *service,
					  DBusMessage        *reply,
					  DBusMessageIter     struct_iter)
{
  DBusMessageIter runlevel_iter;
  OobsServicesRunlevel *rl;
  OobsServiceStatus status;
  gchar *runlevel;
  gint priority;

  while (dbus_message_iter_get_arg_type (&struct_iter) == DBUS_TYPE_STRUCT)
    {
      dbus_message_iter_recurse (&struct_iter, &runlevel_iter);

      dbus_message_iter_get_basic (&runlevel_iter, &runlevel);
      dbus_message_iter_next (&runlevel_iter);

      dbus_message_iter_get_basic (&runlevel_iter, &status);
      dbus_message_iter_next (&runlevel_iter);

      dbus_message_iter_get_basic (&runlevel_iter, &priority);
      dbus_message_iter_next (&runlevel_iter);

      rl = get_runlevel (config, runlevel);

      if (rl)
	oobs_service_set_runlevel_configuration (service, rl, status, priority);

      dbus_message_iter_next (&struct_iter);
    }
}

static OobsService*
create_service_from_dbus_reply (OobsServicesConfig *config,
				DBusMessage        *reply,
				DBusMessageIter     struct_iter)
{
  GObject *service;
  DBusMessageIter iter, runlevels_iter;
  gchar *name;

  dbus_message_iter_recurse (&struct_iter, &iter);

  dbus_message_iter_get_basic (&iter, &name);
  dbus_message_iter_next (&iter);

  service = g_object_new (OOBS_TYPE_SERVICE,
			  "name", name,
			  NULL);

  dbus_message_iter_recurse (&iter, &runlevels_iter);
  create_service_runlevels_from_dbus_reply (config,
					    OOBS_SERVICE (service),
					    reply, runlevels_iter);
  return OOBS_SERVICE (service);
}

static void
oobs_services_config_update (OobsObject *object)
{
  OobsServicesConfigPrivate *priv;
  DBusMessage     *reply;
  DBusMessageIter  iter, elem_iter;
  OobsListIter     list_iter;
  OobsService     *service;
  gchar           *default_runlevel;

  priv  = OOBS_SERVICES_CONFIG (object)->_priv;
  reply = _oobs_object_get_dbus_message (object);

  _oobs_list_set_locked (priv->services_list, FALSE);

  /* First of all, free the previous config */
  oobs_list_clear (priv->services_list);

  dbus_message_iter_init (reply, &iter);

  dbus_message_iter_recurse (&iter, &elem_iter);
  create_runlevels_list_from_dbus_reply (object, reply, elem_iter);
  dbus_message_iter_next (&iter);

  dbus_message_iter_get_basic (&iter, &default_runlevel);
  priv->default_runlevel = get_runlevel (OOBS_SERVICES_CONFIG (object), default_runlevel);
  dbus_message_iter_next (&iter);

  dbus_message_iter_recurse (&iter, &elem_iter);

  while (dbus_message_iter_get_arg_type (&elem_iter) == DBUS_TYPE_STRUCT)
    {
      service = create_service_from_dbus_reply (OOBS_SERVICES_CONFIG (object),
						reply, elem_iter);

      oobs_list_append (priv->services_list, &list_iter);
      oobs_list_set    (priv->services_list, &list_iter, G_OBJECT (service));
      g_object_unref   (service);

      dbus_message_iter_next (&elem_iter);
    }

  _oobs_list_set_locked (priv->services_list, TRUE);
}

static void
create_dbus_struct_from_service_runlevels (OobsService     *service,
					   GList           *runlevels,
					   DBusMessage     *message,
					   DBusMessageIter *iter)
{
  DBusMessageIter runlevels_iter, struct_iter;
  OobsServicesRunlevel *runlevel;
  OobsServiceStatus status;
  gint priority;

  dbus_message_iter_open_container (iter,
				    DBUS_TYPE_ARRAY,
				    DBUS_STRUCT_BEGIN_CHAR_AS_STRING
				    DBUS_TYPE_STRING_AS_STRING
				    DBUS_TYPE_INT32_AS_STRING
				    DBUS_TYPE_INT32_AS_STRING
				    DBUS_STRUCT_END_CHAR_AS_STRING,
				    &runlevels_iter);

  while (runlevels)
    {
      runlevel = runlevels->data;
      runlevels = runlevels->next;

      oobs_service_get_runlevel_configuration (service, runlevel, &status, &priority);

      if (status == OOBS_SERVICE_IGNORE)
	continue;

      dbus_message_iter_open_container (&runlevels_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter);

      utils_append_string (&struct_iter, runlevel->name);
      dbus_message_iter_append_basic (&struct_iter, DBUS_TYPE_INT32, &status);
      dbus_message_iter_append_basic (&struct_iter, DBUS_TYPE_INT32, &priority);

      dbus_message_iter_close_container (&runlevels_iter, &struct_iter);
    }

  dbus_message_iter_close_container (iter, &runlevels_iter);
}

static gboolean
create_dbus_struct_from_service (OobsService     *service,
				 GList           *runlevels,
				 DBusMessage     *message,
				 DBusMessageIter *array_iter)
{
  DBusMessageIter struct_iter;
  gchar *name;

  g_object_get (G_OBJECT (service),
		"name", &name,
		NULL);

  g_return_val_if_fail (name, FALSE);

  dbus_message_iter_open_container (array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter);

  utils_append_string (&struct_iter, name);
  create_dbus_struct_from_service_runlevels (service, runlevels, message, &struct_iter);

  dbus_message_iter_close_container (array_iter, &struct_iter);

  g_free (name);

  return TRUE;
}

static void
oobs_services_config_commit (OobsObject *object)
{
  OobsServicesConfigPrivate *priv;
  DBusMessage *message;
  DBusMessageIter iter, array_iter;
  OobsListIter list_iter;
  gboolean valid, correct;
  GObject *service;

  correct = TRUE;
  priv = OOBS_SERVICES_CONFIG (object)->_priv;
  message = _oobs_object_get_dbus_message (object);
  dbus_message_iter_init_append (message, &iter);

  /* FIXME: nothing inserted here, backends ignore this */
  utils_create_dbus_array_from_string_list (NULL, message, &iter);

  /* FIXME: this is ignored by the backend too */
  utils_append_string (&iter, NULL);

  dbus_message_iter_open_container (&iter,
				    DBUS_TYPE_ARRAY,
				    DBUS_STRUCT_BEGIN_CHAR_AS_STRING
				    DBUS_TYPE_STRING_AS_STRING
				    DBUS_TYPE_ARRAY_AS_STRING
				    DBUS_STRUCT_BEGIN_CHAR_AS_STRING
				    DBUS_TYPE_STRING_AS_STRING
				    DBUS_TYPE_INT32_AS_STRING
				    DBUS_TYPE_INT32_AS_STRING
				    DBUS_STRUCT_END_CHAR_AS_STRING
				    DBUS_STRUCT_END_CHAR_AS_STRING,
				    &array_iter);

  valid = oobs_list_get_iter_first (priv->services_list, &list_iter);

  while (correct && valid)
    {
      service = oobs_list_get (priv->services_list, &list_iter);
      correct = create_dbus_struct_from_service (OOBS_SERVICE (service),
						 priv->runlevels,
						 message, &array_iter);
      g_object_unref (service);
      valid = oobs_list_iter_next (priv->services_list, &list_iter);
    }

  dbus_message_iter_close_container (&iter, &array_iter);

  if (!correct)
    {
      /* malformed data, unset the message */
      _oobs_object_set_dbus_message (object, NULL);
    }
}

/**
 * oobs_services_config_get:
 * @session: An #OobsSession.
 * 
 * Returns the #OobsServicesConfig singleton, which represents
 * the services that are run during system init.
 * 
 * Return Value: the singleton #OobsServicesConfig object.
 **/
OobsObject*
oobs_services_config_get (OobsSession *session)
{
  static OobsObject *object = NULL;

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

  if (!object)
    {
      object = g_object_new (OOBS_TYPE_SERVICES_CONFIG,
			     "remote-object", SERVICES_CONFIG_REMOTE_OBJECT,
			     "session",       session,
			     NULL);

      oobs_object_update (object);
    }

  return object;
}

/**
 * oobs_services_config_get_services:
 * @config: An #OobsServicesConfig.
 * 
 * Returns an #OobsList containing objects of type #OobsService.
 * The returned #OobsList is locked, meaning that new elements
 * can't be added nor removed.
 * 
 * Return Value: an #OobsList containing the services list.
 **/
OobsList*
oobs_services_config_get_services (OobsServicesConfig *config)
{
  OobsServicesConfigPrivate *priv;

  g_return_val_if_fail (config != NULL, NULL);
  g_return_val_if_fail (OOBS_IS_SERVICES_CONFIG (config), NULL);

  priv = config->_priv;

  return priv->services_list;
}

/**
 * oobs_services_config_get_runlevels:
 * @config: An #OobsServicesConfig.
 * 
 * Returns a list of #OobsServicesRunlevel describing the available
 * runlevels.
 * 
 * Return Value: list of runlevels. the list must be freed with
 *               g_list_free ();
 **/
GList*
oobs_services_config_get_runlevels (OobsServicesConfig *config)
{
  OobsServicesConfigPrivate *priv;

  g_return_val_if_fail (OOBS_IS_SERVICES_CONFIG (config), NULL);
  
  priv = config->_priv;

  return g_list_copy (priv->runlevels);
}

/**
 * oobs_services_config_get_default_runlevel:
 * @config: An #OobsServicesConfig.
 * 
 * Returns the current runlevel.
 * 
 * Return Value: An #OobsServicesRunlevel describing the current
 *               runlevel. This value must not be freed, modified,
 *               or stored.
 **/
G_CONST_RETURN OobsServicesRunlevel*
oobs_services_config_get_default_runlevel (OobsServicesConfig *config)
{
  OobsServicesConfigPrivate *priv;

  g_return_val_if_fail (OOBS_IS_SERVICES_CONFIG (config), NULL);
  
  priv = config->_priv;

  return priv->default_runlevel;
}


syntax highlighted by Code2HTML, v. 0.9.1