/*
 * libopensync - A synchronization framework
 * Copyright (C) 2004-2005  Armin Bauer <armin.bauer@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 "opensync.h"
#include "opensync_internals.h"


/** Function used on a path search for a format name array
 *
 * @see osync_conv_find_path_fn(), osync_change_convert_fmtnames()
 */
static osync_bool target_fn_fmtnames(const void *data, OSyncObjFormat *fmt)
{
	const char * const *list = data;
	const char * const *i;
	for (i = list; *i; i++) {
		if (!strcmp(fmt->name, *i))
			/* Found */
			return TRUE;
	}

	/* Not found */
	return FALSE;
}

/** Function used on path searchs for a single format
 *
 * @see osync_conv_find_path_fn(), osync_change_convert()
 */
static osync_bool target_fn_simple(const void *data, OSyncObjFormat *fmt)
{
	const OSyncObjFormat *target = data;
	return target == fmt;
}

/** Function used on path searchs for a single format name
 *
 * @see osync_conv_find_path_fn(), osync_change_convert_fmtname()
 */
static osync_bool target_fn_fmtname(const void *data, OSyncObjFormat *fmt)
{
	const char *name = data;
	return !strcmp(name, fmt->name);
}

/** Function used on path searchs for a sink on a member
 *
 * @see osync_conv_find_path_fn(), osync_change_convert_member_sink()
 */
static osync_bool target_fn_membersink(const void *data, OSyncObjFormat *fmt)
{
	const OSyncMember *memb = data;
	GList *i;
	for (i = memb->format_sinks; i; i = i->next) {
		OSyncObjFormatSink *sink = i->data;
		if (sink->format == fmt)
			return TRUE;
	}

	/* Not found */
	return FALSE;
}

/**
 * @defgroup OSyncChangeCmds OpenSync Change Commands
 * @ingroup OSyncPublic
 * @brief High level functions to manipulate changes
 */
/*@{*/

/*! @brief Returns a string describing a change object
 * 
 * Some formats cannot be printed directly. To be able to print these
 * objects they should specify a print function.
 * 
 * @param change The change to get printable
 * @returns A string describing the object
 * 
 */
char *osync_change_get_printable(OSyncChange *change)
{
	g_assert(change);
	if (!change->has_data)
		return NULL;
		
	OSyncObjFormat *format = osync_change_get_objformat(change);
	g_assert(format);
	
	if (!format->print_func)
		return g_strndup(change->data, change->size);
		
	return format->print_func(change);
}

/*! @brief Returns the revision of the object
 * 
 * @param change The change to get the revision from
 * @param error A error struct
 * @returns The revision of the object in seconds since the epoch in zulu time
 * 
 */
time_t osync_change_get_revision(OSyncChange *change, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, change, error);
	
	g_assert(change);
	if (!change->has_data) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "No data set when asking for the timestamp");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return -1;
	}
	
	OSyncObjFormat *format = osync_change_get_objformat(change);
	g_assert(format);
	
	if (!format->revision_func) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "No revision function set");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return -1;
	}
		
	time_t time = format->revision_func(change, error);
	
	osync_trace(osync_error_is_set(error) ? TRACE_EXIT_ERROR : TRACE_EXIT, "%s: %s, %i", __func__, osync_error_print(error), time);
	return time;
}

/*! @brief Compares the data of 2 changes
 * 
 * Compares the two given changes and returns:
 * CONV_DATA_MISMATCH if they are not the same
 * CONV_DATA_SIMILAR if the are not the same but look similar
 * CONV_DATA_SAME if they are exactly the same
 * 
 * @param leftchange The left change to compare
 * @param rightchange The right change to compare
 * @returns The result of the comparison
 * 
 */
