#include "support.h"

char *olddir = NULL;

static void reset_env(void)
{
	unsetenv("CONNECT_ERROR");
	unsetenv("CONNECT_TIMEOUT");
	unsetenv("INIT_NULL");
	unsetenv("GET_CHANGES_ERROR");
	unsetenv("GET_CHANGES_TIMEOUT");
	unsetenv("GET_CHANGES_TIMEOUT2");
	unsetenv("COMMIT_ERROR");
	unsetenv("COMMIT_TIMEOUT");
	unsetenv("SYNC_DONE_ERROR");
	unsetenv("SYNC_DONE_TIMEOUT");
	unsetenv("DISCONNECT_ERROR");
	unsetenv("DISCONNECT_TIMEOUT");
}

char *setup_testbed(char *fkt_name)
{
	
	setuid(65534);
	char *testbed = g_strdup_printf("%s/testbed.XXXXXX", g_get_tmp_dir());
	mkdtemp(testbed);
	
	char *command = NULL;
	if (fkt_name) {
		command = g_strdup_printf("cp -R "OPENSYNC_TESTDATA"data/%s/* %s", fkt_name, testbed);
		if (system(command))
			abort();
		g_free(command);
	}
	
	command = g_strdup_printf("cp -R ../osplugin/osplugin %s", testbed);
	if (system(command))
		abort();
	g_free(command);
	
	command = g_strdup_printf("cp -R mock-plugin/.libs/*.so %s", testbed);
	if (system(command))
		abort();
	g_free(command);
	
	command = g_strdup_printf("cp -R ../formats/.libs/*.so %s", testbed);
	if (system(command))
		abort();
	g_free(command);
	
	command = g_strdup_printf("cp -R ../formats/vformats-xml/.libs/*.so %s", testbed);
	if (system(command))
		abort();
	g_free(command);
	
	command = g_strdup_printf("chmod -R 700 %s", testbed);
	if (system(command))
		abort();
	g_free(command);
		
	olddir = g_get_current_dir();
	if (chdir(testbed))
		abort();
	
	osync_trace(TRACE_INTERNAL, "Seting up %s at %s", fkt_name, testbed);

//	printf(".");
//	fflush(NULL);
//	fflush(stderr);
	reset_env();
	return testbed;
}

void destroy_testbed(char *path)
{
	char *command = g_strdup_printf("rm -rf %s", path);
	if (olddir)
		chdir(olddir);
	system(command);
	g_free(command);
	osync_trace(TRACE_INTERNAL, "Tearing down %s", path);
	g_free(path);
}

void conflict_handler_choose_first(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);
	
	OSyncChange *change = osengine_mapping_nth_change(mapping, 0);
	osengine_mapping_solve(engine, mapping, change);
}

void conflict_handler_choose_modified(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);
	
	int i;
	for (i = 0; i < osengine_mapping_num_changes(mapping); i++) {
		OSyncChange *change = osengine_mapping_nth_change(mapping, i);
		if (change->changetype == CHANGE_MODIFIED) {
			osengine_mapping_solve(engine, mapping, change);
			return;
		}
	}
	fail();
}

void conflict_handler_choose_deleted(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);

	int i;
	for (i = 0; i < osengine_mapping_num_changes(mapping); i++) {
		OSyncChange *change = osengine_mapping_nth_change(mapping, i);
		if (change->changetype == CHANGE_DELETED) {
			osengine_mapping_solve(engine, mapping, change);
			return;
		}
	}
	fail(NULL);
}

void conflict_handler_duplication(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);
	
	osengine_mapping_duplicate(engine, mapping);
}

void conflict_handler_ignore(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	if (user_data)
		fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);
	
	OSyncError *error = NULL;
	fail_unless(osengine_mapping_ignore_conflict(engine, mapping, &error), NULL);
	fail_unless(error == NULL, NULL);
}


void conflict_handler_random(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);

	int num = osengine_mapping_num_changes(mapping);
	int choosen = g_random_int_range(0, num);
	OSyncChange *change = osengine_mapping_nth_change(mapping, choosen);
	osengine_mapping_solve(engine, mapping, change);
}

