/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008  Michael Bell <michael.bell@opensync.org>
 *
 * This library 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.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *
 */

#include "syncml.h"

#include "syncml_internals.h"
#include "sml_error_internals.h"
#include "sml_parse_internals.h"
#include "sml_command_internals.h"
#include "sml_elements_internals.h"
#include "sml_notification_internals.h"

#include "parser/sml_xml_assm.h"
#include "parser/sml_xml_parse.h"
#include "parser/sml_wbxml.h"

/**
 * @defgroup SmlParser SyncML Parser
 * @ingroup PublicLowLevelAPI
 * @brief Interfaces to parse syncml messages
 * 
 */
/*@{*/

/** @brief Creates a new parser
 * 
 * @param type The type of the parser
 * @param error A pointer to an error struct
 * @return The new parser or NULL in the case of an error
 * 
 */
SmlParser *smlParserNew(SmlMimeType type, unsigned int limit, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %p)", __func__, type, limit, error);
	CHECK_ERROR_REF
	
	SmlParser *parser = smlTryMalloc0(sizeof(SmlParser), error);
	if (!parser)
		goto error;
	
	parser->type = type;
	parser->limit = limit;
	
	switch (type) {
		case SML_MIMETYPE_XML:
			if (!(parser->parser_userdata = smlXmlParserNew(&(parser->functions), error)))
				goto error_free_parser;
			break;
		case SML_MIMETYPE_SAN:
			/* SANs will be converted to normal SyncML 1.1 notifications */
			if (!(parser->parser_userdata = smlXmlParserNew(&(parser->functions), error)))
				goto error_free_parser;
			break;
		case SML_MIMETYPE_WBXML:
#ifdef ENABLE_WBXML
			if (!(parser->parser_userdata = smlWbxmlParserNew(&(parser->functions), error)))
				goto error_free_parser;
			break;
#else
			smlErrorSet(error, SML_ERROR_GENERIC, "Wbxml not enabled in this build");
			goto error_free_parser;
#endif
		default:
			smlErrorSet(error, SML_ERROR_GENERIC, "Unknown parser type");
			goto error_free_parser;
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, parser);
	return parser;

error_free_parser:
	smlSafeFree((gpointer *)&parser);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

void smlParserSetManager(SmlParser *parser, SmlManager *manager)
{
	smlAssert(parser);
	smlAssert(manager);
	parser->manager = manager;
}

/** @brief Frees a parser
 * 
 * @param parser The parser to free
 * 
 */
void smlParserFree(SmlParser *parser)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, parser);
	smlAssert(parser);
	
	if (parser->functions.free)
		parser->functions.free(parser->parser_userdata);
	
	smlSafeFree((gpointer *)&parser);

	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Starts the parser on a given data buffer
 * 
 * This function will parse the data up to the SyncHdr tag
 * 
 * @param parser The parser
 * @param data The data that should be parsed
 * @param size The size of the data
 * @param error A pointer to an error struct
 * @returns TRUE if the parser started successfully, FALSE otherwise
 * 
 */
