/*
 * 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_parse_internals.h"
#include "sml_command_internals.h"
#include "sml_session_internals.h"
#include "sml_elements_internals.h"
#include "sml_error_internals.h"

SmlCommandType smlCommandTypeFromString(const char *name, SmlError **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;
	}
	
	smlErrorSet(error, SML_ERROR_GENERIC, "Unknown command name \"%s\"", name);
	return SML_COMMAND_TYPE_UNKNOWN;
}

const char *smlCommandTypeToString(SmlCommandType type, SmlError **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:
			;
	}
		
	smlErrorSet(error, SML_ERROR_GENERIC, "Unknown command type \"%i\"", type);
	return NULL;
}

SmlStatus *smlStatusNew(SmlErrorType data, unsigned int cmdref, unsigned int msgref, SmlLocation *sourceref, SmlLocation *targetref, SmlCommandType type, SmlError **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;
		smlLocationRef(sourceref);
	}
	
	if (targetref) {
		status->targetRef = targetref;
		smlLocationRef(targetref);
	}
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, status);
	return status;

error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	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)
			smlLocationUnref(status->sourceRef);
	
		if (status->targetRef)
			smlLocationUnref(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;
}

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

SmlCommand *smlCommandNew(SmlCommandType type, SmlError **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__, smlErrorPrint(error));
	return NULL;
}

SmlStatus *smlCommandNewReply(const SmlCommand *cmd, SmlErrorType code, SmlError **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, cmd->private.alert.anchor->next, 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__, smlErrorPrint(error));
	return NULL;
}

SmlCommand *smlCommandNewResult(SmlCommand *cmd, SmlLocation *source, char *data, unsigned int size, const char *contenttype, SmlError **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_free_cmd;
		
	result->private.results.status->item = smlItemNewForData(data, size, error);
	smlSafeCFree(&data);
	if (!result->private.results.status->item)
		goto error_free_cmd;
	
	result->private.results.status->item->contenttype = g_strdup(contenttype);
	result->private.results.status->item->source = smlLocationClone(source, error);
	
	if (!result->private.results.status->item->source)
		goto error_free_cmd;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, result);
	return result;
	
error_free_cmd:
	smlCommandUnref(result);
error:
	if (data)
		smlSafeCFree(&data);
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	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.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++)
					{
						SmlItem *item = g_list_nth_data(cmd->private.change.items, i);
						smlItemUnref(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;
					smlMapItemUnref(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)
			smlLocationUnref(cmd->target);
		
		if (cmd->source)
			smlLocationUnref(cmd->source);
		
		smlSafeFree((gpointer *)&cmd);
	}
	
	smlTrace(TRACE_EXIT, "%s", __func__);
}

SmlCommand *smlCommandNewChange(SmlChangeType type, const char *uid, const char *data, unsigned int size, const char *contenttype, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %s, %p, %i, %s, %p)", __func__, type, VA_STRING(uid), data, size, VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	SmlCommand *cmd = NULL;
	
	switch (type) {
		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:
			smlErrorSet(error, SML_ERROR_GENERIC, "Unknown changetype");
	}
	if (!cmd)
		goto error;

	SmlItem *item = smlItemNewForData(data, size, error);
	if (!item)
		goto error_free_cmd;
	cmd->private.change.items = g_list_append(NULL, item);
	if (!cmd->private.change.items)
	{
		smlItemUnref(item);
		goto error_free_cmd;
	}
	
	SmlLocation *loc = smlLocationNew(uid, NULL, error);
	if (!loc)
		goto error_free_cmd;
	
	/* 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.
	 */
	item->source = loc;
	if (type != SML_CHANGE_ADD) {
		/* If this is an Add command then nobody knows
		 * which ID the remote peer will be use for this item.
		 */
		smlLocationRef(loc);
		item->target = loc;
	}
	
	item->contenttype = g_strdup(contenttype);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

/** Send a fragmented change. You can use this command to fragment a very large change into several
 * objects.
 * @param complete_size The overall size of the object. must be the sum over all partial_sizes
 * @param partial_size The size of this part.
 */