OSyncConvCmpResult osync_change_compare_data(OSyncChange *leftchange, OSyncChange *rightchange)
{
	osync_trace(TRACE_ENTRY, "osync_change_compare_data(%p, %p)", leftchange, rightchange);
	
	g_assert(rightchange);
	g_assert(leftchange);

	OSyncError *error = NULL;
	if (!osync_change_convert_to_common(leftchange, &error)) {
		osync_trace(TRACE_INTERNAL, "osync_change_compare_data: %s", osync_error_print(&error));
		osync_error_free(&error);
		osync_trace(TRACE_EXIT, "osync_change_compare_data: MISMATCH: Could not convert leftchange to common format");
		return CONV_DATA_MISMATCH;
	}
	if (!osync_change_convert_to_common(rightchange, &error)) {
		osync_trace(TRACE_INTERNAL, "osync_change_compare_data: %s", osync_error_print(&error));
		osync_error_free(&error);
		osync_trace(TRACE_EXIT, "osync_change_compare_data: MISMATCH: Could not convert rightchange to common format");
		return CONV_DATA_MISMATCH;
	}

	if (!(rightchange->data == leftchange->data)) {
		if (!(osync_change_get_objtype(leftchange) == osync_change_get_objtype(rightchange))) {
			osync_trace(TRACE_EXIT, "osync_change_compare_data: MISMATCH: Objtypes do not match");
			return CONV_DATA_MISMATCH;
		}
		if (leftchange->format != rightchange->format) {
			osync_trace(TRACE_EXIT, "osync_change_compare_data: MISMATCH: Objformats do not match");
			return CONV_DATA_MISMATCH;
		}
		if (!rightchange->data || !leftchange->data) {
			osync_trace(TRACE_EXIT, "osync_change_compare_data: MISMATCH: One change has no data");
			return CONV_DATA_MISMATCH;
		}
		OSyncObjFormat *format = osync_change_get_objformat(leftchange);
		g_assert(format);
		
		OSyncConvCmpResult ret = format->cmp_func(leftchange, rightchange);
		osync_trace(TRACE_EXIT, "osync_change_compare_data: %i", ret);
		return ret;
	} else {
		osync_trace(TRACE_EXIT, "osync_change_compare_data: SAME: OK. data point to same memory");
		return CONV_DATA_SAME;
	}
}

/*! @brief Compares 2 changes
 * 
 * Compares the two given changes and returns:
 * CONV_DATA_MISMATCH if they are not the same
 * CONV_DATA_SIMILAR if the are not the same but look similar
 * CONV_DATA_SAME if they are exactly the same
 * This function does also compare changetypes etc unlike
 * osync_change_compare_data()
 * 
 * @param leftchange The left change to compare
 * @param rightchange The right change to compare
 * @returns The result of the comparison
 * 
 */
OSyncConvCmpResult osync_change_compare(OSyncChange *leftchange, OSyncChange *rightchange)
{
	osync_trace(TRACE_ENTRY, "osync_change_compare(%p, %p)", leftchange, rightchange);
	
	g_assert(rightchange);
	g_assert(leftchange);

	OSyncError *error = NULL;
	if (!osync_change_convert_to_common(leftchange, &error)) {
		osync_trace(TRACE_INTERNAL, "osync_change_compare: %s", osync_error_print(&error));
		osync_error_free(&error);
		osync_trace(TRACE_EXIT, "osync_change_compare: MISMATCH: Could not convert leftchange to common format");
		return CONV_DATA_MISMATCH;
	}
	if (!osync_change_convert_to_common(rightchange, &error)) {
		osync_trace(TRACE_INTERNAL, "osync_change_compare: %s", osync_error_print(&error));
		osync_error_free(&error);
		osync_trace(TRACE_EXIT, "osync_change_compare: MISMATCH: Could not convert leftchange to common format");
		return CONV_DATA_MISMATCH;
	}

	if (rightchange->changetype == leftchange->changetype) {
		OSyncConvCmpResult ret = osync_change_compare_data(leftchange, rightchange);
		osync_trace(TRACE_EXIT, "osync_change_compare: Compare data: %i", ret);
		return ret;
	} else {
		osync_trace(TRACE_EXIT, "osync_change_compare: MISMATCH: Change types do not match");
		return CONV_DATA_MISMATCH;
	}
}

/*! @brief Copies the data from one change to another change
 * 
 * @param source The change to copy from
 * @param target The change to copy to
 * @param error A error struct
 * @returns TRUE if the copy was successfull
 * 
 */
