/*
 * 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_command_internals.h"

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

#include "parser/sml_xml_assm.h"
#include <libsyncml/data_sync_api/sml_data_sync_change_item_internals.h>

SmlCommandType
smlCommandTypeFromString (const gchar *name,
                          GError **error)
{
	CHECK_ERROR_REF
	if (!name)
		return SML_COMMAND_TYPE_UNKNOWN;
	
	if (!strcmp(name, SML_ELEMENT_ALERT)) {
		return SML_COMMAND_TYPE_ALERT;
	} else if (!strcmp(name, SML_ELEMENT_SYNC)) {
		return SML_COMMAND_TYPE_SYNC;
	} else if (!strcmp(name, SML_ELEMENT_PUT)) {
		return SML_COMMAND_TYPE_PUT;
	} else if (!strcmp(name, SML_ELEMENT_SYNCHDR)) {
		return SML_COMMAND_TYPE_HEADER;
	} else if (!strcmp(name, SML_ELEMENT_ADD)) {
		return SML_COMMAND_TYPE_ADD;
	} else if (!strcmp(name, SML_ELEMENT_REPLACE)) {
		return SML_COMMAND_TYPE_REPLACE;
	} else if (!strcmp(name, SML_ELEMENT_MAP)) {
		return SML_COMMAND_TYPE_MAP;
	} else if (!strcmp(name, SML_ELEMENT_DELETE)) {
		return SML_COMMAND_TYPE_DELETE;
	} else if (!strcmp(name, SML_ELEMENT_RESULTS)) {
		return SML_COMMAND_TYPE_RESULTS;
	} else if (!strcmp(name, SML_ELEMENT_GET)) {
		return SML_COMMAND_TYPE_GET;
	}
	
	g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown command name \"%s\"", name);
	return SML_COMMAND_TYPE_UNKNOWN;
}

G_CONST_RETURN gchar*
smlCommandTypeToString (SmlCommandType type,
                        GError **error)
{
	CHECK_ERROR_REF
	switch (type) {
		case SML_COMMAND_TYPE_ALERT:
			return SML_ELEMENT_ALERT;
		case SML_COMMAND_TYPE_SYNC:
			return SML_ELEMENT_SYNC;
		case SML_COMMAND_TYPE_PUT:
			return SML_ELEMENT_PUT;
		case SML_COMMAND_TYPE_HEADER:
			return SML_ELEMENT_SYNCHDR;
		case SML_COMMAND_TYPE_ADD:
			return SML_ELEMENT_ADD;
		case SML_COMMAND_TYPE_REPLACE:
			return SML_ELEMENT_REPLACE;
		case SML_COMMAND_TYPE_DELETE:
			return SML_ELEMENT_DELETE;
		case SML_COMMAND_TYPE_MAP:
			return SML_ELEMENT_MAP;
		case SML_COMMAND_TYPE_GET:
			return SML_ELEMENT_GET;
		case SML_COMMAND_TYPE_RESULTS:
			return SML_ELEMENT_RESULTS;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown command type \"%i\"", type);
			return NULL;
	}
		
}

SmlStatus*
smlStatusNew (SmlErrorType data,
              gsize cmdref,
              gsize msgref,
              SmlLocation *sourceref,
              SmlLocation *targetref,
              SmlCommandType type,
              GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %i, %i, %p, %p, %i, %p)", __func__, data, cmdref, msgref, sourceref, targetref, type, error);
	CHECK_ERROR_REF
	SmlStatus *status = smlTryMalloc0(sizeof(SmlStatus), error);
	if (!status)
		goto error;
	
	status->refCount = 1;
	status->cmdRef = cmdref;
	status->msgRef = msgref;
	status->type = type;
	if (data != SML_ERROR_UNKNOWN)
		status->data = g_strdup_printf("%i", data);
	
	if (sourceref) {
		status->sourceRef = sourceref;
		g_object_ref(sourceref);
	}
	
	if (targetref) {
		status->targetRef = targetref;
		g_object_ref(targetref);
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, status);
	return status;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlStatus*
smlStatusRef (SmlStatus *status)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, status);
	smlAssert(status);
	
	g_atomic_int_inc(&(status->refCount));
	
	smlTrace(TRACE_EXIT, "%s: New refcount: %i", __func__, status->refCount);
	return status;
}

void
smlStatusUnref (SmlStatus *status)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, status);
	smlAssert(status);
	
	if (g_atomic_int_dec_and_test(&(status->refCount))) {
		smlTrace(TRACE_INTERNAL, "%s: Refcount == 0!", __func__);
		
		if (status->sourceRef)
			g_object_unref(status->sourceRef);
	
		if (status->targetRef)
			g_object_unref(status->targetRef);
		
		if (status->data)
			smlSafeCFree(&(status->data));
		
		if (status->anchor)
			smlAnchorFree(status->anchor);

		if (status->item)
			smlItemUnref(status->item);
			
		smlSafeFree((gpointer *)&status);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlErrorType
smlStatusGetCode (SmlStatus *status)
{
	smlAssert(status);
	smlAssert(status->data);
	return atoi(status->data);
}

SmlErrorClass
smlStatusGetClass (SmlStatus *status)
{
	smlAssert(status);
	smlAssert(status->data);
	return (atoi(status->data) / 100);
}

SmlCommand*
smlStatusGetResult (SmlStatus *status)
{
	if (status->type == SML_COMMAND_TYPE_RESULTS)
		return status->result;
	return NULL;
}

gboolean
smlStatusIsResult (SmlStatus *status)
{
	return status->type == SML_COMMAND_TYPE_RESULTS ? TRUE : FALSE;
}

SmlCommandType
smlStatusGetType (SmlStatus *status)
{
	return status->type;
}

SmlAnchor*
smlStatusGetAnchor (SmlStatus *status)
{
	return status->anchor;
}

SmlChal*
smlStatusGetChal (SmlStatus *status)
{
	return status->chal;
}

void
smlStatusSetChal (SmlStatus *status,
                  SmlChal *chal)
{
	if (status->chal)
		smlChalUnref(status->chal);
	status->chal = chal;
	smlChalRef(chal);
}

gsize
smlStatusGetCommandRef (SmlStatus *status)
{
	return status->cmdRef;
}

gsize
smlStatusGetMessageRef (SmlStatus *status)
{
	return status->msgRef;
}

const gchar*
smlStatusGetData (SmlStatus *status)
{
	return status->data;
}

SmlLocation*
smlStatusGetSourceRef (SmlStatus *status)
{
	return status->sourceRef;
}

SmlLocation*
smlStatusGetTargetRef (SmlStatus *status)
{
	return status->targetRef;
}

SmlCommand*
smlCommandNew (SmlCommandType type,
               GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %p)", __func__, type, error);
	CHECK_ERROR_REF
	
	SmlCommand *cmd = smlTryMalloc0(sizeof(SmlCommand), error);
	if (!cmd)
		goto error;
	
	cmd->refCount = 1;
	cmd->type = type;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlStatus*
smlCommandNewReply (const SmlCommand *cmd,
                    SmlErrorType code,
                    GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, cmd, code, error);
	CHECK_ERROR_REF
	smlAssert(cmd);
	
	SmlStatus *reply = smlStatusNew(code, cmd->cmdID, cmd->msgID, cmd->source, cmd->target, cmd->type, error);
	if (!reply)
		goto error;
	
	switch (cmd->type) {
		case SML_COMMAND_TYPE_ALERT:
			if (cmd->private.alert.anchor) {
				reply->anchor = smlAnchorNew(NULL, smlAnchorGetNext(cmd->private.alert.anchor), error);
				if (!reply->anchor)
					goto error;
			}
			break;
		default:
		;
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, reply);
	return reply;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewResult (SmlCommand *cmd,
                     SmlLocation *source,
                     gchar *data,
                     gsize size,
                     const gchar *contenttype,
                     GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %s, %p)", __func__, cmd, source, data, size, VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	smlAssert(cmd);
	
	SmlCommand *result = smlCommandNew(SML_COMMAND_TYPE_RESULTS, error);
	if (!result)
		goto error;
	
	result->private.results.status = smlStatusNew(SML_NO_ERROR, cmd->cmdID, cmd->msgID, cmd->source, cmd->target, SML_COMMAND_TYPE_RESULTS, error);
	if (!result->private.results.status)
		goto error;
		
	result->private.results.status->item = smlItemNewForData(data, size, error);
	smlSafeCFree(&data);
	if (!result->private.results.status->item)
		goto error;
	
	if (!smlItemSetContentType(result->private.results.status->item, contenttype, error))
		goto error;
	/* FIXME: Why did we use sml_location_clone here? */
	smlItemSetSource(result->private.results.status->item, source);

	smlTrace(TRACE_EXIT, "%s: %p", __func__, result);
	return result;
