/*-
 * $Id: rr-message-start.c,v 1.22 2002/11/08 12:35:28 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>

#include <libxml/xmlmemory.h>
#include <libxml/parser.h>

#include <stdio.h>

static GObjectClass *parent_class = NULL;
static void finalize (GObject *object);
static gboolean process_frame (RRMessage *message, RRFrame *frame, 
				  GError **error);

static RRFrame *get_frame (RRMessage *stat, gsize max_size);

static void
free_item (gpointer data, gpointer user_data)
{
	RRStartItem *item = (RRStartItem *)data;
	g_free (item->piggyback);
	g_free (item);
}

static void
finalize (GObject *object)
{
	RRMessageStart *start = (RRMessageStart *)object;
	
	g_mutex_free (start->done_mutex);
	g_cond_free (start->done_cond);

	g_slist_foreach (start->in_list, free_item, 0);
	g_slist_foreach (start->out_list, (GFunc)g_object_unref, 0);
	g_slist_free (start->in_list);
	g_slist_free (start->out_list);

	g_free (start->server_name);
	if (start->client_init_error)
		g_error_free (start->client_init_error);

	parent_class->finalize (object);
}

static void
rr_message_start_init (GObject *object)
{
	RRMessageStart *start = (RRMessageStart *)object;
	RRMessage *msg = (RRMessage *)object;

	start->done_mutex = g_mutex_new ();
	start->done_cond  = g_cond_new ();
	msg->type = RR_FRAME_TYPE_MSG;
}

static void
rr_message_start_class_init (GObjectClass *klass)
{
	RRMessageClass *msg_class = (RRMessageClass *)klass;

	klass->finalize = finalize;
	msg_class->get_frame = get_frame;
	msg_class->process_frame = process_frame;

	parent_class = g_type_class_peek_parent (klass);
}

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

	if (!rr_type) {
		static GTypeInfo type_info = {
			sizeof (RRMessageStartClass),
			NULL,
			NULL,
			(GClassInitFunc) rr_message_start_class_init,
			NULL,
			NULL,
			sizeof (RRMessageStart),
			16,
			(GInstanceInitFunc) rr_message_start_init
		};
		rr_type = g_type_register_static (RR_TYPE_MESSAGE, 
						  "RRMessageStart", 
						  &type_info, 0);
	}
	return rr_type;
}


RRMessageStart *
rr_message_start_new (gint number, const gchar *server_name)
{
	RRMessageStart *start;

	start = g_object_new (RR_TYPE_MESSAGE_START, NULL);

	start->number = number;
	if (server_name)
		start->server_name = g_strdup (server_name);

	return start;
}

GSList *
rr_message_start_get_channel_list (RRMessageStart *start)
{
	g_return_val_if_fail (RR_IS_MESSAGE_START (start), NULL);

	return start->in_list;
}

void
rr_message_start_add_channel (RRMessageStart *start,
			      RRConnection *conn,
			      GType type,
			      gpointer config_data)
{
	RRChannel *channel;
	GError *error = NULL;
	gpointer global_config = NULL;

	channel = g_object_new (type, NULL);
	channel->id = start->number;
	channel->instance_config = config_data;

	if (conn->profreg) {
		global_config = rr_profile_registry_get_global_config (conn->profreg, type);
	}

	channel->global_config = global_config;

	rr_channel_set_connection (channel, conn);
	/* Give the channel a chance to generate a piggyback */
	if (!rr_channel_client_init (channel, &error)) {
		
		if (error) {
			if (start->client_init_error)
				g_error_free (start->client_init_error);
			start->client_init_error = error;
		}
		rr_debug1 ("message_start::add_channel "
			   "rr_channel_client_init failed: %s, %s", 
			   G_OBJECT_TYPE_NAME (G_OBJECT (channel)),
			   error->message);		
		g_object_unref (G_OBJECT (channel));
	}
	else
		start->out_list = g_slist_append (start->out_list, channel);
}

static void
append_channel (gpointer data, gpointer user_data)
{
	RRChannel *channel = data;
	GString *str = (GString *)user_data;
	const gchar *uri, *piggyback;

	g_return_if_fail (RR_IS_CHANNEL (channel));
	
	uri = rr_channel_get_uri (G_OBJECT_TYPE (G_OBJECT (channel)));
	piggyback = rr_channel_get_piggyback (channel);

	if (piggyback) {
		g_string_append_printf (str, 
					"   <profile uri='%s'>\r\n"
					"      <![CDATA[%s]]>\r\n"
					"   </profile>\r\n",
					uri, piggyback);
	}
	else
		g_string_append_printf (str, "   <profile uri='%s' />\r\n", uri);
}

