/*-
 * $Id: rr-connection.c,v 1.57 2002/10/06 18:17:03 jonas Exp $
 *
 * See the file LICENSE for redistribution information. 
 * If you have not received a copy of the license, please contact CodeFactory
 * by email at info@codefactory.se, or on the web at http://www.codefactory.se/
 * You may also write to: CodeFactory AB, SE-903 47, Umeå, Sweden.
 *
 * Copyright (c) 2002 Jonas Borgström <jonas@codefactory.se>
 * Copyright (c) 2002 Daniel Lundin   <daniel@codefactory.se>
 * Copyright (c) 2002 CodeFactory AB.  All rights reserved.
 */

#include <librr/rr.h>
#include <string.h>

static GObjectClass *parent_class = NULL;

static void finalize (GObject *object);
static void remove_out_queue_entry (RRConnection *connection, gint idx);

static void
rr_connection_init (GObject *object)
{
	RRConnection *conn = (RRConnection *)object;

	g_static_rw_lock_init (&conn->channel_lock);
	conn->channel_by_id = g_hash_table_new (NULL, NULL);

	conn->out_frames = g_queue_new ();

	conn->out_queue_lock = g_mutex_new ();
	conn->out_queue_cond = g_cond_new ();
	conn->out_queue = g_ptr_array_new ();
	conn->active_item = 0;

	conn->manager = rr_manager_new (0);
	rr_channel_set_connection (RR_CHANNEL (conn->manager), conn);

	g_static_rw_lock_init (&conn->languages_lock);
	conn->languages = NULL;

	conn->filter_stack = rr_filterstack_new ();
}

static void
rr_connection_class_init (GObjectClass *klass)
{
	klass->finalize = finalize;

	parent_class = g_type_class_peek_parent (klass);

	/* Register the "BEEP" frame types */
	rr_framefactory_register_type ("MSG", RR_TYPE_FRAME);
	rr_framefactory_register_type ("RPY", RR_TYPE_FRAME);
	rr_framefactory_register_type ("ERR", RR_TYPE_FRAME);
	rr_framefactory_register_type ("ANS", RR_TYPE_FRAME);
	rr_framefactory_register_type ("NUL", RR_TYPE_FRAME);
}

GType 
rr_connection_get_type (void)
{
	static GType rr_type = 0;

	if (!rr_type) {
		static GTypeInfo type_info = {
			sizeof (RRConnectionClass),
			NULL,
			NULL,
			(GClassInitFunc) rr_connection_class_init,
			NULL,
			NULL,
			sizeof (RRConnection),
			16,
			(GInstanceInitFunc) rr_connection_init
		};
		rr_type = g_type_register_static (G_TYPE_OBJECT, "RRConnection", 
						  &type_info, G_TYPE_FLAG_ABSTRACT);
	}
	return rr_type;
}

static void
unref_channel (gpointer key, gpointer data, gpointer user_data)
{
	g_object_unref (G_OBJECT (data));
}

static void
finalize (GObject *object)
{
	RRConnection *conn = (RRConnection *)object;

	if (conn->manager) 
		g_object_unref (G_OBJECT (conn->manager));

	g_static_rw_lock_writer_lock (&conn->channel_lock);
	g_hash_table_foreach (conn->channel_by_id,
			      unref_channel, NULL);
	g_hash_table_destroy (conn->channel_by_id);
	g_static_rw_lock_writer_unlock (&conn->channel_lock);
	g_static_rw_lock_free (&conn->channel_lock);

	g_mutex_lock (conn->out_queue_lock);
	g_list_foreach (conn->out_frames->head, (GFunc)g_object_unref, NULL);
	g_queue_free (conn->out_frames);
	g_ptr_array_free (conn->out_queue, TRUE);
	g_mutex_unlock (conn->out_queue_lock);
	g_mutex_free (conn->out_queue_lock);
	g_cond_free (conn->out_queue_cond);

	g_static_rw_lock_writer_lock (&conn->languages_lock);
	g_slist_foreach (conn->languages, (GFunc)g_free, NULL);
	g_slist_free (conn->languages);
	g_static_rw_lock_writer_unlock (&conn->languages_lock);
	g_static_rw_lock_free (&conn->languages_lock);

	rr_filterstack_free (conn->filter_stack);

	g_slist_foreach (conn->peer_profiles, (GFunc)g_free, NULL);
	g_slist_free (conn->peer_profiles);

	g_free (conn->server_name);

	if (conn->profreg)
		g_object_unref (G_OBJECT (conn->profreg));

	rr_callback_list_free (conn->quiescence_cb);

	parent_class->finalize (object);
}