error:
	if (cmd)
		smlCommandUnref(result);
	if (data)
		smlSafeCFree(&data);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandRef (SmlCommand *cmd)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, cmd);
	smlAssert(cmd);
	
	g_atomic_int_inc(&(cmd->refCount));
	
	smlTrace(TRACE_EXIT, "%s: New refcount: %i", __func__, cmd->refCount);
	return cmd;
}

void
smlCommandUnref (SmlCommand *cmd)
{
	smlTrace(TRACE_ENTRY, "%s(%p)", __func__, cmd);
	smlAssert(cmd);
	
	if (g_atomic_int_dec_and_test(&(cmd->refCount))) {
		smlTrace(TRACE_INTERNAL, "%s: Refcount == 0!", __func__);
		
		if (cmd->parent) {
			cmd->parent->children = g_list_remove(cmd->parent->children, cmd);
		
			smlCommandUnref(cmd->parent);
			cmd->parent = NULL;
		}
		
		switch (cmd->type) {
			case SML_COMMAND_TYPE_UNKNOWN:
			case SML_COMMAND_TYPE_HEADER:
			case SML_COMMAND_TYPE_SYNC:
				break;
			case SML_COMMAND_TYPE_ALERT:
				if (cmd->private.alert.anchor)
					smlAnchorFree(cmd->private.alert.anchor);
				if (cmd->private.alert.contentType)
					smlSafeCFree(&(cmd->private.alert.contentType));
				break;
			case SML_COMMAND_TYPE_PUT:
			case SML_COMMAND_TYPE_GET:
				if (cmd->private.access.type)
					smlSafeCFree(&(cmd->private.access.type));
					
				if (cmd->private.access.lang)
					smlSafeCFree(&(cmd->private.access.lang));
					
				if (cmd->private.access.item)
					smlItemUnref(cmd->private.access.item);
				break;
			case SML_COMMAND_TYPE_ADD:
			case SML_COMMAND_TYPE_REPLACE:
			case SML_COMMAND_TYPE_DELETE:
				if (cmd->private.change.items)
				{
					guint i;
					for (i = 0; i < g_list_length(cmd->private.change.items); i++)
					{
						SmlDataSyncChangeItem *item = g_list_nth_data(cmd->private.change.items, i);
						g_object_unref(item);
					}
					g_list_free(cmd->private.change.items);
				}
				break;
			case SML_COMMAND_TYPE_MAP:
				while (cmd->private.map.items) {
					SmlMapItem *item = cmd->private.map.items->data;
					g_object_unref(item);
					cmd->private.map.items = g_list_delete_link(cmd->private.map.items, cmd->private.map.items);
				}
				break;
			case SML_COMMAND_TYPE_RESULTS:
				if (cmd->private.results.status)
					smlStatusUnref(cmd->private.results.status);
				break;
		}
		
		if (cmd->target)
			g_object_unref(cmd->target);
		
		if (cmd->source)
			g_object_unref(cmd->source);
		
		smlSafeFree((gpointer *)&cmd);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlCommand*
smlCommandNewChange (SmlDataSyncChangeItem *item,
                     GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p)", __func__, item, error);
	CHECK_ERROR_REF
	SmlCommand *cmd = NULL;
	
	switch (sml_data_sync_change_item_get_action(item)) {
		case SML_CHANGE_ADD:
			cmd = smlCommandNew(SML_COMMAND_TYPE_ADD, error);
			break;
		case SML_CHANGE_REPLACE:
			cmd = smlCommandNew(SML_COMMAND_TYPE_REPLACE, error);
			break;
		case SML_CHANGE_DELETE:
			cmd = smlCommandNew(SML_COMMAND_TYPE_DELETE, error);
			break;
		default:
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Unknown changetype");
	}
	if (!cmd)
		goto error;

	cmd->private.change.items = g_list_append(NULL, item);
	if (!cmd->private.change.items)
	{
		goto error;
	}
	g_object_ref(item);
	
	/* The usage of target and source depends on the role of the
	 * local peer. We cannot determine if the local peer is an
	 * OMA DS client or server. This function is only used by
	 * smlDsSessionQueueChange. So the correct usage of target
	 * and source must be ensured by the XML assembler.
	 */
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	if (cmd)
		smlCommandUnref(cmd);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewAlert (SmlAlertType type,
                    SmlLocation *target,
                    SmlLocation *source,
                    const gchar *next,
                    const gchar *last,
                    const gchar *contenttype,
                    GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %p, %p, %s, %s, %s, %p)", __func__, type, target, source, VA_STRING(next), VA_STRING(last), VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	
	SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_ALERT, error);
	if (!cmd)
		goto error;
	
	if (target) {
		cmd->target = target;
		g_object_ref(target);
	}
	
	if (source) {
		cmd->source = source;
		g_object_ref(source);
	}

	/* A synchronization anchor makes only sense if it is not an
	 * alert for the next part of an incomplete package (next
	 * message alert) or SAN.
	 */
	if (type != SML_ALERT_NEXT_MESSAGE && type != SML_ALERT_TWO_WAY_BY_SERVER) {
		/* If this is a slow sync alert then it makes no sense
		 * to send the last sync anchor because a slow sync is
		 * only necessary if there was no earlier sync or
		 * something goes wrong since then which means there is
		 * no safe last anchor.
		 */
		if (type == SML_ALERT_SLOW_SYNC) {
			if (last)
				smlTrace(TRACE_INTERNAL,
					"%s: removing last anchor (%s) from slow sync alert",
					__func__, VA_STRING(last));
			cmd->private.alert.anchor = smlAnchorNew(NULL, next, error);
		} else
			cmd->private.alert.anchor = smlAnchorNew(last, next, error);
		if (!cmd->private.alert.anchor)
			goto error;
	}
	cmd->private.alert.type = type;
	cmd->private.alert.contentType = g_strdup(contenttype);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewSync (SmlLocation *target,
                   SmlLocation *source,
                   gsize num_changes,
                   GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %i, %p)", __func__, target, source, num_changes, error);
	CHECK_ERROR_REF
	SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_SYNC, error);
	if (!cmd)
		goto error;
	
	cmd->target = target;
	g_object_ref(target);
	
	cmd->source = source;
	g_object_ref(source);
	
	cmd->private.sync.numChanged = num_changes;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewPut (SmlLocation *target,
                  SmlLocation *source,
                  const gchar *data,
                  gsize size,
                  const gchar *contenttype,
                  GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p, %i, %s, %p)", __func__, target, source, data, size, VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	
	SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_PUT, error);
	if (!cmd)
		goto error;
	
	cmd->private.access.item = smlItemNewForData(data, size, error);
	if (!cmd->private.access.item)
		goto error;
	
	if (target) {
		cmd->target = target;
		g_object_ref(target);
		smlItemSetTarget(cmd->private.access.item, cmd->target);
	}
	
	if (source) {
		cmd->source = source;
		g_object_ref(source);
		smlItemSetSource(cmd->private.access.item, cmd->source);
	}
	
	if (!smlItemSetContentType(cmd->private.access.item, contenttype, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	if (cmd)
		smlCommandUnref(cmd);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewGet (SmlLocation *target,
                  const gchar *contenttype,
                  GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %s, %p)", __func__, target, VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	smlAssert(target);
	
	SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_GET, error);
	if (!cmd)
		goto error;
	
	cmd->private.access.item = smlItemNew(0, error);
	if (!cmd->private.access.item)
		goto error;
	
	cmd->target = target;
	g_object_ref(target);
	smlItemSetTarget(cmd->private.access.item, cmd->target);
	
	if (!smlItemSetContentType(cmd->private.access.item, contenttype, error))
		goto error;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	if (cmd)
		smlCommandUnref(cmd);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewDevInfResult (SmlCommand *cmd,
                           SmlDevInf *devinf,
                           SmlDevInfVersion version,
                           GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p, %p)", __func__, cmd, devinf, version, error);
	CHECK_ERROR_REF
	smlAssert(cmd);
	SmlLocation *source = NULL;

	char *data = NULL;
	gsize size = 0;
	if (!smlXmlDevInfAssemble(devinf, version, &data, &size, error))
		goto error;
	
	source = sml_location_new();
	if (!source) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Cannot create new SmlLocation object - out of memory.");
		goto error;
	}

	if (version == SML_DEVINF_VERSION_10) {
		sml_location_set_uri(source, "./devinf10");
	} else if (version == SML_DEVINF_VERSION_12) {
		sml_location_set_uri(source, "./devinf12");
	} else {
		sml_location_set_uri(source, "./devinf11");
	}

	SmlCommand *result = smlCommandNewResult(cmd, source, data, size, SML_ELEMENT_DEVINF_XML, error);
	if (!result)
		goto error;

	/* Since the devinf is xml, we want to send it "raw" (without cdata) */
	smlItemSetRaw(result->private.results.status->item, TRUE);
	
	g_object_unref(source);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, result);
	return result;