static gboolean
process_frame (RRMessage *message, RRFrame *frame, GError **error)
{
	RRMessageStart *start;
	RRConnection *conn;
	xmlDocPtr doc;
	xmlNodePtr node;
	gboolean ret;
	gchar *server_name;
	xmlChar *str;
	const gchar *data;
	gint len;

	g_return_val_if_fail (RR_IS_MESSAGE_START (message), FALSE);
	g_return_val_if_fail (RR_IS_FRAME (frame), FALSE);
	g_return_val_if_fail (RR_IS_CHANNEL (message->channel), FALSE);
	g_return_val_if_fail (RR_IS_CONNECTION (message->channel->connection), 
			      FALSE);

	conn = message->channel->connection;
	start = RR_MESSAGE_START (message);

	data = rr_frame_mime_get_body (frame);
	len  = rr_frame_mime_get_body_size (frame);

	doc = xmlParseMemory (data, len);
	if (doc == NULL) {
		g_set_error (error, RR_ERROR, RR_BEEP_CODE_SYNTAX_ERROR, "Invalid start message.");
		return FALSE;
	}

	node = xmlDocGetRootElement (doc);

	if (strcmp (node->name, "start") != 0)
		goto err;

	if ((str = xmlGetProp (node, "number")) == NULL)
		goto err;
	
	start->number = atoi (str);
	xmlFree (str);

	if ((server_name = xmlGetProp (node, "serverName"))) {

		start->server_name = g_strdup (server_name);
		xmlFree (server_name);
	}
	
	node = node->children;

	while (node) {
		xmlChar *uri, *piggyback = NULL;
		GType type;
		RRStartItem *item;

		/* Ignore none element nodes */
		while (node && node->type != XML_ELEMENT_NODE)
			node = node->next;
		if (node == NULL)
			break;

		if (strcmp (node->name, "profile") != 0)
			goto err;

		if ((uri = xmlGetProp (node, "uri")) == NULL)
			goto err;

		type = rr_profile_registry_lookup_by_uri (conn->profreg, uri);
		xmlFree (uri);
		if (type == G_TYPE_INVALID) {
			node = node->next;
			continue;
		}
		/* Extract the piggyback data if it exists */
		if (node->children) {
			xmlNode *child, *cdata_node = NULL;

			child = node->children;
			while (child) {
				if (child->type == XML_CDATA_SECTION_NODE &&
				    child->content) {
					cdata_node = child;
					break;
				}
				child = child->next;
			}
			if (cdata_node && child->content)
				piggyback = cdata_node->content;
			else if (node->children->type == XML_TEXT_NODE &&
				 node->children->content)
				piggyback = node->children->content;
		}
		item = g_new (RRStartItem, 1);
		item->type = type;
		item->piggyback = g_strdup (piggyback);
		start->in_list = g_slist_append (start->in_list, item);
		node = node->next;
	}
	ret = TRUE;
	goto end;
 err:
	g_set_error (error, RR_ERROR, RR_BEEP_CODE_PARAM_ERROR, 
		     RR_GERROR_DEFAULT_MESSAGE); 
	ret = FALSE;
 end:
	xmlFreeDoc (doc);

	return ret;
}

static RRFrame *
get_frame (RRMessage *message, gsize max_size)
{
	RRMessageStart *start = RR_MESSAGE_START(message);
	RRFrame *frame;
	GString *str = g_string_new (RR_BEEP_MIME_HEADER);

	g_assert (RR_IS_MESSAGE (start));

	if (start->server_name)
		g_string_append_printf (str, "<start number='%d' serverName='%s'>\r\n", 
					start->number, start->server_name);
	else
		g_string_append_printf (str, "<start number='%d'>\r\n", 
					start->number);

	g_slist_foreach (start->out_list, append_channel, str);

	g_string_append (str, "</start>\r\n");

	if (str->len > max_size) {

		g_string_free (str, TRUE);
		return NULL;
	}

	frame = rr_frame_new (RR_FRAME_TYPE_MSG, 
			      message->channel->id, FALSE, message->msgno,
			      str->len, 0, str->str, TRUE);

	g_string_free (str, FALSE);

	return frame;
}

void
rr_message_start_done (RRMessageStart *start, RRChannel *channel,
		       GError *error)
{
	g_return_if_fail (RR_IS_MESSAGE_START (start));

	g_mutex_lock (start->done_mutex);
	start->done_channel = channel;
	if (error)
		start->done_error = g_error_copy (error);
	start->done = TRUE;
	g_cond_broadcast (start->done_cond);
	g_mutex_unlock (start->done_mutex);
}

RRChannel *
rr_message_start_wait_for_reply (RRMessageStart *start, GError **error)
{
	g_return_val_if_fail (RR_IS_MESSAGE_START (start), NULL);

	g_mutex_lock (start->done_mutex);
	while (!start->done)
		g_cond_wait (start->done_cond, start->done_mutex);
	g_mutex_unlock (start->done_mutex);

	if (start->done_error) {
		g_propagate_error (error, start->done_error);
		start->done_error = NULL;
	}

	return start->done_channel;
}

/**
 * rr_message_start_get_server_name:
 * @start: A #RRMessageStart.
 * 
 * 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_message_start_get_server_name (RRMessageStart *start)
{
	g_return_val_if_fail (RR_IS_MESSAGE_START (start), NULL);

	return start->server_name;
}

gboolean
rr_message_start_empty_request_p (RRMessageStart *start)
{
	g_return_val_if_fail (RR_IS_MESSAGE_START (start), TRUE);

	return start->out_list == NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1