/*-
 * $Id: rr-frame.c,v 1.24 2002/12/12 14:59:52 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 <stdio.h>
#include <string.h>

#include <stdio.h>
#include <string.h>

static GObjectClass *parent_class = NULL;

static gint build (RRFrame *frame, gchar *buffer);
static gint parse (RRFrame *frame, const gchar *buffer, 
		   const gchar *next_line, gint len, GError **error);

static char *type_string[] = { "UNKNOWN", "MSG", "RPY", "ERR", "ANS", "NUL"};

static void
finalize (GObject *object)
{
	RRFrame *frame = (RRFrame *)object;

	if (frame->payload && frame->should_free)
		g_free (frame->payload);

	/* If the frame holds a reference of a message
	 * release it now */
	if (frame->msg)
		g_object_unref (frame->msg);
	if (frame->part)
		rr_mime_part_free (frame->part);

	parent_class->finalize (object);
}

static void
rr_frame_init (GObject *object)
{
}

static void
rr_frame_class_init (GObjectClass *klass)
{
	RRFrameClass *frame_class = (RRFrameClass *)klass;

	frame_class->build = build;
	frame_class->parse = parse;

	klass->finalize = finalize;

	parent_class = g_type_class_peek_parent (klass);
}

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

	if (!rr_type) {
		static GTypeInfo type_info = {
			sizeof (RRFrameClass),
			NULL,
			NULL,
			(GClassInitFunc) rr_frame_class_init,
			NULL,
			NULL,
			sizeof (RRFrame),
			16,
			(GInstanceInitFunc) rr_frame_init
		};
		rr_type = g_type_register_static (G_TYPE_OBJECT, "RRFrame", 
						  &type_info, 0);
	}
	return rr_type;
}

gint
rr_frame_build (RRFrame *frame, gchar *buffer)
{
	return RR_FRAME_GET_CLASS (frame)->build (frame, buffer);
}

gint
rr_frame_parse (RRFrame *frame, const gchar *buffer, 
		const gchar *body, gint len, GError **error)
{
	return RR_FRAME_GET_CLASS (frame)->parse (frame, buffer, body, 
						  len, error);
}

void rr_frame_reference_message (RRFrame *frame, RRMessage *msg)
{
	g_return_if_fail (RR_IS_FRAME (frame));
	g_return_if_fail (RR_IS_MESSAGE (msg));

	if (frame->msg)
		g_object_unref (G_OBJECT (frame->msg));

	if (msg)
		frame->msg = g_object_ref (G_OBJECT (msg));
	else
		frame->msg = NULL;
}

RRFrame *
rr_frame_new (RRFrameType type, gint32 channel_id, gboolean more, 
	      gint32 msgno, gint32 size, gint32 ansno, 
	      gchar *payload, gboolean should_free)
{
	RRFrame *frame;

	frame = g_object_new (RR_TYPE_FRAME, NULL);

	frame->channel_id  = channel_id;
	frame->type        = type;
	frame->more        = more;
	frame->payload     = payload;
	frame->msgno       = msgno;
	frame->size        = size;
	frame->ansno       = ansno;
	frame->should_free = should_free;

	return frame;
}

static gint
build (RRFrame *frame, gchar *buffer)
{
	gint len;

	switch (frame->type) {
	case RR_FRAME_TYPE_ANS:
		sprintf (buffer, "%s %d %d %c %d %u %d\r\n",
			 type_string[frame->type],
			 frame->channel_id,
			 frame->msgno,
			 frame->more ? '*' : '.',
			 frame->seqno,
			 frame->size,
			 frame->ansno);
		break;
	case RR_FRAME_TYPE_MSG:
	case RR_FRAME_TYPE_RPY:
	case RR_FRAME_TYPE_ERR:
	case RR_FRAME_TYPE_NUL:
		sprintf (buffer, "%s %d %d %c %u %d\r\n",
			 type_string[frame->type],
			 frame->channel_id,
			 frame->msgno,
			 frame->more ? '*' : '.',
			 frame->seqno,
			 frame->size);
		break;
	default:
		g_assert_not_reached ();
	}
	len = strlen (buffer);

	memcpy (buffer + len, frame->payload, frame->size);
	len += frame->size;
	strcpy (buffer + len, "END\r\n");
	len += 5;

	return len;
}

static RRFrameType
get_frame_type (const gchar *buffer)
{
	char str[4];

	g_return_val_if_fail (buffer != NULL, RR_FRAME_TYPE_UNKNOWN);

	if (sscanf (buffer, "%3s", str) != 1) {
		g_print ("frame_divider, parse error1\n");
		return RR_FRAME_TYPE_UNKNOWN;
	}
	if (!strcmp ("MSG", str))
		return RR_FRAME_TYPE_MSG;
	if (!strcmp ("RPY", str))
		return RR_FRAME_TYPE_RPY;
	if (!strcmp ("ERR", str))
		return RR_FRAME_TYPE_ERR;
	if (!strcmp ("ANS", str))
		return RR_FRAME_TYPE_ANS;
	if (!strcmp ("NUL", str))
		return RR_FRAME_TYPE_NUL;

	return RR_FRAME_TYPE_UNKNOWN;
}