error:
	if (data)
		smlSafeCFree(&data);
	if (source)
		g_object_unref(source);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewDevInfPut (SmlDevInf *devinf,
                        SmlDevInfVersion version,
                        GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, devinf, version, error);
	CHECK_ERROR_REF
	smlAssert(devinf);
	SmlLocation *source = NULL;
	SmlCommand *cmd = NULL;

	// update version
	if (sml_dev_inf_get_ver_dtd(devinf) == SML_DEVINF_VERSION_UNKNOWN)
		sml_dev_inf_set_ver_dtd(devinf, version);
	
	source = sml_location_new();
	if (!source) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Cannot create new SmlLocation object - out of memory.");
		goto error;
	}

	if (version == SML_DEVINF_VERSION_10) {
		sml_location_set_uri(source, "./devinf10");
	} else if (version == SML_DEVINF_VERSION_12) {
		sml_location_set_uri(source, "./devinf12");
	} else {
		sml_location_set_uri(source, "./devinf11");
	}
		
	if (!source)
		goto error;

	cmd = smlCommandNewPut(NULL, source, NULL, 0, SML_ELEMENT_DEVINF_XML, error);
	if (!cmd)
		goto error;
	
	g_object_unref(source);
	
	char *data = NULL;
	gsize size = 0;
	if (!smlXmlDevInfAssemble(devinf, version, &data, &size, error))
		goto error;
	
	if (!smlItemAddData(cmd->private.access.item, data, size, error)) {
		smlSafeCFree(&data);
		goto error;
	}
	smlSafeCFree(&data);
	smlItemSetRaw(cmd->private.access.item, TRUE);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	if (cmd)
		smlCommandUnref(cmd);
	if (source)
		g_object_unref(source);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewDevInfGet (SmlDevInfVersion version,
                        GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %p)", __func__, version, error);
	CHECK_ERROR_REF
	SmlLocation *target = NULL;
	SmlCommand *cmd = NULL;
	
	target = sml_location_new();
	if (!target) {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC, "Cannot create new SmlLocation object - out of memory.");
		goto error;
	}

	if (version == SML_DEVINF_VERSION_10) {
		sml_location_set_uri(target, "./devinf10");
	} else if (version == SML_DEVINF_VERSION_12) {
		sml_location_set_uri(target, "./devinf12");
	} else {
		sml_location_set_uri(target, "./devinf11");
	}
		
	if (!target)
		goto error;

	cmd = smlCommandNewGet(target, SML_ELEMENT_DEVINF_XML, error);
	if (!cmd)
		goto error;
	
	g_object_unref(target);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	if (cmd)
		smlCommandUnref(cmd);
	if (target)
		g_object_unref(target);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

