define([
    "jquery",
    "underscore",
    "utils",
    "storage",
    "eventMgr",
    "fileSystem",
    "fileMgr",
    "classes/Provider",
    "providers/dropboxProvider",
    "providers/gdriveProvider",
    "providers/gdrivesecProvider",
    "providers/gdriveterProvider"
], function($, _, utils, storage, eventMgr, fileSystem, fileMgr, Provider) {

    var synchronizer = {};

    // Create a map with providerId: providerModule
    var providerMap = _.chain(arguments).map(function(argument) {
        return argument instanceof Provider && [
            argument.providerId,
            argument
        ];
    }).compact().object().value();

    // Retrieve sync locations from storage
    (function() {
        var syncIndexMap = {};
        _.each(fileSystem, function(fileDesc) {
            utils.retrieveIndexArray(fileDesc.fileIndex + ".sync").forEach(function(syncIndex) {
                try {
                    var syncAttributes = JSON.parse(storage[syncIndex]);
                    // Store syncIndex
                    syncAttributes.syncIndex = syncIndex;
                    // Replace provider ID by provider module in attributes
                    var provider = providerMap[syncAttributes.provider];
                    if(!provider) {
                        throw new Error("Invalid provider ID: " + syncAttributes.provider);
                    }
                    syncAttributes.provider = provider;
                    fileDesc.syncLocations[syncIndex] = syncAttributes;
                    syncIndexMap[syncIndex] = syncAttributes;
                }
                catch(e) {
                    // storage can be corrupted
                    eventMgr.onError(e);
                    // Remove sync location
                    utils.removeIndexFromArray(fileDesc.fileIndex + ".sync", syncIndex);
                }
            });
        });

        // Clean fields from deleted files in local storage
        Object.keys(storage).forEach(function(key) {
            var match = key.match(/sync\.\S+/);
            if(match && !syncIndexMap.hasOwnProperty(match[0])) {
                storage.removeItem(key);
            }
        });
    })();

    // AutoSync configuration
    _.each(providerMap, function(provider) {
        provider.autosyncConfig = utils.retrieveIgnoreError(provider.providerId + ".autosyncConfig") || {};
    });

    // Returns true if at least one file has synchronized location
    synchronizer.hasSync = function(provider) {
        return _.some(fileSystem, function(fileDesc) {
            return _.some(fileDesc.syncLocations, function(syncAttributes) {
                return provider === undefined || syncAttributes.provider === provider;
            });
        });
    };

    /***************************************************************************
     * Synchronization
     **************************************************************************/

    // Entry point for up synchronization (upload changes)
    var uploadCycle = false;
    function syncUp(callback) {
        var uploadFileList = [];

        // Recursive function to upload multiple files
        function fileUp() {
            // No more fileDesc to synchronize
            if(uploadFileList.length === 0) {
                return syncUp(callback);
            }

            // Dequeue a fileDesc to synchronize
            var fileDesc = uploadFileList.pop();
            var uploadSyncAttributesList = _.values(fileDesc.syncLocations);
            if(uploadSyncAttributesList.length === 0) {
                return fileUp();
            }

            var uploadContent = fileDesc.content;
            var uploadContentCRC = utils.crc32(uploadContent);
            var uploadTitle = fileDesc.title;
            var uploadTitleCRC = utils.crc32(uploadTitle);
            var uploadDiscussionList = fileDesc.discussionListJSON;
            var uploadDiscussionListCRC = utils.crc32(uploadDiscussionList);

            // Recursive function to upload a single file on multiple locations
            function locationUp() {

                // No more synchronized location for this document
                if(uploadSyncAttributesList.length === 0) {
                    return fileUp();
                }

                // Dequeue a synchronized location
                var syncAttributes = uploadSyncAttributesList.pop();

                syncAttributes.provider.syncUp(
                    uploadContent,
                    uploadContentCRC,
                    uploadTitle,
                    uploadTitleCRC,
                    uploadDiscussionList,
                    uploadDiscussionListCRC,
                    syncAttributes,
                    function(error, uploadFlag) {
                        if(uploadFlag === true) {
                            // If uploadFlag is true, request another upload cycle
                            uploadCycle = true;
                        }
                        if(error) {
                            return callback(error);
                        }
                        if(uploadFlag) {
                            // Update syncAttributes in storage
                            utils.storeAttributes(syncAttributes);
                        }
                        locationUp();
                    }
                );
            }
            locationUp();
        }

        if(uploadCycle === true) {
            // New upload cycle
            uploadCycle = false;
            uploadFileList = _.values(fileSystem);
            fileUp();
        }
        else {
            callback();
        }
    }

    // Entry point for down synchronization (download changes)
    function syncDown(callback) {
        var providerList = _.values(providerMap);

        // Recursive function to download changes from multiple providers
        function providerDown() {
            if(providerList.length === 0) {
                return callback();
            }
            var provider = providerList.pop();

            // Check that provider has files to sync
            if(!synchronizer.hasSync(provider)) {
                return providerDown();
            }

            // Perform provider's syncDown
            provider.syncDown(function(error) {
                if(error) {
                    return callback(error);
                }
                providerDown();
            });
        }
        providerDown();
    }

    // Entry point for the autosync feature
    function autosyncAll(callback) {
        var autosyncFileList = _.filter(fileSystem, function(fileDesc) {
            return _.size(fileDesc.syncLocations) === 0;
        });

        // Recursive function to autosync multiple files
        function fileAutosync() {
            // No more fileDesc to synchronize
            if(autosyncFileList.length === 0) {
                return callback();
            }
            var fileDesc = autosyncFileList.pop();

            var providerList = _.filter(providerMap, function(provider) {
                return provider.autosyncConfig.mode == 'all';
            });
            function providerAutosync() {
                // No more provider
                if(providerList.length === 0) {
                    return fileAutosync();
                }
                var provider = providerList.pop();

                provider.autosyncFile(fileDesc.title, fileDesc.content, fileDesc.discussionListJSON, provider.autosyncConfig, function(error, syncAttributes) {
                    if(error) {
                        return callback(error);
                    }
                    fileDesc.addSyncLocation(syncAttributes);
                    eventMgr.onSyncExportSuccess(fileDesc, syncAttributes);
                    providerAutosync();
                });
            }

            providerAutosync();
        }

        fileAutosync();
    }

    // Listen to offline status changes
    var isOffline = false;
    eventMgr.addListener("onOfflineChanged", function(isOfflineParam) {
        isOffline = isOfflineParam;
    });

    // Main entry point for synchronization
    var syncRunning = false;
    synchronizer.sync = function() {
        // If sync is already running or offline
        if(syncRunning === true || isOffline === true) {
            return false;
        }
        syncRunning = true;
        eventMgr.onSyncRunning(true);
        uploadCycle = true;

        function isError(error) {
            if(error !== undefined) {
                syncRunning = false;
                eventMgr.onSyncRunning(false);
                return true;
            }
            return false;
        }

        autosyncAll(function(error) {
            if(isError(error)) {
                return;
            }
            syncDown(function(error) {
                if(isError(error)) {
                    return;
                }
                syncUp(function(error) {
                    if(isError(error)) {
                        return;
                    }
                    syncRunning = false;
                    eventMgr.onSyncRunning(false);
                    eventMgr.onSyncSuccess();
                });
            });
        });
        return true;
    };

    /***************************************************************************
     * Initialize module
     **************************************************************************/

    // Initialize the export dialog
    function initExportDialog(provider) {

        // Reset fields
        utils.resetModalInputs();

        // Load preferences
        var exportPreferences = utils.retrieveIgnoreError(provider.providerId + ".exportPreferences");
        if(exportPreferences) {
            _.each(provider.exportPreferencesInputIds, function(inputId) {
                var exportPreferenceValue = exportPreferences[inputId];
                if(_.isBoolean(exportPreferenceValue)) {
                    utils.setInputChecked("#input-sync-export-" + inputId, exportPreferenceValue);
                }
                else {
                    utils.setInputValue("#input-sync-export-" + inputId, exportPreferenceValue);
                }
            });
        }

        // Open dialog
        $(".modal-upload-" + provider.providerId).modal();
    }

    eventMgr.addListener("onFileCreated", function(fileDesc) {
        if(_.size(fileDesc.syncLocations) === 0) {
            _.each(providerMap, function(provider) {
                if(provider.autosyncConfig.mode != 'new') {
                    return;
                }
                provider.autosyncFile(fileDesc.title, fileDesc.content, fileDesc.discussionListJSON, provider.autosyncConfig, function(error, syncAttributes) {
                    if(error) {
                        return;
                    }
                    fileDesc.addSyncLocation(syncAttributes);
                    eventMgr.onSyncExportSuccess(fileDesc, syncAttributes);
                });
            });
        }
    });

    eventMgr.addListener("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 action
            $(".action-sync-export-dialog-" + provider.providerId).click(function() {
                initExportDialog(provider);
            });
            // Provider's autosync action
            $(".action-autosync-dialog-" + provider.providerId).click(function() {
                // Reset fields
                utils.resetModalInputs();
                // Load config
                provider.setAutosyncDialogConfig(provider);
                // Open dialog
                $(".modal-autosync-" + provider.providerId).modal();
            });
            $(".action-sync-export-" + provider.providerId).click(function(event) {
                var fileDesc = fileMgr.currentFile;

                provider.exportFile(event, fileDesc.title, fileDesc.content, fileDesc.discussionListJSON, function(error, syncAttributes) {
                    if(error) {
                        return;
                    }
                    fileDesc.addSyncLocation(syncAttributes);
                    eventMgr.onSyncExportSuccess(fileDesc, syncAttributes);
                });

                // Store input values as preferences for next time we open the
                // export dialog
                var exportPreferences = {};
                _.each(provider.exportPreferencesInputIds, function(inputId) {
                    var inputElt = document.getElementById("input-sync-export-" + inputId);
                    if(inputElt.type == 'checkbox') {
                        exportPreferences[inputId] = inputElt.checked;
                    }
                    else {
                        exportPreferences[inputId] = inputElt.value;
                    }
                });
                storage[provider.providerId + ".exportPreferences"] = JSON.stringify(exportPreferences);
            });
            $(".action-autosync-" + provider.providerId).click(function(event) {
                var config = provider.getAutosyncDialogConfig(event);
                if(config !== undefined) {
                    storage[provider.providerId + ".autosyncConfig"] = JSON.stringify(config);
                    provider.autosyncConfig = config;
                }
            });
        });
    });

    eventMgr.onSynchronizerCreated(synchronizer);
    return synchronizer;
});