SmlBool smlParserStart(SmlParser *parser, const char *data, unsigned int size, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p)", __func__, parser, data, size, error);
	CHECK_ERROR_REF
	smlAssert(parser);
	smlAssert(data);
	smlAssert(size);
	smlAssert(parser->functions.start);
	smlAssertMsg(parser->type != SML_MIMETYPE_SAN || parser->manager,
		SML_ASSERT_MSG_SAN_PARSE_REQUIRES_MANAGER);
	
	if (parser->limit && size > parser->limit) {
		smlErrorSet(error, SML_ERROR_GENERIC, "Input data too large");
		goto error;
	}

	if (parser->type == SML_MIMETYPE_SAN) {
		/* convert OMA DS 1.2 SAN into an SyncML 1.1 notification */
		smlTrace(TRACE_INTERNAL, "%s - detected an OMA DS 1.2 SAN", __func__);

		/* parse the SAN */
		SmlNotification *san = smlNotificationParse(data, size, error);
		if (!san)
			goto error;

		/* fix 1.2 SAN for use with SyncML 1.1 */
		smlNotificationSetManager(san, parser->manager);
		san->type = SML_MIMETYPE_XML;
		san->sessionType = SML_SESSION_TYPE_CLIENT;
		san->target = smlLocationNew("/", NULL, error);
		if (!san->target)
			goto error;

		/* assemble a SyncML 1.1 notification */
		char *xmlData = NULL;
		unsigned int xmlSize = 0;
		if (!smlNotificationAssemble11(san, &xmlData, &xmlSize, SML_VERSION_12, error))
		{
			smlNotificationFree(san);
			goto error;
		}
		smlTrace(TRACE_INTERNAL,
			"%s - converted OMA DS 1.2 SAN to SyncML 1.1 server alerted sync - %s",
			__func__, VA_STRING(xmlData));

		/* cleanup notification */
		smlNotificationFree(san);
		san = NULL;

		/* run the parser on the converted SAN */
		if (!parser->functions.start(parser->parser_userdata, xmlData, xmlSize, error)) {
			smlSafeCFree(&xmlData);
			goto error;
		}
		smlSafeCFree(&xmlData);
	} else {
		/* run the parser directly on the data */
		if (!parser->functions.start(parser->parser_userdata, data, size, error))
			goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Parses the SyncHdr
 * 
 * This function will parse the data inside the SyncHdr Tag. Possible
 * error case are:
 * 
 * - No header was found
 * - The header is missing necessary information
 * - OOM
 * 
 * @param parser The parser
 * @param header The return location of the header
 * @param error A pointer to an error struct
 * @returns TRUE if the parser retrieved the header successfully, FALSE otherwise
 * 
 */
SmlBool smlParserGetHeader(SmlParser *parser, SmlHeader **header, SmlCred **cred, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, parser, header, cred, error);
	CHECK_ERROR_REF
	smlAssert(parser);
	smlAssert(header);
	smlAssert(cred);
	smlAssert(parser->functions.get_header);
	
	if (!parser->functions.get_header(parser->parser_userdata, header, cred, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Parses the next command
 * 
 * This function will parse the next command found in the syncbody. Note that if you
 * call this function too early (before the synchdr is parsed this function will return an
 * error.) The correct order is: getHeader, getStatus until NULL, then getCommand
 * until NULL and then End to end parsing.
 * 
 * @param parser The parser
 * @param cmd The return location of the command
 * @param error A pointer to an error struct
 * @returns TRUE if the parser retrieved the next command successfully or there were no more
 * commands available (in which case *cmd is NULL). FALSE in the case of an error
 * 
 */
SmlParserResult smlParserGetCommand(SmlParser *parser, SmlCommand **cmd, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, parser, cmd, error);
	CHECK_ERROR_REF
	smlAssert(parser);
	smlAssert(cmd);
	smlAssert(parser->functions.get_cmd);
	SmlParserResult result = SML_PARSER_RESULT_ERROR;
	
	if (!(result = parser->functions.get_cmd(parser->parser_userdata, cmd, error)))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s: %i", __func__, result);
	return result;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return SML_PARSER_RESULT_ERROR;
}

/** @brief Parses the next status
 * 
 * This function will parse the next status found in the syncbody. Note that if you
 * call this function too early (before the synchdr is parsed this function will return an
 * error.) The correct order is: getHeader, getStatus until NULL, then getCommand
 * until NULL and then End to end parsing.
 * 
 * @param parser The parser
 * @param status The return location of the status
 * @param error A pointer to an error struct
 * @returns TRUE if the parser retrieved the next status successfully or there were no more
 * statuses available (in which case *status is NULL). FALSE in the case of an error
 * 
 */
SmlBool smlParserGetStatus(SmlParser *parser, SmlStatus **status, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, parser, status, error);
	CHECK_ERROR_REF
	smlAssert(parser);
	smlAssert(status);
	smlAssert(parser->functions.get_status);
	
	if (!parser->functions.get_status(parser->parser_userdata, status, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Ends the parsing
 * 
 * This function will check that all needed closing tags are in place
 * 
 * @param parser The parser
 * @param final Return location to see if this was the final message
 * @param end Return location to see if this message was a end message (only statuses)
 * @param error A pointer to an error struct
 * @returns TRUE if the parser ended successfully, FALSE otherwise
 * 
 */
SmlBool smlParserEnd(SmlParser *parser, SmlBool *final, SmlBool *end, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, parser, final, end, error);
	CHECK_ERROR_REF
	smlAssert(parser);
	smlAssert(parser->functions.end);
	
	if (!parser->functions.end(parser->parser_userdata, final, end, error))
		goto error;
	
	smlTrace(TRACE_INTERNAL, "%s: Final %i, End %i", __func__, final ? *final : -1, end ? *end : -1);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/*@}*/

/**
 * @defgroup SmlAssembler SyncML Assembler
 * @ingroup PublicLowLevelAPI
 * @brief Interfaces to assemble syncml messages
 * 
 */
/*@{*/

/** @brief Creates a new assembler
 * 
 * @param type The type of the assembler
 * @param limit The maximum size that can be assembled
 * @param error A pointer to an error struct
 * @return The new assembler or NULL in the case of an error
 * 
 */
SmlAssembler *smlAssemblerNew(SmlMimeType type, unsigned int limit, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %p)", __func__, type, limit, error);
	CHECK_ERROR_REF
	
	SmlAssembler *assm = smlTryMalloc0(sizeof(SmlAssembler), error);
	if (!assm)
		goto error;
	
	assm->type = type;
	assm->remoteMaxMsgSize = limit;
	assm->empty = TRUE;
	assm->remoteMaxObjSize = 0;
	
	switch (type) {
		case SML_MIMETYPE_XML:
			if (!(assm->assm_userdata = smlXmlAssemblerNew(assm, &(assm->functions), error)))
				goto error_free_assm;
			break;
		case SML_MIMETYPE_WBXML:
#ifdef ENABLE_WBXML
			if (!(assm->assm_userdata = smlWbxmlAssemblerNew(assm, &(assm->functions), error)))
				goto error_free_assm;
			break;
#else
			smlErrorSet(error, SML_ERROR_GENERIC, "Wbxml not enabled in this build");
			goto error_free_assm;
#endif
		default:
			smlErrorSet(error, SML_ERROR_GENERIC, "Unknown assembler type");
			goto error_free_assm;
	}
	
	assm->options = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, assm);
	return assm;
	
error_free_assm:
	smlSafeFree((gpointer *)&assm);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

/** @brief Frees a assembler
 * 
 * @param assm The assembler to free
 * 
 */
void smlAssemblerFree(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);
	
	if (assm->functions.free)
		assm->functions.free(assm->assm_userdata);
	
	g_hash_table_destroy(assm->options);
	
	smlSafeFree((gpointer *)&assm);

	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Starts a new message
 * 
 * Note that the size already includes what will be needed to close the opened tags
 * (like SyncML) and already includes the remaining needed tags (like Final)
 * 
 * @param assm The assembler
 * @param session The session for which to start a message
 * @param error A pointer to an error struct
 * @returns TRUE if the package container was assembled successfully, FALSE otherwise
 * 
 */
SmlBool smlAssemblerStart(SmlAssembler *assm, SmlSession *session, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, assm, session, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(session);
	smlAssert(assm->functions.start);
	
	if (!assm->functions.start(assm->assm_userdata, session, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Assembles the complete message and returns the result
 * 
 * @param assm The assembler
 * @param data Place that will receive the data
 * @param size Place that will receive the size
 * @param error A pointer to an error struct
 * @returns TRUE if the package container was assembled successfully, FALSE otherwise
 * 
 */
SmlBool smlAssemblerRun(SmlAssembler *assm, char **data, unsigned int *size, SmlBool *end, SmlBool final, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %i, %p)", __func__, assm, data, size, end, final, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(data);
	smlAssert(size);
	smlAssert(assm->functions.run);
	
	if (!assm->functions.run(assm->assm_userdata, data, size, end, final, smlAssemblerGetRemoteMaxMsgSize(assm), error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Assembles the complete message and returns the result
 * 
 * @param assm The assembler
 * @param data Place that will receive the data
 * @param size Place that will receive the size
 * @param error A pointer to an error struct
 * @returns TRUE if the package container was assembled successfully, FALSE otherwise
 * 
 */
unsigned int smlAssemblerCheckSize(SmlAssembler *assm, SmlBool headeronly, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, assm, headeronly, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(assm->functions.check_size);
	
	unsigned int size = 0;
	if (!(size = assm->functions.check_size(assm->assm_userdata, headeronly, error)))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s: %i", __func__, size);
	return size;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return 0;
}

/** @brief Flushes the already parsed commands
 * 
 * This command can be used to flush the already parsed commands. You can use
 * this to flush the assembler after you pull part of the data using smlAssemblerRun()
 * 
 * @param assm The assembler
 * @param error A pointer to an error struct
 * @returns TRUE if the flushing was successful, FALSE otherwise
 * 
 */
unsigned int smlAssemblerFlush(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);
	smlAssert(assm->functions.flush);
	
	unsigned int ret = assm->functions.flush(assm->assm_userdata);
	assm->empty = TRUE;
	
	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
}

void smlAssemblerRestoreCommands(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);
	smlAssert(assm->functions.restore_cmds);
	
	assm->functions.restore_cmds(assm->assm_userdata);
	assm->empty = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Gets the available space in the assembler
 * 
 * This command tries to add the given command to the assembler without the
 * item data. Then it returns how big the item data could be without violating
 * the limits. If there is not limit set, -1 is returned. This command only
 * makes sense for change commands */
SmlBool smlAssemblerGetSpace(SmlAssembler *assm, int *space, SmlCommand *parent, SmlCommand *cmd, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p, %p)", __func__, assm, space, parent, cmd, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(space);
	smlAssert(cmd);
	smlAssert(assm->functions.start_cmd);
	smlAssert(assm->functions.rem_cmd);
	smlAssert(cmd->type == SML_COMMAND_TYPE_ADD || cmd->type == SML_COMMAND_TYPE_REPLACE);
	unsigned int parentID = 0;
	
	int limit = smlAssemblerGetRemoteMaxMsgSize(assm);
	if (!limit) {
		*space = -1;
		smlTrace(TRACE_EXIT, "%s: No limit", __func__);
		return TRUE;
	}
	
	/* Add the command */
	if (parent) {
		if (!parent->cmdID) {
			smlErrorSet(error, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = parent->cmdID;
	}
	
	/* disable the items */
	if (cmd->private.change.items)
	{
		guint i;
		for (i=0; i < g_list_length(cmd->private.change.items); i++)
		{
			SmlItem *item = g_list_nth_data(cmd->private.change.items, i);
			item->disabled = TRUE;
		}
	}

	SmlBool noCmdID = FALSE;
	if (!cmd->cmdID) {
		noCmdID = TRUE;
		/* FIXME: This code does not support more
		 * FIXME: than 99.999 commands in one session. */
		cmd->cmdID = 10000;
	}
	
	if (!assm->functions.start_cmd(assm->assm_userdata, parentID, cmd, error))
		goto error_enable_item;
	
	/* Now check the size */
	int size = 0;
	if (!(size = smlAssemblerCheckSize(assm, FALSE, error)))
		goto error_remove;
	
	if (limit <= size)
		*space = 0;
	else
		*space = limit - size - 10;
	
	/* Remove the command again */
	if (!assm->functions.rem_cmd(assm->assm_userdata, parentID, error))
		 goto error_remove;
	
	/* enable the items again */
	if (cmd->private.change.items)
	{
		guint i;
		for (i=0; i < g_list_length(cmd->private.change.items); i++)
		{
			SmlItem *item = g_list_nth_data(cmd->private.change.items, i);
			item->disabled = FALSE;
		}
	}
	
	if (noCmdID)
		cmd->cmdID = 0;
	
	smlTrace(TRACE_EXIT, "%s: %i", __func__, *space);
	return TRUE;

error_remove:
	assm->functions.rem_cmd(assm->assm_userdata, parentID, NULL);
error_enable_item:
	if (cmd->private.change.items)
	{
		guint i;
		for (i=0; i < g_list_length(cmd->private.change.items); i++)
		{
			SmlItem *item = g_list_nth_data(cmd->private.change.items, i);
			item->disabled = FALSE;
		}
	}
	if (noCmdID)
		cmd->cmdID = 0;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Starts a parent command
 * 
 * This command starts a parent command. All commands added after this one will be added
 * as subcommands. This functions also take care if the parent command would spawn 2 messages.
 * This function can nest.
 * 
 * @param assm The assembler
 * @param parent The parent to the command to add. NULL if none
 * @param cmd The command to add
 * @param error A pointer to an error struct
 * @returns SML_ASSEMBLER_RESULT_OK if the command was added ok, SML_ASSEMBLER_RESULT_MISMATCH
 * if the parent command did not fit, SML_ASSEMBLER_RESULT_ERROR if some error occured.
 * 
 */
SmlAssemblerResult smlAssemblerStartCommand(SmlAssembler *assm, SmlCommand *parent, SmlCommand *cmd, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %p)", __func__, assm, parent, cmd, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(cmd);
	smlAssert(assm->functions.start_cmd);
	smlAssert(assm->functions.rem_cmd);
	unsigned int parentID = 0;
	
	/* Add the command */
	if (parent) {
		if (!parent->cmdID) {
			smlErrorSet(error, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = parent->cmdID;
	}
	
	if (!assm->functions.start_cmd(assm->assm_userdata, parentID, cmd, error))
		goto error;
	
	/* Lets see if the new buffer is small enough */
	int limit = smlAssemblerGetRemoteMaxMsgSize(assm);
	if (limit) {
		/* Now check the size */
		int size = 0;
		if (!(size = smlAssemblerCheckSize(assm, FALSE, error)))
			goto error;
		
		if (size > limit) {
			/* The status does not fit. Remove it again */
			if (!assm->functions.rem_cmd(assm->assm_userdata, parentID, error))
				 goto error;
			
			smlTrace(TRACE_EXIT, "%s: Mismatch", __func__);
			return SML_ASSEMBLER_RESULT_MISMATCH;
		}
		smlTrace(TRACE_INTERNAL, "%s: size %i, limit %i", __func__, size, limit);
	}
	
	if (cmd->cmdID)
		assm->empty = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return SML_ASSEMBLER_RESULT_OK;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return SML_ASSEMBLER_RESULT_ERROR;
}

/** @brief Ends a parent command
 * 
 * Ends the last previously started parent command. All commands added afterwards wont be
 * subcommands any more.
 * 
 * @param assm The assembler
 * @param parent The parent of the child to end. NULL if none
 * @param error A pointer to an error struct
 * @returns TRUE if the command was added successfully, FALSE otherwise
 * 
 */
SmlBool smlAssemblerEndCommand(SmlAssembler *assm, SmlCommand *parent, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, assm, parent, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(assm->functions.end_cmd);
	
	unsigned int parentID = 0;
	
	/* Add the command */
	if (parent) {
		if (!parent->cmdID) {
			smlErrorSet(error, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = parent->cmdID;
	}
	
	/* End the command */
	if (!assm->functions.end_cmd(assm->assm_userdata, parentID, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Assembles the header
 * 
 * This command adds the header to the assembler.
 * 
 * @param assm The assembler
 * @param session The session header to add
 * @param error A pointer to an error struct
 * @returns TRUE if the header was added ok, FALSE if some error occured.
 * 
 */
SmlBool smlAssemblerAddHeader(SmlAssembler *assm, SmlSession *session, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, assm, session, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(session);
	smlAssert(assm->functions.add_header);
	
	/* Add the command */
	if (!assm->functions.add_header(assm->assm_userdata, session, error))
		goto error;
	
	/* Now check the size */
	unsigned int size = 0;
	if (!(size = smlAssemblerCheckSize(assm, TRUE, error)))
		goto error;
	
	/* Lets see if the new buffer is small enough */
	if (smlAssemblerGetRemoteMaxMsgSize(assm) && size > smlAssemblerGetRemoteMaxMsgSize(assm)) {
		smlErrorSet(error, SML_ERROR_GENERIC, "Limit to small. Unable to fit a the header");
		goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Reserves a place for a status with this assembler
 * 
 * This command reserves a place for a status. Call this function to make sure that
 * the statuses are added in the correct order. You dont have to reserve for cmdRef=0
 * . This one is always reserved
 * 
 * @param assm The assembler
 * @param cmdRef The cmdRef of the status to reserve
 * @param cmdID The cmdID for this status
 * @param error A pointer to an error struct
 * @returns TRUE if successful, FALSE otherwise
 * 
 */
SmlAssemblerResult smlAssemblerReserveStatus(SmlAssembler *assm, unsigned int cmdRef, unsigned int msgRef, unsigned int cmdID, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %i, %i, %p)", __func__, assm, cmdRef, msgRef, cmdID, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(assm->functions.reserve_status);
	
	/* Reserve the status */
	if (!assm->functions.reserve_status(assm->assm_userdata, cmdRef, msgRef, cmdID, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return FALSE;
}

/** @brief Assembles a status
 * 
 * This command adds a status to the assembler. The complete package is assembled
 * afterwards to see if the status fits inside the current message. If the status
 * does not fit, SML_ASSEMBLER_RESULT_MISMATCH is returned
 * 
 * @param assm The assembler
 * @param status The command to add
 * @param force If set to TRUE, libsyncml will ignore if the outgoing limit has been reached
 * @param error A pointer to an error struct
 * @returns SML_ASSEMBLER_RESULT_OK if the status was added ok, SML_ASSEMBLER_RESULT_MISMATCH
 * if the status did not fit, SML_ASSEMBLER_RESULT_ERROR if some error occured.
 * 
 */
SmlAssemblerResult smlAssemblerAddStatusFull(SmlAssembler *assm, SmlStatus *status, SmlBool force, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p)", __func__, assm, status, force, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(status);
	smlAssert(assm->functions.add_status);
	smlAssert(assm->functions.rem_status);
	
	/* Add the status */
	if (!assm->functions.add_status(assm->assm_userdata, status, error))
		goto error;
	
	if (!force) {
		/* Lets see if the new buffer is small enough */
		int limit = smlAssemblerGetRemoteMaxMsgSize(assm);
		if (limit) {
			/* Now check the size */
			int size = 0;
			if (!(size = smlAssemblerCheckSize(assm, FALSE, error)))
				goto error;
			
			if (size > limit) {
				/* The status does not fit. Remove it again */
				if (!assm->functions.rem_status(assm->assm_userdata, error))
					goto error;
				
				smlTrace(TRACE_EXIT, "%s: Mismatch", __func__);
				return SML_ASSEMBLER_RESULT_MISMATCH;
			}
		}
	}
	
	/*if (status->cmdRef != 0)
		assm->empty = FALSE;*/
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return SML_ASSEMBLER_RESULT_OK;
	
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return SML_ASSEMBLER_RESULT_ERROR;
}

SmlAssemblerResult smlAssemblerAddStatus(SmlAssembler *assm, SmlStatus *status, SmlError **error)
{
	CHECK_ERROR_REF
	return smlAssemblerAddStatusFull(assm, status, FALSE, error);
}

/** @brief Sets a option on the assembler
 * 
 * @param assm The assembler
 * @param optionname The name of the option to set
 * @param value The new value
 * 
 */
void smlAssemblerSetOption(SmlAssembler *assm, const char *optionname, const char *value)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %s)", __func__, assm, VA_STRING(optionname), VA_STRING(value));
	smlAssert(assm);
	smlAssert(optionname);
	
	g_hash_table_replace(assm->options, g_strdup(optionname), g_strdup(value));
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

/** @brief Gets a option from the assembler
 * 
 * @param assm The assembler
 * @param optionname The name of the option to get
 * @returns The value
 * 
 */
const char *smlAssemblerGetOption(SmlAssembler *assm, const char *optionname)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s)", __func__, assm, VA_STRING(optionname));
	smlAssert(assm);
	smlAssert(optionname);
	
	const char *ret = g_hash_table_lookup(assm->options, optionname);
	
	smlTrace(TRACE_EXIT, "%s: %s", __func__, VA_STRING(ret));
	return ret;
}

/* FIXME: DEPRECATED */
unsigned int smlAssemblerGetLimit(SmlAssembler *assm)
{
	return smlAssemblerGetRemoteMaxMsgSize(assm);
}

unsigned int smlAssemblerGetRemoteMaxMsgSize(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);

	smlTrace(TRACE_EXIT, "%s: %i", __func__, assm->remoteMaxMsgSize);
	return assm->remoteMaxMsgSize;
}

/* FIXME: DEPRECATED */
void smlAssemblerSetRequestedLimit(SmlAssembler *assm, unsigned int limit)
{
	smlAssemblerSetRemoteMaxMsgSize(assm, limit);
}

/* FIXME: DEPRECATED */
void smlAssemblerSetLimit(SmlAssembler *assm, unsigned int limit)
{
	smlAssemblerSetRemoteMaxMsgSize(assm, limit);
}

unsigned int smlAssemblerSetRemoteMaxMsgSize(SmlAssembler *assm, unsigned int limit)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, assm, limit);
	smlAssert(assm);

	if (limit == 0)
	{
		/* Reset MaxMsgSize to unused state */
		assm->remoteMaxMsgSize = limit;
	} else {
		if (limit < assm->remoteMaxMsgSize || assm->remoteMaxMsgSize == 0)
		{
			/* There is a real new limit. */
			assm->remoteMaxMsgSize = limit;
		} else {
			smlTrace(TRACE_INTERNAL,
				"%s: using old limit (%d) because of large new limit (%d).",
				__func__, assm->remoteMaxMsgSize, limit);
		}
	}
	
	smlTrace(TRACE_EXIT, "%s - %d", __func__, assm->remoteMaxMsgSize);
	return assm->remoteMaxMsgSize;
}

/* FIXME: DEPRECATED */
int smlAssemblerGetSendingMaxObjSize(SmlAssembler *assm)
{
	return smlAssemblerGetRemoteMaxObjSize(assm);
}

/* FIXME: DEPRECATED */
int smlAssemblerGetRequestedMaxObjSize(SmlAssembler *assm)
{
	return smlAssemblerGetRemoteMaxObjSize(assm);
}


unsigned int smlAssemblerGetRemoteMaxObjSize(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);

	smlTrace(TRACE_EXIT, "%s: %i", __func__, assm->remoteMaxObjSize);
	return assm->remoteMaxObjSize;
}

/* FIXME: DEPRECATED */
void smlAssemblerSetRequestedMaxObjSize(SmlAssembler *assm, int limit)
{
	smlAssemblerSetRemoteMaxObjSize(assm, limit);
}

/* FIXME: DEPRECATED */
void smlAssemblerSetSendingMaxObjSize(SmlAssembler *assm, int limit)
{
	smlAssemblerSetRemoteMaxObjSize(assm, limit);
}

unsigned int smlAssemblerSetRemoteMaxObjSize(SmlAssembler *assm, unsigned int limit)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i)", __func__, assm, limit);
	smlAssert(assm);

	if (limit == 0)
	{
		/* Reset MaxObjSize to unused state */
		assm->remoteMaxObjSize = limit;
	} else {
		if (limit < assm->remoteMaxObjSize || assm->remoteMaxObjSize == 0)
		{
			/* There is a real new limit. */
			assm->remoteMaxObjSize = limit;
		} else {
			smlTrace(TRACE_INTERNAL,
				"%s: using old limit (%d) because of large new limit (%d).",
				__func__, assm->remoteMaxObjSize, limit);
		}
	}
	
	smlTrace(TRACE_EXIT, "%s - %d", __func__, assm->remoteMaxObjSize);
	return assm->remoteMaxObjSize;
}

SmlBool smlAssemblerIsEmpty(SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);
	
	SmlBool ret = assm->empty;
	
	smlTrace(TRACE_EXIT, "%s: %i", __func__, ret);
	return ret;
}

/** @brief Checks if there are reserved statuses missing
 * 
 * @param assm The assembler to check
 * @returns TRUE if statuses are missing, FALSE if all statuses had been added
 */
SmlBool smlAssemblerIsStatusMissing(SmlAssembler *assm)
{
	smlAssert(assm);
	smlAssert(assm->functions.missing_status);
	
	SmlBool ret = assm->functions.missing_status(assm->assm_userdata);
	
	return ret;
}

SmlBool smlAssemblerGetNextCmdRef(SmlAssembler *assm, unsigned int *cmdRef, unsigned int *msgRef)
{
	smlAssert(assm);
	smlAssert(cmdRef);
	smlAssert(msgRef);
	smlAssert(assm->functions.next_cmdref);
	
	return assm->functions.next_cmdref(assm->assm_userdata, cmdRef, msgRef);
}

/*@}*/
