  /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
#include <string.h>
#include <gobject/gvaluecollector.h>
#include "bonobo-app-client.h"
#include "bonobo-exception.h"
#include "bonobo-types.h"


static void bonobo_app_client_class_init    (BonoboAppClientClass *klass);
static void bonobo_app_client_init          (BonoboAppClient      *app_client);
static void bonobo_app_client_free_msgdescs (BonoboAppClient      *self);

static gpointer parent_class;

GType
bonobo_app_client_get_type (void)
{
	static GType app_client_type = 0;

	if (!app_client_type)
	{
		const GTypeInfo app_client_info =
			{
				sizeof (BonoboAppClientClass),
				NULL,		/* base_init */
				NULL,		/* base_finalize */
				(GClassInitFunc) bonobo_app_client_class_init,
				NULL,		/* class_finalize */
				NULL,		/* class_data */
				sizeof (BonoboAppClient),
				0,		/* n_preallocs */
				(GInstanceInitFunc) bonobo_app_client_init
			};
		
		app_client_type = g_type_register_static
			(G_TYPE_OBJECT, "BonoboAppClient", &app_client_info, 0);
	}

	return app_client_type;
}


static void
bonobo_app_client_finalize (GObject *object)
{
	BonoboAppClient *self = BONOBO_APP_CLIENT (object);

	if (self->msgdescs)
		bonobo_app_client_free_msgdescs (self);

	if (self->app_server != CORBA_OBJECT_NIL) {
		bonobo_object_release_unref (self->app_server, NULL);
		self->app_server = CORBA_OBJECT_NIL;
	}

	if (G_OBJECT_CLASS(parent_class)->finalize)
		G_OBJECT_CLASS(parent_class)->finalize (object);
}


static void
bonobo_app_client_class_init (BonoboAppClientClass *class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (class);

	parent_class = g_type_class_peek_parent (class);
	object_class->finalize = bonobo_app_client_finalize;
}


static void
bonobo_app_client_init (BonoboAppClient *app_client)
{

}


/**
 * bonobo_app_client_new:

 * @app_server: object reference to a Bonobo::Application; this
 * function takes ownership of this reference (use
 * bonobo_object_dup_ref() if you want to keep your own reference.)
 * 
 * Create an application client object connected to the remote (or
 * local) Bonobo::Application object.
 *
 * <warning>Applications should not use this function. See
 * bonobo_application_register_unique().</warning>
 * 
 * Return value: a #BonoboAppClient object.
 **/
BonoboAppClient *
bonobo_app_client_new (Bonobo_Application app_server)
{
	BonoboAppClient *app_client;

	app_client = g_object_new (BONOBO_TYPE_APP_CLIENT, NULL);
	app_client->app_server = app_server;
	return app_client;
}


/**
 * bonobo_app_client_msg_send_argv:
 * @app_client: client
 * @message: message name
 * @argv: %NULL-terminated vector of pointers to GValue, the arguments
 * to pass with the message.
 * 
 * Like bonobo_app_client_msg_send(), except that it receives a single
 * argument vector instead of a variable number of arguments.
 * 
 * Return value: the message return value
 **/
GValue *
bonobo_app_client_msg_send_argv (BonoboAppClient   *app_client,
				 const char        *message,
				 const GValue      *argv[],
				 CORBA_Environment *opt_env)
{
	CORBA_any                  *ret;
	Bonobo_Application_ArgList *args;
	GValue                     *rv;
	CORBA_Environment           ev1, *ev;
	int                         i, argv_len;

	g_return_val_if_fail (app_client, NULL);
	g_return_val_if_fail (BONOBO_IS_APP_CLIENT (app_client), NULL);

	for (argv_len = -1; argv[++argv_len];);

	args = Bonobo_Application_ArgList__alloc ();
	args->_length = argv_len;
	args->_buffer = Bonobo_Application_ArgList_allocbuf (argv_len);
	for (i = 0; i < argv_len; ++i) {
		if (!bonobo_arg_from_gvalue_alloc (&args->_buffer[i], argv[i])) {
			g_warning ("Failed to convert type '%s' to CORBA::any",
				   g_type_name (G_VALUE_TYPE (argv[i])));
			args->_buffer[i]._type = TC_void;
		}
	}
	CORBA_sequence_set_release (args, CORBA_TRUE);

	if (opt_env)
		ev = opt_env;
	else {
		CORBA_exception_init (&ev1);
		ev = &ev1;
	}
	ret = Bonobo_Application_message (app_client->app_server, message, args, ev);
	CORBA_free (args);
	if (ev->_major != CORBA_NO_EXCEPTION) {
		if (!opt_env) {
			char *text = bonobo_exception_get_text (ev);
			g_warning ("error while sending message to application server: %s",
				   text);
			g_free (text);
			CORBA_exception_free (&ev1);
		}
		return NULL;
	}
	if (!opt_env)
		CORBA_exception_free (&ev1);
	
	if (ret->_type != TC_void) {
		rv = g_new0 (GValue, 1);
		bonobo_arg_to_gvalue_alloc (ret, rv);
	} else
		rv = NULL;
	CORBA_free (ret);
	return rv;
}


