/*
 * mock-sync - A mock-plugin for the opensync 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 "mock_sync.h"

#define fail(x) abort()

#define fail_unless(condition, msg) do { \
		if (!condition) fail(msg);       \
	} while (0)

int mock_custom_function(mock_env *env, int input, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %i, %p)", __func__, env, input, error);
	
	fail_unless(input == 1, NULL);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
	return 2;
}

/*Load the state from a xml file and return it in the conn struct*/
static osync_bool mock_parse_settings(mock_env *env, char *data, int size, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %p, %i)", __func__, env, data, size);
	xmlDocPtr doc;
	xmlNodePtr cur;

	//set defaults
	env->path = NULL;

	doc = xmlParseMemory(data, size);

	if (!doc) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to parse settings");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}

	cur = xmlDocGetRootElement(doc);

	if (!cur) {
		xmlFreeDoc(doc);
		osync_error_set(error, OSYNC_ERROR_GENERIC, "Unable to get root element of the settings");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}

	if (xmlStrcmp(cur->name, (xmlChar*)"config")) {
		xmlFreeDoc(doc);
		osync_error_set(error, OSYNC_ERROR_GENERIC, "Config valid is not valid");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return FALSE;
	}

	cur = cur->xmlChildrenNode;

	while (cur != NULL) {
		char *str = (char*)xmlNodeGetContent(cur);
		if (str) {
			if (!xmlStrcmp(cur->name, (const xmlChar *)"path")) {
				env->path = g_strdup(str);
			}
			xmlFree(str);
		}
		cur = cur->next;
	}

	xmlFreeDoc(doc);
	osync_trace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static osync_bool mock_get_error(OSyncMember *member, const char *domain)
{
	const char *env = g_getenv(domain);
	if (!env)
		return FALSE;
	
	int num = atoi(env);
	int mask = 1 << (osync_member_get_id(member) - 1);
	if (num & mask) {
		char *chancestr = g_strdup_printf("%s_PROB", domain);
		const char *chance = g_getenv(chancestr);
		g_free(chancestr);
		if (!chance)
			return TRUE;
		int prob = atoi(chance);
		if (prob >= g_random_int_range(0, 100))
			return TRUE;
	}
	return FALSE;
}

static void *mock_initialize(OSyncMember *member, OSyncError **error)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, member, error);

	if (mock_get_error(member, "INIT_NULL")) {
		osync_error_set(error, OSYNC_ERROR_EXPECTED, "Triggering INIT_NULL error");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return NULL;
	}
	char *configdata;
	int configsize;
	mock_env *env = g_malloc0(sizeof(mock_env));

	if (!osync_member_get_config(member, &configdata, &configsize, error)) {
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(error));
		return NULL;
	}
	
	if (!mock_parse_settings(env, configdata, configsize, error)) {
		g_free(env);
		return NULL;
	}
	
	//Rewrite the batch commit functions so we can disable them if necessary
	if (!mock_get_error(member, "BATCH_COMMIT")) {
		OSyncObjFormatSink *fmtsink = member->format_sinks->data;
		osync_trace(TRACE_INTERNAL, "Disabling batch_commit on %p:%s: %i", fmtsink, fmtsink->format ? fmtsink->format->name : "None", g_list_length(member->format_sinks));
		OSyncFormatFunctions *functions = &(fmtsink->functions);
		functions->batch_commit = NULL;
	}
	
	env->member = member;
	env->hashtable = osync_hashtable_new();

	osync_trace(TRACE_EXIT, "%s: %p", __func__, env);
	return (void *)env;
}

static void mock_connect(OSyncContext *ctx)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, ctx);
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);

	env->committed_all = TRUE;

	if (mock_get_error(env->member, "CONNECT_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering CONNECT_ERROR error");
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, "Triggering CONNECT_ERROR error");
		return;
	}
	
	if (mock_get_error(env->member, "CONNECT_TIMEOUT")) {
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, "Triggering CONNECT_TIMEOUT error");
		return;
	}
	
	OSyncError *error = NULL;
	if (!osync_hashtable_load(env->hashtable, env->member, &error)) {
		osync_context_report_osyncerror(ctx, &error);
		osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(&error));
		osync_error_free(&error);
		return;
	}
	
	if (!osync_anchor_compare(env->member, "path", env->path))
		osync_member_set_slow_sync(env->member, "data", TRUE);
	
	GError *direrror = NULL;

	env->dir = g_dir_open(env->path, 0, &direrror);
	if (direrror) {
		//Unable to open directory
		osync_context_report_error(ctx, OSYNC_ERROR_FILE_NOT_FOUND, "Unable to open directory %s", env->path);
		g_error_free (direrror);
	} else {
		osync_context_report_success(ctx);
	}
		
	osync_trace(TRACE_EXIT, "%s", __func__);
}