static void solve_conflict(OSyncMapping *mapping)
{
	sleep(5);
	
	OSyncEngine *engine = mapping->table->engine;
	
	int i;
	for (i = 0; i < osengine_mapping_num_changes(mapping); i++) {
		OSyncChange *change = osengine_mapping_nth_change(mapping, i);
		if (change->changetype == CHANGE_MODIFIED) {
			osengine_mapping_solve(engine, mapping, change);
			return;
		}
	}
}

void conflict_handler_delay(OSyncEngine *engine, OSyncMapping *mapping, void *user_data)
{
	num_conflicts++;
	fail_unless(osengine_mapping_num_changes(mapping) == GPOINTER_TO_INT(user_data), NULL);
	fail_unless(num_engine_end_conflicts == 0, NULL);
	
	g_thread_create ((GThreadFunc)solve_conflict, mapping, TRUE, NULL);
}

void entry_status(OSyncEngine *engine, OSyncChangeUpdate *status, void *user_data)
{
	switch (status->type) {
		case CHANGE_RECEIVED:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_read++;
			break;
		case CHANGE_RECEIVED_INFO:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_read_info++;
			break;
		case CHANGE_SENT:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_written++;
			break;
		case CHANGE_WRITE_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "CHANGE_WRITE_ERROR: %s", status->error->message);
			num_written_errors++;
			break;
		case CHANGE_RECV_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "CHANGE_RECV_ERROR: %s", status->error->message);
			num_recv_errors++;
			break;
	}
}

void member_status(OSyncMemberUpdate *status, void *user_data)
{
	mark_point();
	switch (status->type) {
		case MEMBER_CONNECTED:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_connected++;
			break;
		case MEMBER_DISCONNECTED:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_disconnected++;
			break;
		case MEMBER_SENT_CHANGES:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_member_sent_changes++;
			break;
		case MEMBER_COMMITTED_ALL:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			num_member_comitted_all++;
			break;
		case MEMBER_CONNECT_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MEMBER_CONNECT_ERROR: %s", status->error->message);
			num_member_connect_errors++;
			break;
		case MEMBER_GET_CHANGES_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MEMBER_CONNECT_ERROR: %s", status->error->message);
			num_member_get_changes_errors++;
			break;
		case MEMBER_SYNC_DONE_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MEMBER_SYNC_DONE_ERROR: %s", status->error->message);
			num_member_sync_done_errors++;
			break;
		case MEMBER_DISCONNECT_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MEMBER_DISCONNECT_ERROR: %s", status->error->message);
			num_member_disconnect_errors++;
			break;
		case MEMBER_COMMITTED_ALL_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MEMBER_COMMITTED_ALL_ERROR: %s", status->error->message);
			num_member_comitted_all_errors++;
			break;
	}
}

void engine_status(OSyncEngine *engine, OSyncEngineUpdate *status, void *user_data)
{
	switch (status->type) {
		case ENG_ENDPHASE_CON:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "All clients connected or error");
			num_engine_connected++;
			break;
		case ENG_ENDPHASE_READ:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "All clients sent changes or error");
			num_engine_read++;
			break;
		case ENG_ENDPHASE_WRITE:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "All clients have writen");
			num_engine_wrote++;
			break;
		case ENG_ENDPHASE_DISCON:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "All clients have disconnected");
			num_engine_disconnected++;
			break;
		case ENG_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "ENG_ERROR: %s", status->error->message);
			num_engine_errors++;
			break;
		case ENG_SYNC_SUCCESSFULL:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "Sync Successfull");
			num_engine_successfull++;
			break;
		case ENG_PREV_UNCLEAN:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "Previous sync was unclean");
			num_engine_prev_unclean++;
			break;
		case ENG_END_CONFLICTS:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "End conflicts");
			num_engine_end_conflicts++;
			break;
	}
}

void mapping_status(OSyncMappingUpdate *status, void *user_data)
{
	switch (status->type) {
		case MAPPING_SOLVED:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "Mapping solved");
			break;
		case MAPPING_SYNCED:
			fail_unless(!osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "Mapping Synced");
			break;
		case MAPPING_WRITE_ERROR:
			fail_unless(osync_error_is_set(&(status->error)), NULL);
			osync_debug("TEST", 4, "MAPPING_WRITE_ERROR: %s", status->error->message);
			num_mapping_errors++;
			break;
	}
}