/**
 * bonobo_app_client_msg_send_valist:
 * @app_client: client
 * @message: message name
 * @opt_env: optional corba environment
 * @first_arg_type: first message parameter
 * @var_args: remaining parameters
 * 
 * See bonobo_app_client_msg_send().
 * 
 * Return value: return value
 **/
GValue *
bonobo_app_client_msg_send_valist (BonoboAppClient   *app_client,
				   const char        *message,
				   CORBA_Environment *opt_env,
				   GType              first_arg_type,
				   va_list            var_args)
{
	GValue                     *value, *rv;
	GPtrArray                  *argv;
	GType                       type;
	gchar                      *err;
	int                         i;
	gboolean                    first_arg = TRUE;

	g_return_val_if_fail (app_client, NULL);
	g_return_val_if_fail (BONOBO_IS_APP_CLIENT (app_client), NULL);

	argv = g_ptr_array_new ();
	while ((type = (first_arg? first_arg_type :
			va_arg (var_args, GType))) != G_TYPE_NONE)
	{
		first_arg = FALSE;
		value = g_new0 (GValue, 1);
		g_value_init (value, type);
		G_VALUE_COLLECT(value, var_args, 0, &err);
		if (err) g_error("error collecting value: %s", err);
		g_ptr_array_add (argv, value);
	}
	g_ptr_array_add (argv, NULL);

	rv = bonobo_app_client_msg_send_argv (app_client, message,
					      (const GValue **) argv->pdata,
					      opt_env);

	for (i = 0; i < argv->len - 1; ++i) {
		value = g_ptr_array_index (argv, i);
		g_value_unset (value);
		g_free (value);
	}
	g_ptr_array_free (argv, TRUE);

	return rv;
}

/**
 * bonobo_app_client_msg_send:
 * @app_client: the client interface associated with the application
 * to which we wish to send a message
 * @message: message name
 * @...: arguments
 * 
 * Send a message to the application server. Takes a variable length
 * argument list of GType, value pairs, terminated with
 * %G_TYPE_NONE. Values are direct C values, not GValues! Example:
 * <informalexample><programlisting>
 * GValue *retval;
 * retval = bonobo_app_client_msg_send (app_client, "openURL",
 *                                      G_TYPE_STRING, "http://www.gnome.org",
 *                                      G_TYPE_BOOLEAN, TRUE,
 *                                      G_TYPE_NONE);
 * </programlisting></informalexample>
 * 
 * Return value: a GValue containing the value returned from the
 * aplication server.
 **/
GValue *
bonobo_app_client_msg_send (BonoboAppClient   *app_client,
			    const char        *message,
			    CORBA_Environment *opt_env,
			    GType              first_arg_type,
			    ...)
{
	GValue  *rv;
	va_list  var_args;
	
	va_start (var_args, first_arg_type);
	rv = bonobo_app_client_msg_send_valist (app_client, message, opt_env,
						first_arg_type, var_args);
	va_end (var_args);
	return rv;
}


static inline GType
_typecode_to_gtype (CORBA_TypeCode tc)
{
	static GHashTable *hash = NULL;

	if (!hash) {
		hash = g_hash_table_new (g_direct_hash, g_direct_equal);
#define mapping(gtype, corba_type)\
		g_hash_table_insert (hash, corba_type, GUINT_TO_POINTER (gtype));
		
		mapping (G_TYPE_NONE,    TC_void);
		mapping (G_TYPE_BOOLEAN, TC_CORBA_boolean);
		mapping (G_TYPE_LONG,    TC_CORBA_long);
		mapping (G_TYPE_ULONG,   TC_CORBA_unsigned_long);
		mapping (G_TYPE_FLOAT,   TC_CORBA_float);
		mapping (G_TYPE_DOUBLE,  TC_CORBA_double);
		mapping (G_TYPE_STRING,  TC_CORBA_string);

		mapping (BONOBO_TYPE_CORBA_ANY,  TC_CORBA_any);
#undef mapping
	}
	return GPOINTER_TO_UINT (g_hash_table_lookup (hash, tc));
}