static char *mock_generate_hash(struct stat *buf)
{
	char *hash = g_strdup_printf("%i-%i", (int)buf->st_mtime, (int)buf->st_ctime);
	return hash;
}

static void mock_get_changeinfo(OSyncContext *ctx)
{
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	if (mock_get_error(env->member, "GET_CHANGES_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering GET_CHANGES_ERROR error");
		return;
	}
	if (mock_get_error(env->member, "GET_CHANGES_TIMEOUT"))
		return;
	if (mock_get_error(env->member, "GET_CHANGES_TIMEOUT2"))
		sleep(8);
	
	if (osync_member_get_slow_sync(env->member, "data")) {
		osync_debug("FILE-SYNC", 3, "Slow sync requested");
		osync_hashtable_set_slow_sync(env->hashtable, "data");
	}

	GDir *dir;
	GError *gerror = NULL;
	const char *de = NULL;
	
	dir = g_dir_open(env->path, 0, &gerror);
	if (!dir) {
		osync_trace(TRACE_EXIT_ERROR, "mock_report_dir: Unable to open directory %s: %s", env->path, gerror ? gerror->message : "None");
		return;
	}
	while ((de = g_dir_read_name(dir))) {
		char *filename = g_build_filename(env->path, de, NULL);
		if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
			/* Report normal files */
			OSyncChange *change = osync_change_new();
			osync_change_set_member(change, env->member);
			osync_change_set_uid(change, de);

			osync_change_set_objformat_string(change, "mockformat");
			
			struct stat buf;
			stat(filename, &buf);
			char *hash = mock_generate_hash(&buf);
			osync_change_set_hash(change, hash);
			
			if (mock_get_error(env->member, "ONLY_INFO")) {
				osync_change_set_data(change, NULL, 0, FALSE);			
			} else {
				char *data = NULL;
				int size = 0;
				OSyncError *error = NULL;
				if (!osync_file_read(filename, &data, &size, &error)) {
					osync_context_report_osyncerror(ctx, &error);
					g_free(filename);
					return;
				}
				
				osync_change_set_data(change, data, size, TRUE);
			}
			
			if (mock_get_error(env->member, "SLOW_REPORT"))
				sleep(1);
			
			if (osync_hashtable_detect_change(env->hashtable, change)) {
				osync_context_report_change(ctx, change);
				osync_hashtable_update_hash(env->hashtable, change);
			}
			g_free(hash);
			
			
		}
	}
	g_dir_close(dir);
	osync_hashtable_report_deleted(env->hashtable, ctx, "data");
	
	fail_unless(env->committed_all == TRUE, NULL);
	env->committed_all = FALSE;
	
	osync_context_report_success(ctx);
}

static void mock_get_data(OSyncContext *ctx, OSyncChange *change)
{
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);

	if (mock_get_error(env->member, "GET_DATA_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering GET_DATA_ERROR error");
		return;
	}
	if (mock_get_error(env->member, "GET_DATA_TIMEOUT"))
		return;

	char *filename = g_strdup_printf("%s/%s", env->path, osync_change_get_uid(change));
	char *data = NULL;
	int size = 0;
	OSyncError *error = NULL;
	if (!osync_file_read(filename, &data, &size, &error)) {
		osync_context_report_osyncerror(ctx, &error);
		g_free(filename);
		return;
	}
	
	osync_change_set_data(change, data, size, TRUE);
	g_free(filename);
	
	osync_context_report_success(ctx);
}

static void mock_read(OSyncContext *ctx, OSyncChange *change)
{
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	char *filename = g_strdup_printf("%s/%s", env->path, osync_change_get_uid(change));
	
	char *data = NULL;
	int size = 0;
	OSyncError *error = NULL;
	if (!osync_file_read(filename, &data, &size, &error)) {
		osync_context_report_osyncerror(ctx, &error);
		g_free(filename);
		return;
	}
	
	osync_change_set_data(change, data, size, TRUE);

	g_free(filename);
	
	osync_context_report_success(ctx);
}