osync_bool osync_change_copy_data(OSyncChange *source, OSyncChange *target, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_copy_data(%p, %p, %p)", source, target, error);
	
	OSyncObjFormat *format = NULL;
	format = source->format;
	if (!format)
		format = target->format;
	
	if (target->data)
		osync_change_free_data(target);
	
	if (!source->data) {
		target->data = NULL;
		target->size = 0;
		osync_trace(TRACE_EXIT, "%s: Source had not data", __func__);
		return TRUE;
	}
	
	if (!format || !format->copy_func) {
		osync_trace(TRACE_INTERNAL, "We cannot copy the change, falling back to memcpy");
		target->data = g_malloc0(sizeof(char) * (source->size + 1));
		memcpy(target->data, source->data, source->size);

		/* Make sure that its null terminated */
		target->data[source->size] = 0;
		target->size = source->size;
	} else {
		if (!format->copy_func(source->data, source->size, &(target->data), &(target->size))) {
			osync_error_set(error, OSYNC_ERROR_GENERIC, "Something went wrong during copying");
			osync_trace(TRACE_EXIT_ERROR, "osync_change_copy_data: %s", osync_error_print(error));
			return FALSE;
		}
	}
	
	osync_trace(TRACE_EXIT, "osync_change_copy_data");
	return TRUE;
}

/*! @brief Makes a exact copy of change
 * 
 * @param source The change to copy from
 * @param error A error struct
 * @returns A new change that is the copy, NULL on error
 * 
 */
OSyncChange *osync_change_copy(OSyncChange *source, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_copy(%p, %p)", source, error);
	g_assert(source);
	
	OSyncChange *newchange = osync_change_new();
	newchange->uid = g_strdup(source->uid);
	newchange->hash = g_strdup(source->hash);
	
	newchange->has_data = source->has_data;
	newchange->changetype = source->changetype;
	newchange->format = osync_change_get_objformat(source);
	newchange->objtype = osync_change_get_objtype(source);
	newchange->sourceobjtype = g_strdup(osync_change_get_objtype(source)->name);
	newchange->changes_db = source->changes_db;
	newchange->member = source->member;
	
	if (!osync_change_copy_data(source, newchange, error)) {
		osync_change_free(newchange);
		osync_trace(TRACE_EXIT_ERROR, "osync_change_copy: %s", osync_error_print(error));
		return NULL;
	}

	osync_trace(TRACE_EXIT, "osync_change_copy: %p", newchange);
	return newchange;
}

/*! @brief Duplicates the uid of the change
 * 
 * This will call the duplicate function of a format.
 * This is used if a uid is not unique.
 * 
 * @param change The change to duplicate
 * @returns TRUE if the uid was duplicated successfull
 * 
 */
osync_bool osync_change_duplicate(OSyncChange *change)
{
	g_assert(change);
	OSyncObjFormat *format = osync_change_get_objformat(change);
	osync_debug("OSCONV", 3, "Duplicating change %s with format %s\n", change->uid, format->name);
	if (!format || !format->duplicate_func)
		return FALSE;
	format->duplicate_func(change);
	return TRUE;
}

