/*
 * libsyncml - A syncml protocol implementation
 * Copyright (C) 2005  Armin Bauer <armin.bauer@opensync.org>
 * Copyright (C) 2008-2009  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 "sml_parse_internals.h"

#include <config.h>

#include "sml_error_internals.h"
#include "sml_support.h"

#include "sml_command.h"
#include "sml_elements.h"
#include "sml_notification.h"

#include "data_sync_api/sml_location.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,
              gsize limit,
              GError **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;
			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;
			break;
		case SML_MIMETYPE_WBXML:
#ifdef ENABLE_WBXML
			if (!(parser->parser_userdata = smlWbxmlParserNew(&(parser->functions), error)))
				goto error;
			break;
#else
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Wbxml not enabled in this build");
			goto error;
#endif
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown parser type");
			goto error;
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, parser);
	return parser;
error:
	if (parser)
		smlSafeFree((gpointer *)&parser);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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
 * 
 */
gboolean
smlParserStart (SmlParser *parser,
                const gchar *data,
                gsize size,
                GError **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,
		"If an OMA DS 1.2 SAN must be parsed then the manager must be present.");
	
	if (parser->limit && size > parser->limit) {
		g_set_error(error, SML_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);
		smlNotificationSetMimeType(san, SML_MIMETYPE_XML);
		smlNotificationSetSessionType(san, SML_SESSION_TYPE_CLIENT);
		SmlLocation *target = sml_location_new();
		sml_location_set_uri(target, "/");
		smlNotificationSetTarget(san, target);
		g_object_unref(target);
		target = NULL;

		/* assemble a SyncML 1.1 notification */
		char *xmlData = NULL;
		gsize 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__, (*error)->message);
	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
 * 
 */
gboolean
smlParserGetHeader (SmlParser *parser,
                    SmlHeader **header,
                    SmlCred **cred,
                    GError **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__, (*error)->message);
	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,
                     GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlParserGetStatus (SmlParser *parser,
                    SmlStatus **status,
                    GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlParserEnd (SmlParser *parser,
              gboolean *final,
              gboolean *end,
              GError **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__, (*error)->message);
	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,
                 gsize limit,
                 GError **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;
			break;
		case SML_MIMETYPE_WBXML:
#ifdef ENABLE_WBXML
			if (!(assm->assm_userdata = smlWbxmlAssemblerNew(assm, &(assm->functions), error)))
				goto error;
			break;
#else
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Wbxml not enabled in this build");
			goto error;
#endif
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown assembler type");
			goto error;
	}
	
	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:
	if (assm)
		smlSafeFree((gpointer *)&assm);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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
 * 
 */
gboolean
smlAssemblerStart (SmlAssembler *assm,
                   SmlSession *session,
                   GError **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__, (*error)->message);
	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
 * 
 */
gboolean
smlAssemblerRun (SmlAssembler *assm,
                 gchar **data,
                 gsize *size,
                 gboolean *end,
                 gboolean final,
                 GError **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__, (*error)->message);
	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
 * 
 */
gsize
smlAssemblerCheckSize (SmlAssembler *assm,
                       gboolean headeronly,
                       GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, assm, headeronly, error);
	CHECK_ERROR_REF
	smlAssert(assm);
	smlAssert(assm->functions.check_size);
	
	gsize 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__, (*error)->message);
	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
 * 
 */
gsize
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, 0 is returned. This command only
 * makes sense for change commands */