static gint
parse (RRFrame *frame, const gchar *buffer, 
       const gchar *body, gint len, GError **error)
{
	gint header_len;
	gchar more;
	
	g_return_val_if_fail (RR_IS_FRAME (frame), 0);

	header_len = body - buffer;

	frame->type = get_frame_type (buffer);

	if (sscanf (buffer, "%*s %d %d %c %u %d %d", 
		    &frame->channel_id, &frame->msgno, &more, 
		    &frame->seqno, &frame->size, &frame->ansno) < 5) {
		
		g_set_error (error, RR_BEEP_ERROR, RR_BEEP_CODE_SYNTAX_ERROR,
			     "frame header parse error");
		return -1;
	}
	if (frame->size < 0) {

		g_set_error (error, RR_BEEP_ERROR, RR_BEEP_CODE_SYNTAX_ERROR,
			     "frame header parse error");
		return -1;
	}
	frame->more = more == '*' ? TRUE : FALSE;

	/* Do we need more data? */
	if (len - header_len - 5 < frame->size) { /* 5 = strlen ("END\r\n") */
		
		return 0;
	}
	if (strncmp (body + frame->size, "END", 3) != 0) {

		g_set_error (error, RR_BEEP_ERROR, RR_BEEP_CODE_SYNTAX_ERROR,
			     "frame syntax error");
		return -1;
	}

	frame->payload = g_new (guint8, frame->size + 1);
	frame->should_free = TRUE;
	memcpy (frame->payload, body, frame->size);
	frame->payload[frame->size] = 0;
	
	return header_len + frame->size + 5;
}

RRFrame *
rr_frame_aggregate (GSList **frame_list, RRFrame *frame)
{
	/* Return immediately if message consists only of one frame */
	if ((NULL == *frame_list) && !frame->more) {
		return g_object_ref (G_OBJECT (frame));
	}

	*frame_list = g_slist_append (*frame_list, 
				      g_object_ref (G_OBJECT (frame)));
	
	if (!frame->more) {
		/* If this is the last frame, create one new frame
		 * with all the payload */
		RRFrame *new_frame;
		GSList *list;
		gchar *payload, *ptr;
		gint size = 0;
			
		/* Calculate the total size */
		list = *frame_list;
		while (list) {
			size += RR_FRAME (list->data)->size;
			list = list->next;
		}
		payload = ptr = g_new (guint8, size + 1);
		
		/* Aggregate the frames */
		list = *frame_list;
		while (list) {
			RRFrame *frame = RR_FRAME (list->data);
			memcpy (ptr, frame->payload, frame->size);
			ptr += frame->size;
			list = list->next;
		}
		payload[size] = 0;
		/* Allocate _one_ large frame that will contain all the data */
		new_frame = rr_frame_new (frame->type, frame->channel_id,
					  FALSE, frame->msgno, size, 0, 
					  payload, TRUE);
		
		rr_frame_aggregator_free (frame_list);

		return new_frame;
	}

	return NULL;
}


void
rr_frame_aggregator_free (GSList **list)
{
	/* Free the other frames */
	g_slist_foreach (*list, (GFunc)g_object_unref, NULL);
	g_slist_free (*list);
	*list = NULL;
}

/**
 * rr_frame_parse_mime:
 * @frame: A #RRFrame.
 * 
 * This function attempts to parse the frame payload as a mime message.
 * 
 * Note: The RRMimePart object returned by this function will be freed
 * by RoadRunner when the #RRFrame object is destroyed.
 * 
 * Return value: A pointer to a #RRMimePart or %NULL if parse failed.
 **/
RRMimePart *
rr_frame_parse_mime (RRFrame *frame)
{
	g_return_val_if_fail (RR_IS_FRAME (frame), NULL);
	g_return_val_if_fail (frame->payload != NULL, NULL);

	if (frame->part == NULL) {
		frame->part = rr_mime_parse (frame->payload, frame->size);
	}
	return frame->part;
}

/**
 * rr_frame_mime_get_body:
 * @frame: A #RRFrame
 * 
 * Returns a pointer to the body of the mime body. If the frame
 * contains a multipart mime message a pointer to the body of the 
 * first non-multipart part.
 * 
 * Return value: A pointer to the mime body or %NULL.
 **/
const gchar *
rr_frame_mime_get_body (RRFrame *frame)
{
	RRMimePart *part, *first;

	g_return_val_if_fail (RR_IS_FRAME (frame), NULL);

	part = rr_frame_parse_mime (frame);
	if (part) {
		first = rr_mime_part_get_first (part);
		if (first) {
			return rr_mime_part_get_body (first);
		}
	}
	return NULL;
}


/**
 * rr_frame_mime_get_body_size:
 * @frame: 
 * 
 * Returns the length of the body of the mime body. If the frame
 * contains a multipart mime message the length of the body of the 
 * first non-multipart part.
 * 
 * Return value: The length of the mime body returned by rr_frame_mime_get_body.
 **/
gint32
rr_frame_mime_get_body_size (RRFrame *frame)
{
	RRMimePart *part, *first;

	g_return_val_if_fail (RR_IS_FRAME (frame), 0);

	part = rr_frame_parse_mime (frame);
	if (part) {
		first = rr_mime_part_get_first (part);
		if (first) {
			return rr_mime_part_get_body_len (first);
		}
	}
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1