/**
 * rr_connection_register_sender:
 * @connection: the #RRConnection object
 * @channel: the #RRChannel object
 * 
 * Tells the @connection that @channel has some things to send.
 *
 **/
void
rr_connection_register_sender (RRConnection *connection, RRChannel *channel)
{
	gint i;

	g_assert (RR_IS_CHANNEL (channel));
	g_assert (RR_IS_CONNECTION (connection));

	/* FIXME: return error if the connection isn't established */

	g_mutex_lock (connection->out_queue_lock);

	/* No point in adding a channel that hasn't anything to send */
	if (rr_channel_out_queue_empty (channel)) {

		g_mutex_unlock (connection->out_queue_lock);
		return;
	}

	/* Just return if the channel is already registered */
	for (i = 0; i < connection->out_queue->len; i++) {
		if (channel == g_ptr_array_index (connection->out_queue, i)) {

			g_mutex_unlock (connection->out_queue_lock);
			return;
		}
	}
	g_ptr_array_add (connection->out_queue, channel);
	g_mutex_unlock (connection->out_queue_lock);

	if (!connection->output_disabled)
		rr_connection_enable_output (connection);
}

/**
 * rr_connection_get_channel:
 * @connection: a #RRConnection.
 * @id: channel id 0..2147483647
 * 
 * Finds and returns the RRChannel instance that has channel @id on @connection.
 * 
 * Return value: a #RRChannel or %NULL.
 **/
RRChannel *
rr_connection_get_channel (RRConnection *connection, gint id)
{
	RRChannel *channel;

	g_static_rw_lock_reader_lock (&connection->channel_lock);
	channel = g_hash_table_lookup (connection->channel_by_id, 
				       GINT_TO_POINTER (id));
	g_static_rw_lock_reader_unlock (&connection->channel_lock);

	return channel;
}

/**
 * rr_connection_get_channel_locked:
 * @connection: a #RRConnection.
 * @id: channel id 0..2147483647
 * 
 * Finds and returns the RRChannel instance that has channel @id on @connection.
 * The channel mutex lock is also locked for the returned channel.
 * 
 * Return value: a #RRChannel or %NULL.
 **/
RRChannel *
rr_connection_get_channel_locked (RRConnection *connection, gint id)
{
	RRChannel *channel;

	g_static_rw_lock_reader_lock (&connection->channel_lock);
	channel = g_hash_table_lookup (connection->channel_by_id, 
				       GINT_TO_POINTER (id));
	if (channel)
		g_mutex_lock (channel->mutex);

	g_static_rw_lock_reader_unlock (&connection->channel_lock);

	return channel;
}

/**
 * rr_connection_add_channel:
 * @connection: a #RRConnection.
 * @channel: a #RRChannel.
 * 
 * Adds @channel to the list of channels started on the @connection.
 **/
void
rr_connection_add_channel (RRConnection *conn, RRChannel *channel)
{
	g_return_if_fail (RR_IS_CONNECTION (conn));
	g_return_if_fail (RR_IS_CHANNEL (channel));

	channel->connection = conn;
	g_static_rw_lock_writer_lock (&conn->channel_lock);
	g_hash_table_insert (conn->channel_by_id, GINT_TO_POINTER(channel->id), 
			     g_object_ref (G_OBJECT (channel)));
	g_static_rw_lock_writer_unlock (&conn->channel_lock);
}