SmlCommand*
smlCommandNewMap (SmlLocation *target,
                  SmlLocation *source,
                  GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, target, source, error);
	CHECK_ERROR_REF
	
	SmlCommand *cmd = smlCommandNew(SML_COMMAND_TYPE_MAP, error);
	if (!cmd)
		goto error;
	
	cmd->target = target;
	g_object_ref(target);
	
	cmd->source = source;
	g_object_ref(source);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, (*error)->message);
	return NULL;
}

gboolean
smlCommandAddMapItem (SmlCommand *map,
                      SmlMapItem *item,
                      GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, map, item, error);
	CHECK_ERROR_REF
	smlAssert(map);
	smlAssert(map->type == SML_COMMAND_TYPE_MAP);
	smlAssert(item);
	
	g_object_ref(item);
	map->private.map.items = g_list_append(map->private.map.items, item);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

SmlAlertType
smlAlertTypeConvert (guint id,
                     GError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%u, %p)", __func__, id, error);
	CHECK_ERROR_REF

	if (id == 0 ||
	    id == 100 ||
	    (200 <= id && id <= 210) ||
	    (221 <= id && id <= 223))
	{
		SmlAlertType result = (SmlAlertType) id;
		smlTrace(TRACE_EXIT, "%s - %u", __func__, result);
		return result;
	} else {
		g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
			"The unknown alert code %u was detected.", id);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, (*error)->message);
		return SML_ALERT_UNKNOWN;
	}	
}