OSyncEngine *init_engine(OSyncGroup *group)
{
	OSyncError *error = NULL;
	OSyncEngine *engine = osengine_new(group, &error);
	osengine_set_enginestatus_callback(engine, engine_status, NULL);
	osengine_set_memberstatus_callback(engine, member_status, NULL);
	osengine_set_mappingstatus_callback(engine, mapping_status, NULL);
	osengine_set_changestatus_callback(engine, entry_status, NULL);
	mark_point();
	fail_unless(engine != NULL, NULL);
	fail_unless(osengine_init(engine, &error), NULL);
	return engine;
}

osync_bool synchronize_once(OSyncEngine *engine, OSyncError **error)
{
	num_connected = 0;
	num_disconnected = 0;
	num_conflicts = 0;
	num_written = 0;
	num_read = 0;
	num_read_info = 0;
	num_member_connect_errors = 0;
	num_member_sent_changes = 0;
	num_engine_errors = 0;
	num_engine_successfull = 0;
	num_member_get_changes_errors = 0;
	num_written_errors = 0;
	num_mapping_errors = 0;
	num_member_sync_done_errors = 0;
	num_member_disconnect_errors = 0;
	num_engine_prev_unclean = 0;
	num_engine_end_conflicts = 0;
	num_engine_connected = 0;
	num_engine_read = 0;
	num_engine_wrote = 0;
	num_engine_disconnected = 0;
	num_member_comitted_all_errors = 0;
	num_recv_errors = 0;
	num_member_comitted_all = 0;
	
	mark_point();
	return osengine_sync_and_block(engine, error);
}

/*needed because of an incompatible API change in 0.94*/
#if CHECK_VERSION <= 903
void create_case(Suite *s, const char *name, void (*function)(void))
#else /*CHECK_VERSION > 903*/
void create_case(Suite *s, const char *name, void (*function)(int))
#endif /*CHECK_VERSION*/
{
	TCase *tc_new = tcase_create(name);
	tcase_set_timeout(tc_new, 30);
	suite_add_tcase (s, tc_new);
	tcase_add_test(tc_new, function);
}

OSyncMappingTable *mappingtable_load(OSyncGroup *group, int num_mappings, int num_unmapped)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %i, %i)", __func__, group, num_mappings, num_unmapped);
	mark_point();
	OSyncEnv *osync = init_env();
	OSyncGroup *newgroup = osync_group_load(osync, "configs/group", NULL);
	OSyncMappingTable *maptable = _osengine_mappingtable_load_group(newgroup);
	mark_point();
	fail_unless(g_list_length(maptable->mappings) == num_mappings, NULL);
	fail_unless(g_list_length(maptable->unmapped) == num_unmapped, NULL);
	osync_trace(TRACE_EXIT, "%s: %p", __func__, maptable);
	return maptable;
}

void mappingtable_close(OSyncMappingTable *maptable)
{
	osync_trace(TRACE_ENTRY, "%s(%p)", __func__, maptable);
	osengine_mappingtable_close(maptable);
	osync_trace(TRACE_EXIT, "%s", __func__);
}