static gboolean
remove_helper (gpointer key, gpointer data, gpointer user_data)
{
	RRChannel *channel = data;
	RRConnection *conn = user_data;
	gint i;

	g_assert (RR_IS_CHANNEL (channel));

	/* A special case for the manager channel */
	if (channel->id == 0) {

		g_object_unref (G_OBJECT (channel));
		return TRUE;
	}

	rr_channel_lock (channel);

	rr_main_work_pool_join (RRWPGROUP (channel));

	/* Remove the channel from the "active" list */
	g_mutex_lock (conn->out_queue_lock);
	for (i = 0; i < conn->out_queue->len; i++) {
		if (channel == g_ptr_array_index (conn->out_queue, i)) {
			remove_out_queue_entry (conn, i);
			break;
		}
	}
	g_mutex_unlock (conn->out_queue_lock);

	channel->connection = NULL;

	rr_channel_unlock (channel);
	g_object_unref (G_OBJECT (channel));

	return TRUE;
}

/**
 * rr_connection_remove_channel:
 * @connection: a #RRConnection.
 * @channel: a #RRChannel.
 * 
 * Removes @channel from the list of channels started on the @connection.
 **/
void
rr_connection_remove_channel (RRConnection *conn, RRChannel *channel)
{
	gint32 id;

	g_return_if_fail (RR_IS_CONNECTION (conn));
	g_return_if_fail (RR_IS_CHANNEL (channel));

	g_static_rw_lock_writer_lock (&conn->channel_lock);

	id = channel->id;
	remove_helper (NULL, channel, conn);

	g_hash_table_remove (conn->channel_by_id, GINT_TO_POINTER (id));

	g_static_rw_lock_writer_unlock (&conn->channel_lock);
}

static void
close_all_helper (gpointer key, gpointer data, gpointer user_data)
{
	RRChannel *channel = data;

	rr_channel_close_confirmation (channel, RR_BEEP_CODE_ABORTED, 
				       NULL, "disconnect()");
}

void
rr_connection_close_all (RRConnection *conn)
{
	g_return_if_fail (RR_IS_CONNECTION (conn));

	g_static_rw_lock_writer_lock (&conn->channel_lock);

	g_hash_table_foreach (conn->channel_by_id, close_all_helper, conn);
	g_hash_table_foreach_remove (conn->channel_by_id, remove_helper, conn);

	g_static_rw_lock_writer_unlock (&conn->channel_lock);
}

/**
 * rr_connection_enable_input:
 * @connection: A #RRConnection
 * 
 * Enable processing of incoming BEEP frames.
 **/
gboolean
rr_connection_enable_input (RRConnection *connection)
{
	if (RR_CONNECTION_GET_CLASS (connection)->enable_input)
		return RR_CONNECTION_GET_CLASS (connection)->enable_input (connection);
	else
		return FALSE;
}

/**
 * rr_connection_disable_input:
 * @connection: A #RRConnection.
 * 
 * Disable processing of incoming BEEP frames.
 **/
gboolean
rr_connection_disable_input (RRConnection *connection)
{
	if (RR_CONNECTION_GET_CLASS (connection)->disable_input)
		return RR_CONNECTION_GET_CLASS (connection)->disable_input (connection);
	else
		return FALSE;
}

/**
 * rr_connection_enable_output:
 * @connection: A #RRConnection.
 * 
 * Enable transmission of enqueued frames/messages.
 **/
gboolean
rr_connection_enable_output (RRConnection *connection)
{
	connection->output_disabled = FALSE;
	if (RR_CONNECTION_GET_CLASS (connection)->enable_output)
		return RR_CONNECTION_GET_CLASS (connection)->enable_output (connection);
	else
		return FALSE;
}