/*! @brief Convert a change to a specific format with a specific extension
 * 
 * This will convert the change with its data to the specified format
 * if possible. And this will also convert the data to the specified format
 * extension.
 * 
 * @param env The conversion environment to use
 * @param change The change to convert
 * @param targetformat To which format you want to convert to
 * @param extension_name The name of the extension
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert_extension(OSyncFormatEnv *env, OSyncChange *change, OSyncObjFormat *targetformat, const char *extension_name, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_convert(%p, %p, %p:%s, %s, %p)", env, change, targetformat, targetformat ? targetformat->name : "NONE", extension_name, error);
	if (osync_conv_convert_fn(env, change, target_fn_simple, targetformat, extension_name, error)) {
		osync_trace(TRACE_EXIT, "osync_change_convert: TRUE");
		return TRUE;
	} else {
		osync_trace(TRACE_EXIT_ERROR, "osync_change_convert: %s", osync_error_print(error));
		return FALSE;
	}
}

/*! @brief Convert a change to a specific format
 * 
 * This will convert the change with its data to the specified format
 * if possible.
 * 
 * @param env The conversion environment to use
 * @param change The change to convert
 * @param targetformat To which format you want to convert to
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert(OSyncFormatEnv *env, OSyncChange *change, OSyncObjFormat *targetformat, OSyncError **error)
{
	return osync_change_convert_extension(env, change, targetformat, NULL, error);
}

/*! @brief Convert a change to the specified common format
 * 
 * @param change The change to convert
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert_to_common(OSyncChange *change, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_convert_to_common(%p, %p)", change, error);
	
	if (!osync_change_get_objtype(change)) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "The change has no objtype");
		osync_trace(TRACE_EXIT_ERROR, "osync_change_convert_to_common: %s", osync_error_print(error));
		return FALSE;
	}
	OSyncFormatEnv *env = osync_change_get_objtype(change)->env;
	
	if (!osync_change_get_objtype(change)->common_format) {
		osync_trace(TRACE_EXIT, "osync_change_convert_to_common: No common format set");
		return TRUE;
	}
	
	osync_trace(TRACE_INTERNAL, "Converting from %s to %s", osync_change_get_objformat(change)->name, osync_change_get_objtype(change)->common_format->name);
	
	if (osync_change_convert(env, change, osync_change_get_objtype(change)->common_format, error)) {
		osync_trace(TRACE_EXIT, "osync_change_convert_to_common: TRUE");
		return TRUE;
	} else {
		osync_trace(TRACE_EXIT_ERROR, "osync_change_convert_to_common: %s", osync_error_print(error));
		return FALSE;
	}
}

/*! @brief Convert a change to a specific format with the given name
 * 
 * This will convert the change with its data to the specified format
 * if possible.
 * 
 * @param env The conversion environment to use
 * @param change The change to convert
 * @param targetname To which format you want to convert to
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert_fmtname(OSyncFormatEnv *env, OSyncChange *change, const char *targetname, OSyncError **error)
{
	return osync_conv_convert_fn(env, change, target_fn_fmtname, targetname, NULL, error);
}

/*! @brief Convert a change to some formats
 * 
 * This will convert the change with its data to one of the specified formats
 * if possible.
 * 
 * @param env The conversion environment to use
 * @param change The change to convert
 * @param targetnames NULL-Terminated array of formatnames
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert_fmtnames(OSyncFormatEnv *env, OSyncChange *change, const char **targetnames, OSyncError **error)
{
	return osync_conv_convert_fn(env, change, target_fn_fmtnames, targetnames, NULL, error);
}

/*! @brief Convert a change to the nearest sink on a member
 * 
 * 
 * @param env The conversion environment to use
 * @param change The change to convert
 * @param member The member that will receive the change
 * @param error The error-return location
 * @returns TRUE on success, FALSE otherwise
 * 
 */
osync_bool osync_change_convert_member_sink(OSyncFormatEnv *env, OSyncChange *change, OSyncMember *member, OSyncError **error)
{
	if (!osync_member_require_sink_info(member, error))
		return FALSE;

	return osync_conv_convert_fn(env, change, target_fn_membersink, member, member->extension, error);
}

/*! @brief Tries to detect the object type of the given change
 * 
 * This will try to detect the object type of the data on the change
 * and return it, but not set it.
 * 
 * @param env The conversion environment to use
 * @param change The change to detect
 * @param error The error-return location
 * @returns The objecttype on success, NULL otherwise
 * 
 */
OSyncObjType *osync_change_detect_objtype(OSyncFormatEnv *env, OSyncChange *change, OSyncError **error)
{
	OSyncObjFormat *format = NULL;
	if (!(format = osync_change_detect_objformat(env, change, error)))
		return NULL;
	return format->objtype;
}

/*! @brief Tries to detect the encapsulated object type of the given change
 * 
 * This will try to detect the encapsulated object type of the data on the change
 * and return it, but not set it. This will try to detect the change, deencapsulate
 * it, detect again etc until it cannot deencapsulate further.
 * 
 * @param env The conversion environment to use
 * @param change The change to detect
 * @param error The error-return location
 * @returns The objecttype on success, NULL otherwise
 * 
 */