gsize
smlCommandGetID (SmlCommand *cmd)
{
	return cmd->cmdID;
}

void
smlCommandSetID (SmlCommand *cmd,
                 gsize id)
{
	cmd->cmdID = id;
}

gsize
smlCommandGetMessageID (SmlCommand *cmd)
{
	return cmd->msgID;
}

void
smlCommandSetMessageID (SmlCommand *cmd,
                 gsize id)
{
	cmd->msgID = id;
}

gsize
smlCommandGetMaxObjSize (SmlCommand *cmd)
{
	switch (cmd->type)
	{
		case SML_COMMAND_TYPE_ALERT:
			return cmd->private.alert.maxObjSize;
		case SML_COMMAND_TYPE_SYNC:
			return cmd->private.sync.maxObjSize;
		default:
			return 0;
	}
}

gsize
smlCommandGetSize (SmlCommand *cmd)
{
	return cmd->size;
}

void
smlCommandSetSize (SmlCommand *cmd,
                   gsize size)
{
	cmd->size = size;
}

void
smlCommandSetNoResp (SmlCommand *cmd,
                     gboolean noResp)
{
	cmd->noResp = noResp;
}

gboolean
smlCommandGetPushedBack (SmlCommand *cmd)
{
	return cmd->pushedBack;
}