/**
 * rr_connection_disable_output:
 * @connection: A #RRConnection
 * 
 * Disable transmission of enqueued frames/messages.
 **/
gboolean
rr_connection_disable_output (RRConnection *connection)
{
	connection->output_disabled = TRUE;
	if (RR_CONNECTION_GET_CLASS (connection)->disable_output)
		return RR_CONNECTION_GET_CLASS (connection)->disable_output (connection);
	else
		return FALSE;
}

/**
 * rr_connection_set_profile_registry:
 * @connection: A #RRConnection.
 * @profreg: A #RRProfileRegistry.
 * 
 * Give the @connection a list of profiles to support.
 **/
void
rr_connection_set_profile_registry (RRConnection *connection, 
				    RRProfileRegistry *profreg)
{
	g_return_if_fail (RR_IS_CONNECTION (connection));
	g_return_if_fail (RR_IS_PROFILE_REGISTRY (profreg));
	
	connection->profreg = g_object_ref (G_OBJECT (profreg));
}

static RRFrame *
get_next_frame_from_out_frames (RRConnection *conn)
{
	RRFrame *frame;
	RRChannel *channel = NULL;
	
	frame = RR_FRAME (g_queue_pop_tail (conn->out_frames));
	channel = rr_connection_get_channel (conn, frame->channel_id);
	rr_channel_register_frame (channel, frame);
	
	return frame;
}

static RRChannel *
get_active_channel (RRConnection *connection, gint *idx)
{
	RRChannel *channel;

	*idx = connection->active_item;
	channel = g_ptr_array_index (connection->out_queue, *idx);
	g_assert (RR_IS_CHANNEL (channel));
	connection->active_item++;
	connection->active_item %= connection->out_queue->len;

	return channel;
}

static void
remove_out_queue_entry (RRConnection *connection, gint idx)
{
	g_ptr_array_remove_index (connection->out_queue, idx);
	if (connection->active_item > idx)
		connection->active_item--;
}

RRFrame *
rr_connection_get_next_frame (RRConnection *conn, gsize buffer_size)
{
	GObject *item;
	RRChannel *channel;
	RRFrame *frame;
	gint idx, i;

	g_assert (RR_IS_CONNECTION (conn));

	g_mutex_lock (conn->out_queue_lock);

	/* First look in the out_frames queue, it has higher priority */
	if (g_queue_is_empty (conn->out_frames) == FALSE) {
		frame = get_next_frame_from_out_frames (conn);
		g_mutex_unlock (conn->out_queue_lock);
		return frame;
	}

	/* The out_queue shouldn't be empty */
	if (conn->out_queue->len == 0) {
		g_mutex_unlock (conn->out_queue_lock);
		return NULL;
	}
	
	/* Search for the next "active" channel in line for transmission,
	   skip channels with no transmit window bytes left */
	for (i = 0; i < conn->out_queue->len; i++) {
		channel = get_active_channel (conn, &idx);

		if (channel->window_out == 0 || 
		    channel->starved  == TRUE ||
		    channel->disabled == TRUE)
			continue;

		item = rr_channel_get_active_item (channel);
		if (RR_IS_FRAME (item)) {
			/* Remove the channel entry if it's queue is empty */
			if (rr_channel_remove_active_item (channel))
				remove_out_queue_entry (conn, idx);

			rr_channel_register_frame (channel, RR_FRAME (item));
			g_mutex_unlock (conn->out_queue_lock);
			return RR_FRAME (item);
		} 
		if (RR_IS_MESSAGE (item)) {
			RRMessage *msg = RR_MESSAGE (item);
			gsize max_size;

			/* The window size might be larger then the output
			 * buffer size */
			max_size = MIN (msg->channel->window_out, buffer_size);

			frame = rr_message_get_frame (msg, max_size);
			if (frame == NULL) {
				/* This indicates that rr_message_get_frame
				 * thinks that window_out is to small to send
				 * anything, so we suspend this channel
				 * until the channel gets a lager window_out */
				channel->starved = TRUE;
				continue;
			}
			if (frame->more == FALSE) {
			
				/* Remove the channel entry if it's queue is
				 * empty */
				g_object_unref (G_OBJECT (msg));
				if (rr_channel_remove_active_item (channel))
					remove_out_queue_entry (conn, idx);

			}
			rr_channel_register_frame (channel, frame);
			g_mutex_unlock (conn->out_queue_lock);
			return frame;
		}
	}
	g_mutex_unlock (conn->out_queue_lock);
	return NULL;
}