static osync_bool mock_access(OSyncContext *ctx, OSyncChange *change)
{
	/*TODO: Create directory for file, if it doesn't exist */
	osync_trace(TRACE_ENTRY, "%s(%p, %p)", __func__, ctx, change);
	
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	char *filename = NULL;
	OSyncError *error = NULL;
	filename = g_strdup_printf ("%s/%s", env->path, osync_change_get_uid(change));
		
	switch (osync_change_get_changetype(change)) {
		case CHANGE_DELETED:
			if (!remove(filename) == 0) {
				osync_debug("FILE-SYNC", 0, "Unable to remove file %s", filename);
				osync_context_report_error(ctx, OSYNC_ERROR_FILE_NOT_FOUND, "Unable to write");
				g_free(filename);
				osync_trace(TRACE_EXIT_ERROR, "%s: Unable to write", __func__);
				return FALSE;
			}
			break;
		case CHANGE_ADDED:
			if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
				osync_debug("FILE-SYNC", 0, "File %s already exists", filename);
				osync_context_report_error(ctx, OSYNC_ERROR_EXISTS, "Entry already exists");
				g_free(filename);
				osync_trace(TRACE_EXIT_ERROR, "%s: Entry already exists", __func__);
				return FALSE;
			}
			/* No break. Continue below */
		case CHANGE_MODIFIED:
			//FIXME add permission and ownership for file-sync
			if (!osync_file_write(filename, osync_change_get_data(change), osync_change_get_datasize(change), 0700, &error)) {
				osync_debug("FILE-SYNC", 0, "Unable to write to file %s", filename);
				osync_context_report_osyncerror(ctx, &error);
				g_free(filename);
				osync_trace(TRACE_EXIT_ERROR, "%s: %s", __func__, osync_error_print(&error));
				return FALSE;
			}
			
			struct stat buf;
			stat(filename, &buf);
			char *hash = mock_generate_hash(&buf);
			osync_change_set_hash(change, hash);
			break;
		default:
			fail("no changetype given");
	}
	g_free(filename);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
	return TRUE;
}

static osync_bool mock_commit_change(OSyncContext *ctx, OSyncChange *change)
{
	osync_debug("FILE-SYNC", 4, "start: %s", __func__);
	osync_debug("FILE-SYNC", 3, "Writing change %s with changetype %i", osync_change_get_uid(change), osync_change_get_changetype(change));
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	fail_unless(env->committed_all == FALSE, NULL);
	
	if (mock_get_error(env->member, "COMMIT_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering COMMIT_ERROR error");
		return FALSE;
	}
	if (mock_get_error(env->member, "COMMIT_TIMEOUT"))
		return FALSE;
	
	if (!mock_access(ctx, change))
		return FALSE;

	osync_hashtable_update_hash(env->hashtable, change);
	osync_context_report_success(ctx);
	osync_debug("FILE-SYNC", 4, "end: %s", __func__);
	return TRUE;
}

static void mock_sync_done(OSyncContext *ctx)
{
	osync_debug("FILE-SYNC", 3, "start: %s", __func__);
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	if (mock_get_error(env->member, "SYNC_DONE_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering SYNC_DONE_ERROR error");
		return;
	}
	if (mock_get_error(env->member, "SYNC_DONE_TIMEOUT"))
		return;
	
	osync_anchor_update(env->member, "path", env->path);
	osync_context_report_success(ctx);
	osync_debug("FILE-SYNC", 3, "end: %s", __func__);
}

static void mock_disconnect(OSyncContext *ctx)
{
	osync_debug("FILE-SYNC", 3, "start: %s", __func__);
	mock_env *env = (mock_env *)osync_context_get_plugin_data(ctx);
	
	if (!g_getenv("NO_COMMITTED_ALL_CHECK"))
		fail_unless(env->committed_all == TRUE, NULL);
	env->committed_all = FALSE;
	
	if (mock_get_error(env->member, "DISCONNECT_ERROR")) {
		osync_context_report_error(ctx, OSYNC_ERROR_EXPECTED, "Triggering DISCONNECT_ERROR error");
		return;
	}
	if (mock_get_error(env->member, "DISCONNECT_TIMEOUT"))
		return;
	
	g_dir_close(env->dir);
	osync_hashtable_close(env->hashtable);
	osync_context_report_success(ctx);
	osync_debug("FILE-SYNC", 3, "end: %s", __func__);
}

