/**
 * JIO Revision Storage.
 * It manages document version and can generate conflicts.
 * Description:
 * {
 *     "type": "revision",
 *     "secondstorage": <sub storage description>
 * }
 */
jIO.addStorageType('revision', function (spec, my) {
    "use strict";
    var that, priv = {};
    spec = spec || {};
    that = my.basicStorage(spec, my);

    priv.substorage_key = "secondstorage";
    priv.doctree_suffix = ".revision_tree.json";
    priv.substorage = spec[priv.substorage_key];

    that.serialized = function () {
        var o = {};
        o[priv.substorage_key] = priv.substorage;
        return o;
    };

    /**
     * Generate a new uuid
     * @method generateUuid
     * @return {string} The new uuid
     */
    priv.generateUuid = function () {
        var S4 = function () {
            var i, string = Math.floor(
                Math.random() * 0x10000 /* 65536 */
            ).toString(16);
            for (i = string.length; i < 4; i += 1) {
                string = '0'+string;
            }
            return string;
        };
        return S4() + S4() + "-" +
            S4() + "-" +
            S4() + "-" +
            S4() + "-" +
            S4() + S4() + S4();
    };

    /**
     * Generates a hash code of a string
     * @method hashCode
     * @param  {string} string The string to hash
     * @return {string} The string hash code
     */
    priv.hashCode = function (string) {
        return hex_sha256(string);
    };

    /**
     * Returns an array version of a revision string
     * @method revisionToArray
     * @param  {string} revision The revision string
     * @return {array} Array containing a revision number and a hash
     */
    priv.revisionToArray = function (revision) {
        if (typeof revision === "string") {
            return [parseInt(revision.split('-')[0],10),
                    revision.split('-')[1]]
        }
        return revision;
    };

    /**
     * Generates the next revision of [previous_revision]. [string] helps us
     * to generate a hash code.
     * @methode generateNextRev
     * @param  {string} previous_revision The previous revision
     * @param  {string} string String to help generate hash code
     * @return {array} 0:The next revision number and 1:the hash code
     */
    priv.generateNextRevision = function (previous_revision, string) {
        if (typeof previous_revision === "number") {
            return [previous_revision + 1, priv.hashCode(string)];
        }
        previous_revision = priv.revisionToArray(previous_revision);
        return [previous_revision[0]+1, priv.hashCode(string)];
    };

    /**
     * Checks a revision format
     * @method checkRevisionFormat
     * @param  {string} revision The revision string
     * @return {boolean} True if ok, else false
     */
    priv.checkRevisionFormat = function (revision) {
        return /^[0-9]+-[0-9a-zA-Z]+$/.test(revision);
    };

    /**
     * Creates an empty document tree
     * @method createDocumentTree
     * @param  {array} children An array of children (optional)
     * @return {object} The new document tree
     */
    priv.createDocumentTree = function(children) {
        return {"children":children || []};
    };

    /**
     * Creates a new document tree node
     * @method createDocumentTreeNode
     * @param  {string} revision The node revision
     * @param  {string} status The node status
     * @param  {array} children An array of children (optional)
     * @return {object} The new document tree node
     */
    priv.createDocumentTreeNode = function(revision,status,children) {
        return {"rev":revision,"status":status,"children":children || []};
    };

    /**
     * Gets the winner revision from a document tree.
     * The winner is the deeper revision on the left.
     * @method getWinnerRevisionFromDocumentTree
     * @param  {object} document_tree The document tree
     * @return {string} The winner revision
     */
    priv.getWinnerRevisionFromDocumentTree = function (document_tree) {
        var i, result, search;
        result = {"deep":-1,"revision":''};
        // search method fills "result" with the winner revision
        search = function (document_tree, deep) {
            var i;
            if (document_tree.children.length === 0) {
                // This node is a leaf
                if (result.deep < deep) {
                    // The leaf is deeper than result
                    result = {"deep":deep,"revision":document_tree.rev};
                }
                return;
            }
            // This node has children
            for (i = 0; i < document_tree.children.length; i += 1) {
                // searching deeper to find the deeper leaf
                search(document_tree.children[i], deep+1);
            }
        };
        search(document_tree, 0);
        return result.rev;
    };

    /**
     * Add a document revision to the document tree
     * @method postToDocumentTree
     * @param  {object} doctree The document tree object
     * @param  {object} doc The document object
     * @return {array} The added document revs_info
     */
    priv.postToDocumentTree = function (doctree, doc) {
        var i, revs_info, next_rev, next_rev_str, selectNode, selected_node;
        revs_info = [];
        selected_node = doctree;
        selectNode = function (node) {
            var i;
            if (typeof node.rev !== "undefined") {
                // node is not root
                revs_info.unshift({"rev":node.rev,"status":node.status});
            }
            if (node.rev === doc._rev) {
                selected_node = node;
                return "node_selected";
            } else {
                for (i = 0; i < node.children.length; i += 1) {
                    if (selectNode(node.children[i]) === "node_selected") {
                        return "node_selected";
                    }
                    revs_info.shift();
                }
            }
        };
        if (typeof doc._rev === "string") {
            // document has a previous revision
            if (selectNode(selected_node) !== "node_selected") {
                // no node was selected, so add a node with a specific rev
                revs_info.unshift({
                    "rev": doc._rev,
                    "status": "missing"
                });
                selected_node.children.unshift({
                    "rev": doc._rev,
                    "status": "missing",
                    "children": []
                });
                selected_node = selected_node.children[0];
            }
        }
        next_rev = priv.generateNextRevision(
            doc._rev || 0, JSON.stringify(doc) + JSON.stringify(revs_info));
        next_rev_str = next_rev.join("-");
        // don't add if the next rev already exists
        console.log (JSON.stringify (revs_info));
        for (i = 0; i < selected_node.children.length; i += 1) {
            console.log (selected_node.children[i].rev);
            if (selected_node.children[i].rev === next_rev_str) {
                revs_info.unshift({
                    "rev": next_rev_str,
                    "status": "available"
                });
                if (selected_node.children[i].status !== "available") {
                    selected_node.children[i].status = "available";
                }
                return revs_info;
            }
        }
        revs_info.unshift({
            "rev": next_rev.join('-'),
            "status": "available"
        });
        selected_node.children.unshift({
            "rev": next_rev.join('-'),
            "status": "available",
            "children": []
        });
        return revs_info;
    };

    /**
     * Gets an array of leaves revisions from document tree
     * @method getLeavesFromDocumentTree
     * @param  {object} document_tree The document tree
     * @return {array} The array of leaves revisions
     */
    priv.getLeavesFromDocumentTree = function (document_tree) {
        var i, result, search;
        result = [];
        // search method fills [result] with the winner revision
        search = function (document_tree) {
            var i;
            if (document_tree.children.length === 0) {
                // This node is a leaf
                result.push(document_tree.rev);
                return;
            }
            // This node has children
            for (i = 0; i < document_tree.children.length; i += 1) {
                // searching deeper to find the deeper leaf
                search(document_tree.children[i]);
            }
        };
        search(document_tree);
        return result;
    };


    /**
     * Post the document metadata and create or update a document tree.
     * Options:
     * - {boolean} keep_revision_history To keep the previous revisions
     *                                   (false by default) (NYI).
     * @method post
     * @param  {object} command The JIO command
     */
    that.post = function (command) {
        var f = {}, doctree, revs_info, doc;
        doc = command.cloneDoc();
        if (typeof doc._rev === "string" &&
            priv.checkRevisionFormat(doc._rev)) {
            that.error({
                "status": 30,
                "statusText": "Wrong Revision Format",
                "error": "wrong_revision_format",
                "message": "The document previous revision does not match "+
                    "[0-9]+-[0-9a-zA-Z]+",
                "reason": "Previous revision is wrong"
            });
            return;
        }
        f.getDocumentTree = function () {
            var option = command.cloneOption();
            if (option["max_retry"] === 0) {
                option["max_retry"] = 3;
            }
            that.addJob(
                "get",
                priv.substorage,
                command.getDocId()+priv.doctree_suffix,
                option,
                function (response) {
                    doctree = response;
                    f.postDocument("put");
                }, function (err) {
                    switch(err.status) {
                    case 404:
                        doctree = priv.createDocumentTree();
                        f.postDocument("post");
                        break;
                    default:
                        err.message = "Cannot get document revision tree";
                        that.error(err);
                        break;
                    }
                }
            );
        };
        f.postDocument = function (doctree_update_method) {
            revs_info = priv.postToDocumentTree(doctree, doc);
            doc._id = command.getDocId()+"."+revs_info[0].rev;
            that.addJob(
                "post",
                priv.substorage,
                doc,
                command.cloneOption(),
                function (response) {
                    f.sendDocumentTree (doctree_update_method);
                }, function (err) {
                    switch(err.status) {
                    case 409:
                        // file already exists
                        f.sendDocumentTree (doctree_update_method);
                        break;
                    default:
                        err.message = "Cannot upload document".
                        that.error(err);
                        break;
                    }
                }
            );
        };
        f.sendDocumentTree = function (method) {
            doctree._id = command.getDocId()+priv.doctree_suffix;
            that.addJob(
                method,
                priv.substorage,
                doctree,
                command.cloneOption(),
                function (response) {
                    that.success({
                        "ok":true,
                        "id":command.getDocId(),
                        "rev":revs_info[0].rev
                    })
                }, function (err) {
                    // xxx do we try to delete the posted document ?
                    err.message = "Cannot save document revision tree";
                    that.error(err);
                }
            );
        };
        f.getDocumentTree();
    };

    return that;
});