/**
 * rr_connection_send_frame:
 * @connection: a #RRConnection
 * @frame: a #RRFrame
 * @error: location to return an error of type RR_ERROR or RR_BEEP_ERROR.
 * 
 * Enqueue a frame to be sent on @connection, this function has higher priority
 * then #rr_channel_send_frame and #rr_channel_send_message. So @frame will
 * be sent before frames and messages enqueued by those functions.
 * Note: The frame will be unref:ed after the transmission.
 * 
 * Return value: 
 **/
gboolean
rr_connection_send_frame (RRConnection *connection, RRFrame *frame, 
			  GError **error)
{
	g_assert (RR_IS_CONNECTION (connection));
	g_assert (RR_IS_FRAME (frame));

	g_mutex_lock (connection->out_queue_lock);
	g_queue_push_head (connection->out_frames, RR_FRAME (frame));

	g_mutex_unlock (connection->out_queue_lock);

	if (!connection->output_disabled)
		rr_connection_enable_output (connection);

	return TRUE;
}

/**
 * rr_connection_pending_transmissions_p:
 * @connection: a #RRConnection
 * 
 * Return value: %TRUE if the out queue is not empty.
 **/
gboolean
rr_connection_pending_transmissions_p (RRConnection *connection)
{
	gboolean empty = FALSE;

	g_mutex_lock (connection->out_queue_lock);

	if (connection->out_frames->length == 0 && 
	    connection->out_queue->len == 0)
		empty = TRUE;

	g_mutex_unlock (connection->out_queue_lock);

	return !empty;
}



/* Note: Returns a copy of the list */
GSList *
rr_connection_get_languages (RRConnection *connection)
{
	GSList *langs_copy = NULL;
	GSList *lang;
	g_static_rw_lock_reader_lock (&connection->languages_lock);
	lang = connection->languages;
	while (lang) {

		langs_copy = g_slist_append (langs_copy, g_strdup (lang->data));
		lang = lang->next;
	}
	g_static_rw_lock_reader_unlock (&connection->languages_lock);

	return langs_copy;
}


gchar *
rr_connection_get_languages_str (RRConnection *connection)
{
	GSList *lang;
	GString *langstr;
	gchar *ptr;

	g_static_rw_lock_reader_lock (&connection->languages_lock);
	lang = connection->languages;
	if (lang == NULL) {
		g_static_rw_lock_reader_unlock (&connection->languages_lock);
		return NULL;
	}
	langstr = g_string_new ("");
	while (lang) {
		g_string_append (langstr, (const gchar *)lang->data);
		lang = lang->next;
		if (lang)
			g_string_append (langstr, " ");
	}
	g_static_rw_lock_reader_unlock (&connection->languages_lock);
	ptr = langstr->str;
	g_string_free (langstr, FALSE);

	return ptr;
}


gboolean
rr_connection_language_supported (RRConnection *connection, 
				  const gchar *lang)
{
	gboolean found = FALSE;
	g_return_val_if_fail (RR_IS_CONNECTION (connection), FALSE);
	g_static_rw_lock_reader_lock (&connection->languages_lock);

	if (g_slist_find_custom (connection->languages, lang,
				 (GCompareFunc)strcmp)) {
		found = TRUE;
		g_print ("connection::language_supported: "
			 "Yes, we do indeed speak '%s'.\n", lang);
	}
	g_static_rw_lock_reader_unlock (&connection->languages_lock);

	return found;
}