static void mock_finalize(void *data)
{
	osync_debug("FILE-SYNC", 3, "start: %s", __func__);
	mock_env *env = (mock_env *)data;
	osync_hashtable_free(env->hashtable);

	g_free(env->path);
	g_free(env);
}

static osync_bool mock_is_available(OSyncError **error)
{
	if (g_getenv("IS_NOT_AVAILABLE")) {
		osync_error_set(error, OSYNC_ERROR_GENERIC, "file-sync plugin is not available");
		return FALSE;
	}
	return TRUE;
}

static void mock_batch_commit(OSyncContext *context, OSyncContext **contexts, OSyncChange **changes)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %p, %p)", __func__, context, contexts, changes);
	mock_env *env = (mock_env *)osync_context_get_plugin_data(context);
	
	fail_unless(env->committed_all == FALSE, NULL);
	env->committed_all = TRUE;
	
	int i;
	for (i = 0; contexts[i]; i++) {
		if (mock_access(contexts[i], changes[i])) {
			osync_hashtable_update_hash(env->hashtable, changes[i]);
			osync_context_report_success(contexts[i]);
		}
	}
	
	if (g_getenv("NUM_BATCH_COMMITS")) {
		int req = atoi(g_getenv("NUM_BATCH_COMMITS"));
		fail_unless(req == i, NULL);
	}
		
	if (mock_get_error(env->member, "COMMITTED_ALL_ERROR")) {
		osync_context_report_error(context, OSYNC_ERROR_EXPECTED, "Triggering COMMITTED_ALL_ERROR error");
		return;
	}
	
	osync_context_report_success(context);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
}

static void mock_committed_all(OSyncContext *context)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, context);
	mock_env *env = (mock_env *)osync_context_get_plugin_data(context);
	
	fail_unless(env->committed_all == FALSE, NULL);
	env->committed_all = TRUE;
	
	if (mock_get_error(env->member, "COMMITTED_ALL_ERROR")) {
		osync_context_report_error(context, OSYNC_ERROR_EXPECTED, "Triggering COMMITTED_ALL_ERROR error");
		osync_trace(TRACE_EXIT_ERROR, "%s: Reporting error", __func__);
		return;
	}
	
	osync_context_report_success(context);
	
	osync_trace(TRACE_EXIT, "%s", __func__);
}

void get_info(OSyncEnv *env)
{
	OSyncPluginInfo *info = osync_plugin_new_info(env);
	
	info->name = "file-sync";
	info->longname = "Mock Plugin";
	info->description = "Mock Plugin";
	info->version = 1;

	info->functions.initialize = mock_initialize;
	info->functions.connect = mock_connect;
	info->functions.sync_done = mock_sync_done;
	info->functions.disconnect = mock_disconnect;
	info->functions.finalize = mock_finalize;
	info->functions.get_changeinfo = mock_get_changeinfo;
	info->functions.get_data = mock_get_data;

	osync_plugin_accept_objtype(info, "data");
	osync_plugin_accept_objformat(info, "data", "mockformat", NULL);
	
	osync_plugin_set_access_objformat(info, "data", "mockformat", mock_access);
	osync_plugin_set_read_objformat(info, "data", "mockformat", mock_read);

	//Lets reduce the timeouts a bit so the checks work faster
	info->timeouts.disconnect_timeout = 5;
	info->timeouts.connect_timeout = 5;
	info->timeouts.sync_done_timeout = 5;
	info->timeouts.get_changeinfo_timeout = 5;
	info->timeouts.get_data_timeout = 5;
	info->timeouts.commit_timeout = 15;
	
	
	if (g_getenv("NO_TIMEOUTS")) {
		info->timeouts.disconnect_timeout = 0;
		info->timeouts.connect_timeout = 0;
		info->timeouts.sync_done_timeout = 0;
		info->timeouts.get_changeinfo_timeout = 0;
		info->timeouts.get_data_timeout = 0;
		info->timeouts.commit_timeout = 0;
	}
	
	if (g_getenv("IS_AVAILABLE"))
		info->functions.is_available = mock_is_available;
	
	osync_plugin_set_batch_commit_objformat(info, "data", "mockformat", mock_batch_commit);
	osync_plugin_set_commit_objformat(info, "data", "mockformat", mock_commit_change);
	osync_plugin_set_committed_all_objformat(info, "data", "mockformat", mock_committed_all);
}


syntax highlighted by Code2HTML, v. 0.9.1