OSyncObjType *osync_change_detect_objtype_full(OSyncFormatEnv *env, OSyncChange *change, OSyncError **error)
{
	OSyncObjFormat *format = NULL;
	if (!(format = osync_change_detect_objformat_full(env, change, error)))
		return NULL;
	return format->objtype;
}

/*! @brief Tries to detect the format of the given change
 * 
 * This will try to detect the format of the data on the change
 * and return it, but not set it.
 * 
 * @param env The conversion environment to use
 * @param change The change to detect
 * @param error The error-return location
 * @returns The format on success, NULL otherwise
 * 
 */
OSyncObjFormat *osync_change_detect_objformat(OSyncFormatEnv *env, OSyncChange *change, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_detect_objformat(%p, %p, %p)", env, change, error);
	if (!change->has_data) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "The change has no data");
		osync_trace(TRACE_EXIT_ERROR, "osync_change_detect_objformat: %s", osync_error_print(error));
		return NULL;
	}
	
	//Run all datadetectors for our source type
	GList *d = NULL;
	for (d = env->converters; d; d = d->next) {
		OSyncFormatConverter *converter = d->data;
		osync_trace(TRACE_INTERNAL, "running detector %s for format %s\n", converter->source_format->name, osync_change_get_objformat(change)->name);
		if (!strcmp(converter->source_format->name, osync_change_get_objformat(change)->name)) {
			if (converter->detect_func && converter->detect_func(env, change->data, change->size)) {
				osync_trace(TRACE_EXIT, "osync_change_detect_objformat: %p:%s", converter->target_format, converter->target_format->name);
				return converter->target_format;
			}
		}
	}
	
	osync_error_set(error, OSYNC_ERROR_GENERIC, "None of the detectors was able to recognize this data");
	osync_trace(TRACE_EXIT_ERROR, "osync_change_detect_objformat: %s", osync_error_print(error));
	return NULL;
}

/*! @brief Tries to detect the encapsulated format of the given change
 * 
 * This will try to detect the encapsulated format of the data on the change
 * and return it, but not set it. This will try to detect the change, deencapsulate
 * it, detect again etc until it cannot deencapsulate further.
 * 
 * @param env The conversion environment to use
 * @param change The change to detect
 * @param error The error-return location
 * @returns The format on success, NULL otherwise
 * 
 */
OSyncObjFormat *osync_change_detect_objformat_full(OSyncFormatEnv *env, OSyncChange *change, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "osync_change_detect_objformat_full(%p, %p, %p)", env, change, error);
	if (!change->has_data) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "The change has no data");
		osync_trace(TRACE_EXIT_ERROR, "osync_change_detect_objformat: %s", osync_error_print(error));
		return NULL;
	}
	OSyncChange *new_change = change;
	
	//Try to decap the change as far as possible
	while (1) {
		GList *d = NULL;
		for (d = env->converters; d; d = d->next) {
			OSyncFormatConverter *converter = d->data;
			if (!strcmp(converter->source_format->name, osync_change_get_objformat(change)->name) && converter->type == CONVERTER_DECAP) {
				osync_bool free_output = FALSE;
				if (!(new_change = osync_converter_invoke_decap(converter, new_change, &free_output))) {
					osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to decap the change");
					osync_trace(TRACE_EXIT_ERROR, "osync_change_detect_objformat_full: %s", osync_error_print(error));
					return NULL;
				}
				continue;
			}
		}
		break;
	}

	OSyncObjFormat *ret = osync_change_detect_objformat(env, new_change, error);
	if (!ret)
		osync_trace(TRACE_EXIT_ERROR, "osync_change_detect_objformat_full: %s", osync_error_print(error));
	else
		osync_trace(TRACE_EXIT, "osync_change_detect_objformat_full: %p:%s", ret, ret->name);
	return ret;
}

/*@}*/


syntax highlighted by Code2HTML, v. 0.9.1