define([
    '/common/cryptpad-common.js',
    '/common/cryptget.js',
    '/common/userObject.js',
    'json.sortify'
], function (Cryptpad, Crypt, FO, Sortify) {
    var exp = {};

    var getType = function (el) {
        if (el === null) { return "null"; }
        return Array.isArray(el) ? "array" : typeof(el);
    };

    var findAvailableKey = function (obj, key) {
        if (typeof (obj[key]) === "undefined") { return key; }
        var i = 1;
        var nkey = key;
        while (typeof (obj[nkey]) !== "undefined") {
            nkey = key + '_' + i;
            i++;
        }
        return nkey;
    };

    var copy = function (el) {
        if (typeof (el) !== "object") { return el; }
        return JSON.parse(JSON.stringify(el));
    };

    var deduplicate = function (array) {
        var a = array.slice();
        for(var i=0; i<a.length; i++) {
            for(var j=i+1; j<a.length; j++) {
                if(a[i] === a[j] || (
                    typeof(a[i]) === "object" && Sortify(a[i]) === Sortify(a[j]))) {
                    a.splice(j--, 1);
                }
            }
        }
        return a;
    };

    // Merge obj2 into obj1
    // If keepOld is true, obj1 values are kept in case of conflicti
    // Not used ATM
    var merge = function (obj1, obj2, keepOld) {
        if (typeof (obj1) !== "object" || typeof (obj2) !== "object") { return; }
        Object.keys(obj2).forEach(function (k) {
            var v = obj2[k];
            // If one of them is not an object or if we have a map and a array, don't override, create a new key
            if (!obj1[k] || typeof(obj1[k]) !== "object" || typeof(obj2[k]) !== "object" ||
                    (getType(obj1[k]) !== getType(obj2[k]))) {
                // We don't want to override the values in the object (username, preferences)
                // These values should be the ones stored in the first object
                if (keepOld) { return; }
                if (obj1[k] === obj2[k]) { return; }
                var nkey = findAvailableKey(obj1, k);
                obj1[nkey] = copy(obj2[k]);
                return;
            }
            // Else, they're both maps or both arrays
            if (getType(obj1[k]) === "array" && getType(obj2[k]) === "array") {
                var c = obj1[k].concat(obj2[k]);
                obj1[k] = deduplicate(c);
                return;
            }
            merge(obj1[k], obj2[k], keepOld);
        });
    };

    var createFromPath = function (proxy, oldFo, path, href) {
        var root = proxy.drive;

        var error = function (msg) {
            console.error(msg || "Unable to find that path", path);
        };

        if (oldFo.isInTrashRoot(path)) {
            href = oldFo.find(path.slice(0,3));
            path.pop();
        }

        var p, next, nextRoot;
        path.forEach(function (p, i) {
            if (!root) { return; }
            if (typeof(p) === "string") {
                if (getType(root) !== "object") { root = undefined; error(); return; }
                if (i === path.length - 1) {
                    root[findAvailableKey(root, p)] = href;
                    return;
                }
                next = getType(path[i+1]);
                nextRoot = getType(root[p]);
                if (nextRoot !== "undefined") {
                    if (next === "string" && nextRoot === "object" || next === "number" && nextRoot === "array") {
                        root = root[p];
                        return;
                    }
                    p = findAvailableKey(root, p);
                }
                if (next === "number") {
                    root[p] = [];
                    root = root[p];
                    return;
                }
                root[p] = {};
                root = root[p];
                return;
            }
            // Path contains a non-string element: it's an array index
            if (typeof(p) !== "number") { root = undefined; error(); return; }
            if (getType(root) !== "array") { root = undefined; error(); return; }
            if (i === path.length - 1) {
                if (root.indexOf(href) === -1) { root.push(href); }
                return;
            }
            next = getType(path[i+1]);
            if (next === "number") {
                error('2 consecutives arrays in the user object');
                root = undefined;
                //root.push([]);
                //root = root[root.length - 1];
                return;
            }
            root.push({});
            root = root[root.length - 1];
            return;
        });
    };

    var mergeAnonDrive = exp.anonDriveIntoUser = function (proxy, cb) {
        // Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
        if (!localStorage.FS_hash || !Cryptpad.isLoggedIn()) {
            if (typeof(cb) === "function") { cb(); }
        }
        // Get the content of FS_hash and then merge the objects, remove the migration key and cb
        var todo = function (err, doc) {
            if (err) { console.error("Cannot migrate recent pads", err); return; }
            var parsed;
            if (!doc) {
                if (typeof(cb) === "function") { cb(); }
                return;
            }
            try { parsed = JSON.parse(doc); } catch (e) {
                if (typeof(cb) === "function") { cb(); }
                console.error("Cannot parsed recent pads", e);
                return;
            }
            if (parsed) {
                //merge(proxy, parsed, true);
                var oldFo = FO.init(parsed.drive, {
                    Cryptpad: Cryptpad
                });
                oldFo.fixFiles();
                var newData = Cryptpad.getStore().getProxy();
                var newFo = newData.fo;
                var newRecentPads = proxy.drive[Cryptpad.storageKey];
                var newFiles = newFo.getFiles([newFo.FILES_DATA]);
                var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
                oldFiles.forEach(function (href) {
                    // Do not migrate a pad if we already have it, it would create a duplicate in the drive
                    if (newFiles.indexOf(href) !== -1) { return; }
                    // If we have a stronger version, do not add the current href
                    if (Cryptpad.findStronger(href, newRecentPads)) { console.log(href); return; }
                    // If we have a weaker version, replace the href by the new one
                    // NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
                    var weaker = Cryptpad.findWeaker(href, newRecentPads);
                    if (weaker) {
                        // Update RECENTPADS
                        newRecentPads.some(function (pad) {
                            if (pad.href === weaker) {
                                pad.href = href;
                                return true;
                            }
                            return;
                        });
                        // Update the file in the drive
                        newFo.replace(weaker, href);
                        return;
                    }
                    // Here it means we have a new href, so we should add it to the drive at its old location
                    var paths = oldFo.findFile(href);
                    if (paths.length === 0) { return; }
                    createFromPath(proxy, oldFo, paths[0], href);
                    // Also, push the file data in our array
                    var data = oldFo.getFileData(href);
                    if (data) {
                        newRecentPads.push(data);
                    }
                });
            }
            if (typeof(cb) === "function") { cb(); }
        };
        Crypt.get(localStorage.FS_hash, todo);
    };

    return exp;
});
