/* liblivejournal - a client library for LiveJournal.
 * Copyright (C) 2003 Evan Martin <evan@livejournal.com>
 *
 * vim: tabstop=4 shiftwidth=4 noexpandtab :
 */

#include <glib.h>

#include "getevents.h"
#include "syncitems.h"
#include "sync.h"

typedef struct _Sync {
	LJUser *user;
	const char *usejournal;

	char *lastsync;
	int synced;
	int total;

	gboolean sync_complete;
	GSList *syncitems;

	GError **err;

	LJPutLastSyncCallback put_lastsync;
	LJRunVerbCallback run_verb;
	LJPutEntryCallback put_entry;
	LJSyncProgressCallback sync_progress;
	gpointer user_data;
} Sync;

static int
entry_date_compare_func(const void *a, const void *b) {
	/* struct tm* cast needed to un-constify the times passed to mktime. */
	const LJEntry *entrya = a; time_t timea = mktime((struct tm*)&entrya->time);
	const LJEntry *entryb = b; time_t timeb = mktime((struct tm*)&entryb->time);
	/* mktime actually converts the times to local time, which isn't
	 * quite correct, but since we're comparing times directly like this
	 * it should still sort the same way and timegm is potentially slower. */
	if (timea < timeb) return -1;
	if (timea > timeb) return 1;
	return 0;
}

static time_t
run_one_getevents(Sync *sync, GSList **warnings) {
	LJGetEventsSync *getevents;
	int lastitemid, i;
	time_t lastsync = 0;

	getevents = lj_getevents_sync_new(sync->user,
			sync->usejournal, sync->lastsync);
	if (!sync->run_verb(sync->user_data, (LJVerb*)getevents, sync->err)) {
		lj_getevents_sync_free(getevents, TRUE);
		return 0;
	}
	if (getevents->entry_count == 0) {
		g_warning("getevents returned no entries!?\n");
		lj_getevents_sync_free(getevents, TRUE);
		return 0;
	}

	lastitemid = getevents->entries[getevents->entry_count-1]->itemid;
	/*g_print("lastsync %s : got %d entries\n",
			sync->lastsync ? sync->lastsync : "(none)", getevents->entry_count);*/

	/* pop off all the syncitems for the entries we have now received,
	 * also scanning for the latest time we managed to sync.
	 * this process is O(e*s), e=entry_count, s=sync_count,
	 * but in practice we find the entries fairly early so s is small. */
	for (i = 0; i < getevents->entry_count; i++) {
		int id = getevents->entries[i]->itemid;
		GSList *last = NULL, *cur = sync->syncitems;

		while (cur && ((LJSyncItem*)cur->data)->id != id) {
			last = cur; cur = cur->next;
		}

		if (cur) {
			LJSyncItem *item = (LJSyncItem*)cur->data;
			sync->synced++;

			/* update latest time. */
			if (item->time > lastsync)
				lastsync = item->time;

			/* pop this off the syncitems list. */
			if (last) last->next = cur->next;
			else sync->syncitems = cur->next;
			g_free(item);
			g_slist_free_1(cur);
		}
	}

	/* a sync returns a list in update order,
	 * and we don't want to depend on that order anyway.
	 * we want them ordered by entry time. */
	qsort(getevents->entries, getevents->entry_count, sizeof(LJEntry*),
	      entry_date_compare_func);
	/* and finally, put these to disk. */
	for (i = 0; i < getevents->entry_count; i++) {
		sync->put_entry(sync->user_data, getevents->entries[i], sync->err);
	}

	lj_getevents_sync_free(getevents, TRUE);

	return lastsync;
}

static gboolean
run_one_syncitems(Sync *sync) {
	LJSyncItems *syncitems;

	syncitems = lj_syncitems_new(sync->user, sync->usejournal,
	                             sync->lastsync);
	syncitems->items = sync->syncitems;
	if (!sync->run_verb(sync->user_data, (LJVerb*)syncitems, sync->err)) {
		lj_syncitems_free(syncitems, TRUE);
		return 0;
	}

	if (!sync->total)
		sync->total = syncitems->total;

	if (syncitems->count == syncitems->total)
		sync->sync_complete = TRUE;

	g_free(sync->lastsync);
	sync->lastsync = g_strdup(syncitems->lastsync);

	sync->syncitems = syncitems->items;
	lj_syncitems_free(syncitems, FALSE);

	return TRUE;
}