void
smlCommandSetPushedBack (SmlCommand *cmd,
                         gboolean pushedBack)
{
	cmd->pushedBack = pushedBack;
}

SmlItem*
smlCommandGetItem(SmlCommand *cmd)
{
	switch(cmd->type)
	{
		case SML_COMMAND_TYPE_PUT:
		case SML_COMMAND_TYPE_GET:
			return cmd->private.access.item;
		case SML_COMMAND_TYPE_RESULTS:
			return cmd->private.results.status->item;
		default:
			return NULL;
	}
}

SmlCommandType
smlCommandGetType (SmlCommand *cmd)
{
	return cmd->type;
}

SmlAlertType
smlCommandGetAlertType (SmlCommand *cmd)
{
	return cmd->private.alert.type;
}

SmlChangeType
smlCommandGetChangeType (SmlCommand *cmd)
{
	return cmd->private.change.type;
}

SmlLocation*
smlCommandGetSource (SmlCommand *cmd)
{
	return cmd->source;
}

SmlLocation*
smlCommandGetTarget (SmlCommand *cmd)
{
	return cmd->target;
}

const gchar*
smlCommandGetContentType (SmlCommand *cmd)
{
	return cmd->private.alert.contentType;
}

SmlAnchor*
smlCommandGetAnchor (SmlCommand *cmd)
{
	return cmd->private.alert.anchor;
}

/* NumberOfChanges in sync command */
gsize
smlCommandGetNumberOfChanges (SmlCommand *cmd)
{
	return cmd->private.sync.numChanged;
}

gsize
smlCommandGetNumChanges (SmlCommand *cmd)
{
	smlAssert(cmd);

	return g_list_length(cmd->private.change.items);
}

SmlDataSyncChangeItem*
smlCommandGetNthChange (SmlCommand *cmd,
                        gsize n)
{
	smlAssert(cmd);
	return g_list_nth_data(cmd->private.change.items, n);
}

/* number of items in change command */
gsize
smlCommandGetNumMappings (SmlCommand *cmd)
{
	smlAssert(cmd);
	return g_list_length(cmd->private.map.items);
}

SmlMapItem*
smlCommandGetNthMapping (SmlCommand *cmd,
                         gsize n)
{
	smlAssert(cmd);
	return g_list_nth_data(cmd->private.map.items, n);
}