void
rr_connection_add_language (RRConnection *connection, 
				 const gchar *lang)
{
	g_return_if_fail (RR_IS_CONNECTION (connection));

	if (!rr_connection_language_supported (connection, lang)) {
		g_static_rw_lock_writer_lock (&connection->languages_lock);
		connection->languages = g_slist_prepend (connection->languages,
							 g_strdup (lang));
		g_static_rw_lock_writer_unlock (&connection->languages_lock);
	}		
}


gboolean
rr_connection_remove_language (RRConnection *connection, 
					const gchar *lang)
{
	GSList *item;
	gchar *data;
	gboolean result = FALSE;

	g_return_val_if_fail (RR_IS_CONNECTION (connection), FALSE);

	g_static_rw_lock_writer_lock (&connection->languages_lock);
	item = g_slist_find_custom (connection->languages, lang,
				    (GCompareFunc)strcmp);	
	data = item->data;
	if (item) {
		connection->languages = g_slist_remove (connection->languages,
							data);
		g_free (data);
		result = TRUE;
	}
	g_static_rw_lock_writer_unlock (&connection->languages_lock);

	return result;
}

/**
 * rr_connection_wait_quiescence:
 * @connection: A #RRConnection
 * @error: location to return an error or %NULL
 * 
 * Blocks until all frames and messages in the out queue are sent.
 * 
 * Return value: %TRUE on success, %FALSE on failure.
 **/
gboolean
rr_connection_wait_quiescence (RRConnection *connection, GError **error)
{
	g_mutex_lock (connection->out_queue_lock);

	while (connection->out_frames->length != 0 || 
	       connection->out_queue->len != 0) {
		
		if (connection->connected == FALSE) {
			g_set_error (error, RR_ERROR, RR_ERROR_OTHER,
				     "disconnect ()");
			g_mutex_unlock (connection->out_queue_lock);
			return FALSE;
		}
		g_cond_wait (connection->out_queue_cond, 
			     connection->out_queue_lock);
	}
	g_mutex_unlock (connection->out_queue_lock);

	return TRUE;
}

/**
 * rr_connection_do_quiescence:
 * @connection: A #RRConnection
 * @callback: a callback function
 * @data: data to use as the first argument to the callback function.
 * @user_data: data to use as the second argument to the callback function.
 * 
 * The callback function is called called when the outbound queues are empty.
 **/
void
rr_connection_do_quiescence (RRConnection *connection, GFunc callback,
			       gpointer data, gpointer user_data)
{
	gboolean do_call = FALSE;

	if (connection->connected == FALSE)
		return;

	g_mutex_lock (connection->out_queue_lock);

	if (connection->out_frames->length != 0 || 
	    connection->out_queue->len != 0)
		rr_callback_list_push (&connection->quiescence_cb, 
				       callback, data, user_data);
	else if (callback)
		do_call = TRUE;
	g_mutex_unlock (connection->out_queue_lock);
	if (do_call)
		callback (data, user_data);
}

static void
warn_channel (gpointer key, gpointer data, gpointer user_data)
{
	RRChannel *channel = RR_CHANNEL (data);
	GError **error = (GError **)user_data;

	if (error && *error)
		return;

	if (channel->id != 0) {
		rr_channel_close_indication (RR_CHANNEL (data), 200, NULL, 
					     "Tuning reset", error);
	}
}

/**
 * rr_connection_begin_tuning_reset:
 * @connection: a #RRConnection
 * @error: location to return an error of type RR_ERROR or RR_BEEP_ERROR.
 * 
 * Warns the active channels that a tuning reset is pending by invoking
 * rr_channel_close_indication. If no channel objects then the @connection
 * gets ready to receive a new greeting message.
 * 
 * Return value: %TRUE on success, %FALSE on failure.
 **/
