define(["jquery", "core", "dropbox-provider", "gdrive-provider", "underscore"], function($, core) {
	var synchronizer = {};
	
	// Create a map with providerId: providerObject
	var providerMap = _.chain(arguments)
		.map(function(argument) {
			return argument && argument.providerId && [argument.providerId, argument];
		}).compact().object().value();

	// Used to know if user can force synchronization
	var uploadPending = false;
	
	// Allows external modules to update uploadPending flag
	synchronizer.notifyChange = function(fileIndex) {
		// Check that file has synchronized locations
		if(localStorage[fileIndex + ".sync"].length !== 1) {
			uploadPending = true;
			synchronizer.updateSyncButton();
		}
	};
	
	// Used to enable/disable the synchronization button
	synchronizer.updateSyncButton = function() {
		if(syncRunning === true || uploadPending === false || core.isOffline) {
			$(".action-force-sync").addClass("disabled");
		}
		else {
			$(".action-force-sync").removeClass("disabled");
		}
	};
	// Run updateSyncButton on online/offline event
	core.addOfflineListener(synchronizer.updateSyncButton);

	// Force the synchronization
	synchronizer.forceSync = function() {
		lastSync = 0;
		synchronizer.sync();
	};
	
	// Recursive function to upload a single file on multiple locations
	var uploadFileSyncIndexList = [];
	var uploadContent = undefined;
	var uploadContentCRC = undefined;
	var uploadTitle = undefined;
	var uploadTitleCRC = undefined;
	function locationUp(callback) {
		
		// No more synchronized location for this document
		if (uploadFileSyncIndexList.length === 0) {
			fileUp(callback);
			return;
		}
		
		// Dequeue a synchronized location
		var syncIndex = uploadFileSyncIndexList.pop();
		var syncAttributes = JSON.parse(localStorage[syncIndex]);
		// Use the specified provider to perform the upload
		providerMap[syncAttributes.provider].syncUp(
			uploadContent,
			uploadContentCRC,
			uploadTitle,
			uploadTitleCRC,
			syncAttributes,
			function(error, uploadFlag) {
				if(uploadFlag === true) {
					// If uploadFlag is true, request another upload cycle
					uploadCycle = true;
					// When page is refreshed, this flag is false but should be true here
					uploadPending = true;
				}
				if(error) {
					callback(error);
					return;
				}
				if(uploadFlag) {
					// Update syncAttributes in localStorage
					localStorage[syncIndex] = JSON.stringify(syncAttributes);
				}
				locationUp(callback);
			}
		);
	}

	// Recursive function to upload multiple files
	var uploadFileIndexList = [];
	function fileUp(callback) {
		
		// No more fileIndex to synchronize
		if (uploadFileIndexList.length === 0) {
			syncUp(callback);
			return;
		}
		
		// Dequeue a fileIndex
		var fileIndex = uploadFileIndexList.pop();
		var fileSyncIndexes = localStorage[fileIndex + ".sync"];
		if(fileSyncIndexes.length === 1) {
			fileUp(callback);
			return;
		}

		// Get document title/content 
		uploadContent = localStorage[fileIndex + ".content"];
		uploadContentCRC = core.crc32(uploadContent);
		uploadTitle = localStorage[fileIndex + ".title"];
		uploadTitleCRC = core.crc32(uploadTitle);

		// Parse the list of synchronized locations associated to the document
		uploadFileSyncIndexList = _.compact(fileSyncIndexes.split(";"));
		locationUp(callback);
	}

	// Entry point for up synchronization (upload changes)
	var uploadCycle = false;
	function syncUp(callback) {
		if(uploadCycle === true) {
			// New upload cycle
			uploadCycle = false;
			uploadFileIndexList = _.compact(localStorage["file.list"].split(";"));
			fileUp(callback);
		}
		else {
			callback();
		} 
	}

	// Recursive function to download changes from multiple providers
	var providerList = [];
	function providerDown(callback) {
		if(providerList.length === 0) {
			callback();
			return;
		}
		var provider = providerList.pop();
		provider.syncDown(function(error) {
			if(error) {
				callback(error);
				return;
			}
			providerDown(callback);
		});
	}
	
	// Entry point for down synchronization (download changes)
	function syncDown(callback) {
		providerList = _.values(providerMap);
		providerDown(callback);
	};
	
	// Main entry point for synchronization
	var syncRunning = false;
	var lastSync = 0;
	synchronizer.sync = function() {
		// If sync is already running or timeout is not reached or offline
		if (syncRunning || lastSync + SYNC_PERIOD > core.currentTime || core.isOffline) {
			return;
		}
		syncRunning = true;
		uploadCycle = true;
		lastSync = core.currentTime;
		synchronizer.updateSyncButton();
		
		function isError(error) {
			if(error !== undefined) {
				console.error(error);
				syncRunning = false;
				synchronizer.updateSyncButton();
				return true;
			}
			return false;
		}

		syncDown(function(error) {
			if(isError(error)) {
				return;
			}
			syncUp(function(error) {
				if(isError(error)) {
					return;
				}
				syncRunning = false;
				uploadPending = false;
			});
		});
	};
	// Run sync function periodically
	if(viewerMode === false) {
		core.addPeriodicCallback(synchronizer.sync);
	}
	
	// Used to populate the "Manage synchronization" dialog
	var lineTemplate = ['<div class="input-prepend input-append">',
		'<span class="add-on" title="<%= provider.providerName %>">',
		'<i class="icon-<%= provider.providerId %>"></i></span>',
		'<input class="span5" type="text" value="<%= syncDesc %>" disabled />',
		'</div>'].join("");
	var removeButtonTemplate = '<a class="btn" title="Remove this location"><i class="icon-trash"></i></a>';
	synchronizer.refreshManageSync = function() {
		var fileIndex = core.fileManager.getCurrentFileIndex();
		var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
		$(".msg-no-sync, .msg-sync-list").addClass("hide");
		var syncList = $("#manage-sync-list").empty();
		if (syncIndexList.length > 0) {
			$(".msg-sync-list").removeClass("hide");
		} else {
			$(".msg-no-sync").removeClass("hide");
		}
		_.each(syncIndexList, function(syncIndex) {
			var syncAttributes = JSON.parse(localStorage[syncIndex]);
			var syncDesc = syncAttributes.id || syncAttributes.path;
			var lineElement = $(_.template(lineTemplate, {
				provider: providerMap[syncAttributes.provider],
				syncDesc: syncDesc
			}));
			lineElement.append($(removeButtonTemplate).click(function() {
				core.fileManager.removeSync(syncIndex);
				core.fileManager.updateFileTitles();
			}));
			syncList.append(lineElement);
		});
	};
	
	// Used to enable/disable provider synchronization
	synchronizer.resetSyncFlags = function() {
		_.each(providerMap, function(provider) {
			provider.useSync = false;
		});		
	};
	synchronizer.getSyncAttributesFromFile = function(fileIndex) {
		var syncIndexList = _.compact(localStorage[fileIndex + ".sync"].split(";"));
		var attributesList = [];
		_.each(syncIndexList, function(syncIndex) {
			var syncAttributes = JSON.parse(localStorage[syncIndex]);
			attributesList.push(syncAttributes);
			providerMap[syncAttributes.provider].useSync = true;
		});
		return attributesList;
	};
	
	core.onReady(function() {
		// Init each provider
		_.each(providerMap, function(provider) {
			// Provider's import button
			$(".action-sync-import-" + provider.providerId).click(function(event) {
				provider.importFiles(event);
			});
			// Provider's export button
			$(".action-sync-export-" + provider.providerId).click(function(event) {
				var fileIndex = core.fileManager.getCurrentFileIndex();
				var title = localStorage[fileIndex + ".title"];
				var content = localStorage[fileIndex + ".content"];
				provider.exportFile(event, title, content, function(error, syncIndex) {
					if(error) {
						return;
					}
					localStorage[fileIndex + ".sync"] += syncIndex + ";";
					synchronizer.refreshManageSync();
					core.fileManager.updateFileTitles();
					core.showMessage('"' + title
						+ '" will now be synchronized on ' + provider.providerName + '.');
				});
			});
			// Provider's manual sync button
			$(".action-sync-manual-" + provider.providerId).click(function(event) {
				var fileIndex = core.fileManager.getCurrentFileIndex();
				var title = localStorage[fileIndex + ".title"];
				var content = localStorage[fileIndex + ".content"];
				provider.exportManual(event, title, content, function(error, syncIndex) {
					if(error) {
						return;
					}
					localStorage[fileIndex + ".sync"] += syncIndex + ";";
					synchronizer.refreshManageSync();
					core.fileManager.updateFileTitles();
					core.showMessage('"' + title
						+ '" will now be synchronized on ' + provider.providerName + '.');
				});
			});
		});
		
		synchronizer.updateSyncButton();
		$(".action-force-sync").click(function() {
			if(!$(this).hasClass("disabled")) {
				synchronizer.forceSync();
			}
		});
	});

	return synchronizer;
});