SmlCommand*
smlCommandCloneWithNumMappings (SmlCommand *cmd,
                                gsize n,
                                GError **error)
{
	CHECK_ERROR_REF
	smlAssert(cmd);
	smlAssert(cmd->type == SML_COMMAND_TYPE_MAP);

	SmlCommand *clone = NULL;

	/* create clone command */

	clone = smlCommandNew(cmd->type, error);
	if (!clone)
		goto error;
	clone->source = sml_location_clone(cmd->source);
	clone->target = sml_location_clone(cmd->target);

	gsize i = 0;
	for (; i < n; i++)
	{
		SmlMapItem *item = smlCommandGetNthMapping(cmd, i);
		g_object_ref(item);
		clone->private.map.items = g_list_append (clone->private.map.items, item);
	}

	return clone;
error:
	if (clone)
		smlCommandUnref(clone);
	return NULL;
}

gboolean
smlCommandRemoveNumMappings (SmlCommand *cmd,
                             gsize n,
                             GError **error)
{
	CHECK_ERROR_REF
	smlAssert(cmd);
	smlAssert(cmd->type == SML_COMMAND_TYPE_MAP);

	while (n && cmd->private.map.items)
	{
		n--;
		SmlMapItem *item = cmd->private.map.items->data;
		if (!item) {
			g_set_error(error, SML_ERROR, SML_ERROR_GENERIC,
				"The item number %d of the map command is empty.", (n+1));
			goto error;
		}
		cmd->private.map.items = g_list_remove(cmd->private.map.items, item);
		g_object_unref(item);
	}
	return TRUE;
error:
	return FALSE;
}

gsize
smlCommandGetNthItemSize (SmlCommand *cmd,
                          gsize n,
                          GError **error)
{
	smlAssert(cmd);

	SmlDataSyncChangeItem *item = g_list_nth_data(cmd->private.change.items, n);
	if (!sml_data_sync_change_item_get_data(item))
		return 0;
	return strlen(sml_data_sync_change_item_get_data(item));
}

SmlCommand*
smlCommandGetFragment (SmlCommand *orig_cmd,
                       gsize start,
                       gsize space,
                       GError **error)
{
	smlAssert(orig_cmd);

	SmlDataSyncChangeItem *frag_item = NULL;
	SmlCommand *frag_cmd = NULL;

	SmlDataSyncChangeItem *orig_item = g_list_nth_data(orig_cmd->private.change.items, 0);

	/* create item fragment */

	frag_item = sml_data_sync_change_item_get_fragment(orig_item, start, space, error);
	if (!frag_item)
		goto error;

	/* create command fragment */

	frag_cmd = smlCommandNew(orig_cmd->type, error);
	if (!frag_cmd)
		goto error;

	frag_cmd->private.change.items = g_list_append (NULL, frag_item);
	if (!frag_cmd->private.change.items)
	{
		goto error;
	}
	frag_item = NULL;

	/* If this is the first sent part of the fragmented command
	 * then this command must include the size of the complete
	 * command.
	 */
	if (start == 0)
		frag_cmd->size = strlen(sml_data_sync_change_item_get_data(orig_item));

	return frag_cmd;
error:
	if (frag_cmd)
		smlCommandUnref(frag_cmd);
	if (frag_item)
		g_object_unref(frag_item);
	return NULL;
}

SmlStatus*
smlCommandResultsGetStatus (SmlCommand *cmd)
{
	smlAssert(cmd);
	return cmd->private.results.status;
}

void
smlCommandTransferItems (SmlCommand *source,
                         SmlCommand *target,
                         gsize start)
{
	smlAssert(source);
	smlAssert(target);

	while(1) {
		SmlDataSyncChangeItem *item = g_list_nth_data(source->private.change.items, start);
		if (!item)
			return;
		target->private.change.items = g_list_append(target->private.change.items, item);
		source->private.change.items = g_list_remove(source->private.change.items, item);
	}
}

void
smlCommandFreeFirstChange (SmlCommand *cmd)
{
	smlAssert(cmd);
	SmlDataSyncChangeItem *first = smlCommandGetNthChange(cmd, 0);
	cmd->private.change.items = g_list_remove(cmd->private.change.items, first);
	g_object_unref(first);
}