void check_mapping(OSyncMappingTable *maptable, int memberid, int mappingid, int numentries, const char *uid, const char *format, const char *objecttype)
{
	osync_trace(TRACE_ENTRY, "%s(%p, %i, %i, %i, %s, %s, %s)", __func__, maptable, memberid, mappingid, numentries, uid, format, objecttype);
	OSyncMapping *mapping = NULL;
	mark_point();
	OSyncMember *member = osync_member_from_id(maptable->group, memberid);
	OSyncMappingView *view = osengine_mappingtable_find_view(maptable, member);
	mark_point();
	if (mappingid != -1) {
		mapping = g_list_nth_data(maptable->mappings, mappingid);
	} else {
		GList *m;
		for (m = maptable->mappings; m; m = m->next) {
			mapping = m->data;
			OSyncMappingEntry *entry = osengine_mapping_find_entry(mapping, NULL, view);
			if (!entry)
				continue;
			OSyncChange *change = entry->change;
			fail_unless(change != NULL, NULL);
			if (!strcmp(osync_change_get_uid(change), uid))
				break;
		}
	}
	fail_unless(mapping != NULL, NULL);
	fail_unless(osengine_mapping_num_changes(mapping) == numentries, "osengine_mapping_num_changes(mapping) == numentries for %s, %i: %i != %i", uid, memberid, osengine_mapping_num_changes(mapping), numentries);
	mark_point();
	
	
	OSyncChange *change = osengine_mapping_find_entry(mapping, NULL, view)->change;
	fail_unless(change != NULL, NULL);
	if (format)
		fail_unless(!strcmp(osync_objformat_get_name(osync_change_get_objformat(change)), format), NULL);
	if (objecttype)
		fail_unless(!strcmp(osync_objtype_get_name(osync_change_get_objtype(change)), objecttype), NULL);
	if (uid && strcmp(osync_change_get_uid(change), uid)) {
		printf("uid mismatch: %s != %s for member %i and mapping %i\n", osync_change_get_uid(change), uid, memberid, mappingid);
		fail("uid mismatch");
	}
	osync_trace(TRACE_EXIT, "%s", __func__);
}

OSyncHashTable *hashtable_load(OSyncGroup *group, int memberid, int entries)
{
	mark_point();
	OSyncMember *member = osync_member_from_id(group, memberid);
	mark_point();
	OSyncHashTable *table = osync_hashtable_new();
	mark_point();
    fail_unless(osync_hashtable_load(table, member, NULL), NULL);
    mark_point();
    fail_unless(osync_hashtable_num_entries(table) == entries, NULL);
    return table;
}

void check_hash(OSyncHashTable *table, const char *cmpuid)
{
	char *uid = NULL;
	char *hash = NULL;
	int i;
	osync_bool found = FALSE;
	for (i = 0; i < osync_hashtable_num_entries(table); i++) {
		osync_hashtable_nth_entry(table, i, &uid, &hash);
		if (!strcmp(cmpuid, uid))
			found = TRUE;
	}
	fail_unless(found == TRUE, NULL);
}

static void load_format(OSyncEnv *env, const char *name)
{
	OSyncError *error = NULL;
	char *path = g_strdup_printf("%s/%s", g_get_current_dir(), name);	
	fail_unless(osync_module_load(env, path, &error), NULL);
	g_free(path);
}

OSyncEnv *init_env(void)
{
	mark_point();
	OSyncEnv *osync = osync_env_new();
	mark_point();
	osync_env_set_option(osync, "LOAD_GROUPS", "FALSE");
	osync_env_set_option(osync, "LOAD_FORMATS", "FALSE");
	osync_env_set_option(osync, "LOAD_PLUGINS", "FALSE");
	mark_point();
	OSyncError *error = NULL;
	fail_unless(osync_env_initialize(osync, &error), NULL);
	fail_unless(!osync_error_is_set(&error), NULL);
	
	char *path = g_strdup_printf("%s/%s", g_get_current_dir(), "mock_sync.so");	
	fail_unless(osync_module_load(osync, path, &error), NULL);
	g_free(path);
	
	load_format(osync, "contact.so");
	load_format(osync, "data.so");
	load_format(osync, "event.so");
	load_format(osync, "note.so");
	load_format(osync, "todo.so");
	load_format(osync, "xml-vcal.so");
	load_format(osync, "xml-vcard.so");
	load_format(osync, "xml-vnote.so");
	load_format(osync, "xml-evolution.so");
	load_format(osync, "xml-kde.so");
	load_format(osync, "mockformat.so");
	
	return osync;
}

OSyncEnv *init_env_none(void)
{
	mark_point();
	OSyncEnv *osync = osync_env_new();
	mark_point();
	osync_env_set_option(osync, "LOAD_GROUPS", "FALSE");
	osync_env_set_option(osync, "LOAD_FORMATS", "FALSE");
	osync_env_set_option(osync, "LOAD_PLUGINS", "FALSE");
	mark_point();
	OSyncError *error = NULL;
	fail_unless(osync_env_initialize(osync, &error), NULL);
	fail_unless(!osync_error_is_set(&error), NULL);
	return osync;
}


syntax highlighted by Code2HTML, v. 0.9.1