gboolean
rr_connection_begin_tuning_reset (RRConnection *connection, GError **error)
{
	RRManager *manager = RR_MANAGER (connection->manager);

	/* Call close_indication for all open channels */
	g_static_rw_lock_reader_lock (&connection->channel_lock);
	g_hash_table_foreach (connection->channel_by_id, warn_channel,
				     error);
	g_static_rw_lock_reader_unlock (&connection->channel_lock);

	if (error && *error)
		return FALSE;

	/* FIXME: should we send a SEQ frame here */
	rr_manager_set_expects_greeting (manager, TRUE);
	rr_manager_set_greeting_sent (manager, FALSE);

	return TRUE;
}

static gboolean
reset_channel (gpointer key, gpointer data, gpointer user_data)
{
	RRChannel *channel  = RR_CHANNEL (data);

	/* Don't close the manager channel */
	if (channel->id != 0) {
		rr_channel_lock (channel);
		rr_channel_close_confirmation (channel, 200, NULL, 
					       "Tuning reset");
		rr_channel_unlock (channel);
		g_object_unref (G_OBJECT (channel));
		return TRUE;
	}
	else
		return FALSE;
}

static void
do_complete_tuning_reset (gpointer data, gpointer user_data)
{
	RRConnection *connection = RR_CONNECTION (data);
	RRManager *manager;

	manager = RR_MANAGER (connection->manager);
	
	/* Call close_confirmation and unref all channels */
	g_static_rw_lock_writer_lock (&connection->channel_lock);
	g_hash_table_foreach_remove (connection->channel_by_id, reset_channel,
				     NULL);
	
	g_static_rw_lock_writer_unlock (&connection->channel_lock);
	
	rr_manager_send_greeting (manager, NULL);
}

/**
 * rr_connection_complete_tuning_reset:
 * @connection: the #RRConnection.
 * @channel: the channel that is tuning.
 * 
 * Closes all open channels on @connection. Sends a new greeting and waits 
 * until a greeting from the other peer is received.
 * 
 **/
void
rr_connection_complete_tuning_reset (RRConnection *connection, 
				     RRChannel *channel)
{
	g_object_ref (G_OBJECT (channel));
	rr_connection_do_quiescence (connection, do_complete_tuning_reset,
				       connection, NULL);
	rr_main_work_pool_push (0, (GFunc)g_object_unref, channel, NULL);
}

/**
 * rr_connection_set_peer_profiles:
 * @connection: The #RRConnection.
 * @list: a list of supported profiles.
 * 
 * Store a list of peer supported profiles in the @connection object.
 **/
void
rr_connection_set_peer_profiles (RRConnection *connection, GSList *list)
{
	g_slist_foreach (connection->peer_profiles, (GFunc)g_free, NULL);
	g_slist_free (connection->peer_profiles);
	
	connection->peer_profiles = list;
}

/**
 * rr_connection_get_peer_profiles:
 * @connection: The #RRConnection
 * 
 * Returns a list of profile uri's that the remote peer supports.
 * 
 * Return value: A const pointer to the profile list.
 **/
const GSList *
rr_connection_get_peer_profiles (RRConnection *connection)
{
	return connection->peer_profiles;
}

/**
 * rr_connection_peer_supports_profile:
 * @connection: The #RRConnection.
 * @profile: A profile type.
 * 
 * Determines if the remote peer supports the @profile.
 * 
 * Return value: TRUE if the @profile is supported else FALSE.
 **/
gboolean
rr_connection_peer_supports_profile (RRConnection *connection,
				     GType profile) 
{
	const gchar *uri;

	g_return_val_if_fail (g_type_is_a (profile, RR_TYPE_CHANNEL), FALSE);

	uri = rr_channel_get_uri (profile);
	g_return_val_if_fail (uri != NULL, FALSE);
	
	return g_slist_find_custom (connection->peer_profiles, uri,
				    (GCompareFunc)strcmp) != NULL;
}