gboolean
smlAssemblerGetSpace (SmlAssembler *assm,
                      gssize *space,
                      SmlCommand *parent,
                      SmlCommand *cmd,
                      GError **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);

	/* only one change per command is supported by libsyncml
	 * It is possible to parse commands with more items
	 * but it is not supported to send more items than one
	 */
	if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_ADD ||
	    smlCommandGetType(cmd) == SML_COMMAND_TYPE_REPLACE)
		smlAssert(smlCommandGetNumChanges(cmd) == 1);
	unsigned int parentID = 0;
	
	gsize limit = smlAssemblerGetRemoteMaxMsgSize(assm);
	if (!limit) {
		/* The function call is useless in this case. */
		*space = -1;
		smlTrace(TRACE_EXIT, "%s: No limit", __func__);
		return TRUE;
	}
	
	/* Add the command */
	if (parent) {
		if (!smlCommandGetID(parent)) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = smlCommandGetID(parent);
	}
	
	gboolean noCmdID = FALSE;
	if (!smlCommandGetID(cmd)) {
		noCmdID = TRUE;
		/* FIXME: This code does not support more
		 * FIXME: than 99.999 commands in one session.
		 * FIXME: Why don't we use 0?
		 */
		smlCommandSetID(cmd, 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;

	/* Now calculate the size without the data of the item.
	 * The data can be splitted into several chunks.
	 * Please see SupportLargeObj and MoreData.
	 */
	if (smlCommandGetType(cmd) == SML_COMMAND_TYPE_ADD ||
	    smlCommandGetType(cmd) == SML_COMMAND_TYPE_REPLACE)
	{
		gsize length = strlen(sml_data_sync_change_item_get_data(smlCommandGetNthChange(cmd, 0)));
		smlTrace(TRACE_INTERNAL, "%s: item data size %d", __func__, length);
		size -= length;
	}

	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;
	
	if (noCmdID)
		smlCommandSetID(cmd, 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 (noCmdID)
		smlCommandSetID(cmd, 0);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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,
                          GError **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 (!smlCommandGetID(parent)) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = smlCommandGetID(parent);
	}
	
	if (!assm->functions.start_cmd(assm->assm_userdata, parentID, cmd, error))
		goto error;
	
	/* Lets see if the new buffer is small enough */
	gsize limit = smlAssemblerGetRemoteMaxMsgSize(assm);
	if (limit) {
		/* Now check the size */
		gsize size = 0;
		if (!(size = smlAssemblerCheckSize(assm, FALSE, error)))
			goto error;
		
		if (size > limit) {
			/* The status/command does not fit. Remove it again */
			if (!assm->functions.rem_cmd(assm->assm_userdata, parentID, error))
				 goto error;
			
			smlTrace(TRACE_EXIT, "%s: Mismatch => size (%d) > limit (%d)", __func__, size, limit);
			return SML_ASSEMBLER_RESULT_MISMATCH;
		}
		smlTrace(TRACE_INTERNAL, "%s: size %i, limit %i", __func__, size, limit);
	}
	
	if (smlCommandGetID(cmd))
		assm->empty = FALSE;
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return SML_ASSEMBLER_RESULT_OK;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	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
 * 
 */
gboolean
smlAssemblerEndCommand (SmlAssembler *assm,
                        SmlCommand *parent,
                        GError **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 (!smlCommandGetID(parent)) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Parent has to be added before");
			goto error;
		}
		parentID = smlCommandGetID(parent);
	}
	
	/* 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__, (*error)->message);
	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.
 * 
 */
gboolean
smlAssemblerAddHeader (SmlAssembler *assm,
                       SmlSession *session,
                       GError **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)) {
		g_set_error(error, SML_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__, (*error)->message);
	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,
                           guint cmdRef,
                           guint msgRef,
                           guint cmdID,
                           GError **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__, (*error)->message);
	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,
                           gboolean force,
                           GError **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 */
		gsize limit = smlAssemblerGetRemoteMaxMsgSize(assm);
		if (limit) {
			/* Now check the size */
			gsize 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__, (*error)->message);
	return SML_ASSEMBLER_RESULT_ERROR;
}

SmlAssemblerResult
smlAssemblerAddStatus (SmlAssembler *assm,
                       SmlStatus *status,
                       GError **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 gchar *optionname,
                       const gchar *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 gchar*
smlAssemblerGetOption (SmlAssembler *assm,
                       const gchar *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;
}

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

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

gsize smlAssemblerSetRemoteMaxMsgSize (SmlAssembler *assm,
                                       gsize 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;
}

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

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

gsize
smlAssemblerSetRemoteMaxObjSize (SmlAssembler *assm,
                                 gsize 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;
}

gboolean
smlAssemblerIsEmpty (SmlAssembler *assm)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, assm);
	smlAssert(assm);
	
	gboolean 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
 */
gboolean
smlAssemblerIsStatusMissing (SmlAssembler *assm)
{
	smlAssert(assm);
	smlAssert(assm->functions.missing_status);
	
	gboolean ret = assm->functions.missing_status(assm->assm_userdata);
	
	return ret;
}

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

/*@}*/