static void
bonobo_app_client_free_msgdescs (BonoboAppClient *self)
{
	int i;
	
	for (i = 0; self->msgdescs[i].name; ++i) {
		g_free (self->msgdescs[i].name);
		g_free (self->msgdescs[i].types);
	}
	g_free (self->msgdescs);
	self->msgdescs = NULL;
}

static void
bonobo_app_client_get_msgdescs (BonoboAppClient *self)
{
	Bonobo_Application_MessageList  *msglist;
	CORBA_Environment                ev;
	int                              i, j;

	g_return_if_fail (!self->msgdescs);
	CORBA_exception_init (&ev);
	msglist = Bonobo_Application_listMessages (self->app_server, &ev);
	if (ev._major != CORBA_NO_EXCEPTION) {
		char *text = bonobo_exception_get_text (&ev);
		g_warning ("Bonobo::Application::listMessages: %s", text);
		g_free (text);
		CORBA_exception_free (&ev);
		return;
	}
	CORBA_exception_free (&ev);
	g_return_if_fail (msglist);
	self->msgdescs = g_new (BonoboAppClientMsgDesc, msglist->_length + 1);
	for (i = 0; i < msglist->_length; ++i) {
		self->msgdescs[i].name = g_strdup (msglist->_buffer[i].name);
		self->msgdescs[i].return_type =
			_typecode_to_gtype (msglist->_buffer[i].return_type);
		self->msgdescs[i].types = g_new (GType, msglist->_buffer[i].types._length + 1);
		for (j = 0; j < msglist->_buffer[i].types._length; ++j)
			self->msgdescs[i].types[j] =
				_typecode_to_gtype (msglist->_buffer[i].types._buffer[j]);
		self->msgdescs[i].types[j] = G_TYPE_NONE;
		self->msgdescs[i].description = g_strdup (msglist->_buffer[i].description);
	}
	self->msgdescs[i].name = NULL;
	self->msgdescs[i].return_type = G_TYPE_NONE;
	self->msgdescs[i].types = NULL;
	CORBA_free (msglist);
}


/**
 * bonobo_app_client_msg_list:
 * 
 * Obtain a list of messages supported by the application server.
 * 
 * Return value: a NULL terminated vector of strings; use g_strfreev()
 * to free it.
 **/
BonoboAppClientMsgDesc const *
bonobo_app_client_msg_list (BonoboAppClient *app_client)
{

	g_return_val_if_fail (BONOBO_IS_APP_CLIENT (app_client), NULL);

	if (!app_client->msgdescs)
		bonobo_app_client_get_msgdescs (app_client);
	return app_client->msgdescs;
}


/**
 * bonobo_app_client_new_instance:
 * @app_client: a #BonoboAppClient
 * @argc: length of @argv
 * @argv: array of command-line arguments
 * @opt_env: a #CORBA_Environment, or %NULL.
 * 
 * Ask the application server to emit a "new-instance" signal
 * containing the specified string vector.
 * 
 * Return value: the message return value
 **/
gint
bonobo_app_client_new_instance (BonoboAppClient   *app_client,
				int                argc,
				char              *argv[],
				CORBA_Environment *opt_env)
{
	CORBA_sequence_CORBA_string *corba_argv;
	int                          i;
	gint                         rv;
	CORBA_Environment            *ev, ev1;

	corba_argv = CORBA_sequence_CORBA_string__alloc();
	corba_argv->_buffer  = CORBA_sequence_CORBA_string_allocbuf (argc);
	corba_argv->_length  = argc;
	corba_argv->_maximum = argc;
	for (i = 0; i < argc; ++i)
		corba_argv->_buffer[i] = CORBA_string_dup (argv[i]);

	if (opt_env)
		ev = opt_env;
	else {
		CORBA_exception_init (&ev1);
		ev = &ev1;
	}
	rv = Bonobo_Application_newInstance (app_client->app_server, corba_argv, ev);
	CORBA_free (corba_argv);
	if (!opt_env) {
		if (ev->_major != CORBA_NO_EXCEPTION) {
			char *text = bonobo_exception_get_text (ev);
			g_warning ("newInstance failed: %s", text);
			g_free (text);
		}
		CORBA_exception_free (&ev1);
	}
	return rv;
}