/**
 * rr_connection_get_server_name:
 * @connection: The #RRConnection.
 * 
 * Returns the "serverName" attribute for the first successful "start" element
 * received by a BEEP peer is meaningful for the duration of the BEEP
 * session.
 * 
 * Return value: the %serverName.
 **/
const gchar *
rr_connection_get_server_name (RRConnection *connection)
{
	g_return_val_if_fail (RR_IS_CONNECTION (connection), NULL);

	return connection->server_name;
}

/**
 * rr_connection_set_server_name:
 * @connection: The #RRConnection.
 * @server_name: a %serverName.
 * 
 * Sets the serverName that is received from the BEEP peer.
 **/
void
rr_connection_set_server_name (RRConnection *connection, const gchar *server_name)
{
	g_return_if_fail (RR_IS_CONNECTION (connection));

	g_free (connection->server_name);
	if (server_name)
		connection->server_name = g_strdup (server_name);
}

/**
 * rr_connection_disconnect:
 * @connection: A #RRConnection
 * @error: @error: location to return an error of type G_IO_ERROR, RR_ERROR or 
 * RR_BEEP_ERROR.
 * 
 * Attempts to close the connection.
 * NOTE: connection will be unref:ed.
 * 
 * Return value: %TRUE on success, %FALSE on failure.
 **/
gboolean
rr_connection_disconnect (RRConnection *connection, GError **error)
{
	return RR_CONNECTION_GET_CLASS (connection)->disconnect (connection, 
								 error);
}

/**
 * rr_connection_get_manager:
 * @connection: A #RRConnection.
 * 
 * Returns the manager channel (channel 0) for the given @connection.
 * 
 * Return value: the #RRManager channel associated with this connection.
 **/
RRManager *
rr_connection_get_manager (RRConnection *connection)
{
	g_return_val_if_fail (RR_IS_CONNECTION (connection), NULL);

	return RR_MANAGER (connection->manager);
}


/**
 * rr_connection_start:
 * @connection: The #RRConnection.
 * @server_name: # The server_name parameter or NULL.
 * @profile_type: The profile type to start.
 * @config_data: Profile specific config data or NULL.
 * @error: location to return an error of type RR_ERROR or RR_BEEP_ERROR
 * 
 * Tries to create a new channel of the provided profile type.
 * 
 * Return value: A #RRProfile on success, %NULL on failure.
 **/
RRChannel *
rr_connection_start (RRConnection *connection, const gchar *server_name,
		  GType profile_type, gpointer config_data, GError **error)
{
	g_return_val_if_fail (RR_IS_CONNECTION (connection), NULL);
	g_return_val_if_fail (RR_IS_MANAGER (connection->manager), NULL);

	return rr_manager_start_multi (connection->manager, server_name, error, 
				       profile_type, config_data, 
				       G_TYPE_INVALID);
}

/**
 * rr_connection_start_multi:
 * @connection: The #RRConnection.
 * @server_name: The server_name parameter or NULL.
 * @error: location to return an error of type RR_ERROR or RR_BEEP_ERROR
 * @...: A list of Profile type + config_data pairs and a G_TYPE_INVALID at the
 * end.
 * 
 * Tries to create a new channel with one of the provided profiles.
 * 
 * Return value: A #RRProfile on success, %NULL on failure.
 **/
RRChannel *
rr_connection_start_multi (RRConnection *connection, const gchar *server_name,
			   GError **error, ...)
{
	RRChannel *ret;
	va_list args;

	g_return_val_if_fail (RR_IS_CONNECTION (connection), NULL);
	g_return_val_if_fail (RR_IS_MANAGER (connection->manager), NULL);

	va_start (args, error);
	ret = rr_manager_start_multiv (connection->manager, server_name, error, args);
	va_end (args);

	return ret;
}


syntax highlighted by Code2HTML, v. 0.9.1