SmlCommand *smlCommandNewPartialChange(SmlChangeType type, const char *uid, const char *data, unsigned int complete_size, unsigned int partial_size, const char *contenttype, SmlError **error)
{
	smlTrace(TRACE_ENTRY, "%s(%i, %s, %p, %i, %i, %s, %p)", __func__, type, VA_STRING(uid), data, complete_size, partial_size, VA_STRING(contenttype), error);
	CHECK_ERROR_REF
	SmlCommand *cmd = NULL;
	
	switch (type) {
		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:
			smlErrorSet(error, SML_ERROR_GENERIC, "Unknown changetype");
	}
	if (!cmd)
		goto error;
	cmd->size = complete_size;

	SmlItem *item = smlItemNewForData(data, partial_size, error);
	if (!item)
		goto error_free_cmd;
	cmd->private.change.items = g_list_append(NULL, item);
	if (!cmd->private.change.items)
	{
		smlItemUnref(item);
		goto error_free_cmd;
	}
	
	SmlLocation *loc = smlLocationNew(uid, NULL, error);
	if (!loc)
		goto error_free_cmd;
	
	if (type != SML_CHANGE_ADD)
		item->target = loc;
	else
		item->source = loc;
	
	item->moreData = TRUE;
	item->contenttype = g_strdup(contenttype);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlCommand *smlCommandNewAlert(SmlAlertType type, SmlLocation *target, SmlLocation *source, const char *next, const char *last, const char *contenttype, SmlError **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;
		smlLocationRef(target);
	}
	
	if (source) {
		cmd->source = source;
		smlLocationRef(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__, smlErrorPrint(error));
	return NULL;
}

SmlCommand *smlCommandNewSync(SmlLocation *target, SmlLocation *source, unsigned int num_changes, SmlError **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;
	smlLocationRef(target);
	
	cmd->source = source;
	smlLocationRef(source);
	
	cmd->private.sync.numChanged = num_changes;
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

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

SmlCommand *smlCommandNewPut(SmlLocation *target, SmlLocation *source, const char *data, unsigned int size, const char *contenttype, SmlError **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_free_cmd;
	
	if (target) {
		cmd->target = target;
		smlLocationRef(target);
		smlItemSetTarget(cmd->private.access.item, cmd->target);
	}
	
	if (source) {
		cmd->source = source;
		smlLocationRef(source);
		smlItemSetSource(cmd->private.access.item, cmd->source);
	}
	
	cmd->private.access.item->contenttype = g_strdup(contenttype);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlCommand *smlCommandNewGet(SmlLocation *target, const char *contenttype, SmlError **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_free_cmd;
	
	cmd->target = target;
	smlLocationRef(target);
	smlItemSetTarget(cmd->private.access.item, cmd->target);
	
	cmd->private.access.item->contenttype = g_strdup(contenttype);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

error_free_cmd:
	smlCommandUnref(cmd);
error:
	smlTrace(TRACE_EXIT_ERROR, "%s: %s", __func__, smlErrorPrint(error));
	return NULL;
}

SmlCommand *smlCommandNewMap(SmlLocation *target, SmlLocation *source, SmlError **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;
	smlLocationRef(target);
	
	cmd->source = source;
	smlLocationRef(source);
	
	smlTrace(TRACE_EXIT, "%s: %p", __func__, cmd);
	return cmd;

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

SmlBool smlCommandAddMapItem(SmlCommand *map, SmlMapItem *item, SmlError **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);
	
	smlMapItemRef(item);
	map->private.map.items = g_list_append(map->private.map.items, item);
	
	smlTrace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

SmlAlertType smlAlertTypeConvert(unsigned int id, SmlError **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 {
		smlErrorSet(error, SML_ERROR_GENERIC,
			"The unknown alert code %u was detected.", id);
		smlTrace(TRACE_EXIT_ERROR, "%s - %s", __func__, smlErrorPrint(error));
		return SML_ALERT_UNKNOWN;
	}	
}