static gboolean
run_syncitems(Sync *sync) {
	gboolean sentfirst = FALSE;
	int itemcount;
	do {
		if (!run_one_syncitems(sync))
			return FALSE;
		if (!sentfirst) {
			sync->sync_progress(sync->user_data, LJ_SYNC_PROGRESS_ITEMS,
					0, sync->total, sync->lastsync);
			sentfirst = TRUE;
		}
		itemcount = g_slist_length(sync->syncitems);
		if (itemcount > 0)
			sync->sync_progress(sync->user_data, LJ_SYNC_PROGRESS_ITEMS,
					itemcount, sync->total, sync->lastsync);
	} while (!sync->sync_complete);
	
	return TRUE;
}

static gboolean
run_getevents(Sync *sync, GSList **warnings) {
	time_t lastsync;
	int entrycount = 0;
	GSList *cur = sync->syncitems, *last = NULL;

	/* strip off non-entry syncitems and
	 * count how many entries we have to sync. */
	while (cur) {
		LJSyncItem *item = (LJSyncItem*)cur->data;
		if (item->type != LJ_SYNCITEM_TYPE_ENTRY) {
			GSList *next = cur->next;

			/* pop this off the syncitems list. */
			if (last) last->next = cur->next;
			else sync->syncitems = cur->next;
			g_free(item);
			g_slist_free_1(cur);
			cur = next;
		} else {
			last = cur; cur = cur->next;
			entrycount++;
		}
	}

	sync->sync_progress(sync->user_data, LJ_SYNC_PROGRESS_ENTRIES,
			0, entrycount, sync->lastsync);

	/* now sync them. */
	while (sync->syncitems) {
		lastsync = run_one_getevents(sync, warnings);
		if (lastsync <= 0)
			return FALSE;

		/* now that we've successfully synced this batch of entries,
		 * let the caller know; they should write it out to disk,
		 * so if we're cancelled before we complete,
		 * we can resume at this point instead of starting over. */
		g_free(sync->lastsync);
		sync->lastsync = lj_tm_to_ljdate(gmtime(&lastsync));
		sync->put_lastsync(sync->user_data, sync->lastsync, sync->err);

		sync->sync_progress(sync->user_data, LJ_SYNC_PROGRESS_ENTRIES,
				sync->synced, entrycount, sync->lastsync);
	}

	return TRUE;
}

gboolean
lj_sync_run(LJUser *user, const char *usejournal,
            const char *lastsync, LJPutLastSyncCallback put_lastsync_cb,
            LJRunVerbCallback run_verb_cb, LJPutEntryCallback put_entry_cb,
            LJSyncProgressCallback sync_progress_cb,
			gpointer user_data, GSList **warnings, GError **err) {
	Sync st = {0}, *sync = &st;
	gboolean success = FALSE;

	sync->user = user;
	sync->usejournal = usejournal;
	sync->lastsync = lastsync ? g_strdup(lastsync) : NULL;
	sync->put_lastsync = put_lastsync_cb;
	sync->run_verb = run_verb_cb;
	sync->put_entry = put_entry_cb;
	sync->sync_progress = sync_progress_cb;
	sync->user_data = user_data;
	sync->err = err;

	if (run_syncitems(sync)) {
		g_free(sync->lastsync);
		sync->lastsync = lastsync ? g_strdup(lastsync) : NULL;
		if (run_getevents(sync, warnings))
			success = TRUE;
	}

	g_free(sync->lastsync);
	/* there shouldn't be any leftover syncitems in the list,
	 * but it's good to be safe. */
	g_slist_foreach(sync->syncitems, (GFunc)g_free, NULL);
	g_slist_free(sync->syncitems);
	return success;
}

