/*jslint vars: true, white: true, plusplus: true, maxerr: 50, indent: 4 */



// Offers functional views, traversals of the DOM and other tree-like structures.
// See Functional Pearl: The Zipper, by G\'erard Huet
// J. Functional Programming 7 (5): 549--554 Sepember 1997


var TreeCursor = (function() {
    "use strict";

    var TreeCursor = function(parent, node, prevs, nexts, openF, closeF, atomicF) {
        this.parent = parent; // Parent can be the top (undefined), or a TreeCursor
        this.node = node;
        this.prevs = prevs;
        this.nexts = nexts;

        // openF: node -> (arrayof node)
        this.openF = openF;

        // closeF: node (arrayof node) -> node
        // Given a node and its array of children, return a new node.
        this.closeF = closeF;

        // atomicF: node -> boolean
        // Produces true if the node should be treated atomically.
        this.atomicF = atomicF;
    };

    TreeCursor.prototype.isOnAtomicElement = function() {
        return this.atomicF(this.node);
    };

    TreeCursor.prototype.canDown = function() {
        return (!(this.atomicF(this.node)) &&
                this.openF(this.node).length !== 0);
    };

    TreeCursor.prototype.down = function() {
        if (this.atomicF(this.node)) {
            throw new Error("down of atomic element");
        }
        var opened = this.openF(this.node);
        if (opened.length === 0) {
            throw new Error("down of empty");
        }
        return new TreeCursor(this, 
                              opened[0],
                              [],
                              opened.slice(1),
                              this.openF, 
                              this.closeF,
                              this.atomicF);
    };


    TreeCursor.prototype.canUp = function() {
        return this.parent !== undefined;
    };

    TreeCursor.prototype.up = function() {
        var parent = this.parent;
        return new TreeCursor(parent.parent,
                              this.closeF(parent.node,
                                          this.prevs.concat([this.node]).concat(this.nexts)),
                              parent.prevs,
                              parent.nexts,
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.canLeft = function() { return this.prevs.length !== 0; };

    TreeCursor.prototype.left = function() {
        if (this.prevs.length === 0) { throw new Error("left of first"); }
        return new TreeCursor(this.parent,
                              this.prevs[this.prevs.length - 1],
                              this.prevs.slice(0, this.prevs.length - 1),
                              [this.node].concat(this.nexts),
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.canRight = function() { return this.nexts.length !== 0; };

    TreeCursor.prototype.right = function() {
        if (this.nexts.length === 0) { throw new Error("right of last"); }
        return new TreeCursor(this.parent,
                              this.nexts[0],
                              this.prevs.concat([this.node]),
                              this.nexts.slice(1),
                              this.openF, 
                              this.closeF,
                              this.atomicF);
    };
    
    TreeCursor.prototype.succ = function() {
        var n;
        if (this.canDown()) {
            return this.down();
        } else if (this.canRight()) {
            return this.right();
        } else {
            n = this;
            while (true) {
                n = n.up();
                if (n.canRight()) {
                    return n.right();
                }
            }
        }
    };
 
    TreeCursor.prototype.pred = function() {
        var n;
        if (this.canLeft()) {
            n = this.left();
            while (n.canDown()) {
                n = n.down();
                while (n.canRight()) {
                    n = n.right();
                }
            }
            return n;
        } else {
            return this.up();
        }
    };

    TreeCursor.prototype.canPred = function() {
        return this.canLeft() || this.canUp();
    };

    TreeCursor.prototype.canSucc = function() {
        var n;
        if (this.canDown()) {
            return true;
        } else if (this.canRight()) {
            return true;
        } else {
            n = this;
            while (true) {
                if (! n.canUp()) { return false; }
                n = n.up();
                if (n.canRight()) {
                    return true;
                }
            }
        }
    };

    TreeCursor.prototype.top = function() {
        var n = this;
        while (n.canUp()) { n = n.up(); }
        return n;
    };


    //////////////////////////////////////////////////////////////////////


    TreeCursor.prototype.replaceNode = function(n) {
        return new TreeCursor(this.parent,
                              n,
                              this.prevs,
                              this.nexts,
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.insertRight = function(n) {
        return new TreeCursor(this.parent,
                              n,
                              this.prevs.concat([this.node]),
                              this.nexts,
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.insertLeft = function(n) {
        return new TreeCursor(this.parent,
                              n,
                              this.prevs,
                              [this.node].concat(this.nexts),
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.insertDown = function(n) {
        if (this.atomicF(this.node)) {
            throw new Error("down of atomic element");
        }
        return new TreeCursor(this,
                              n,
                              [],
                              this.openF(this.node),
                              this.openF,
                              this.closeF,
                              this.atomicF);
    };

    TreeCursor.prototype.deleteNode = function() {
        var parent;
        if (this.nexts.length !== 0) {
            return new TreeCursor(this.parent,
                                  this.nexts[0],
                                  this.prevs,
                                  this.nexts.slice(1),
                                  this.openF,
                                  this.closeF,
                                  this.atomicF);
        } else if (this.prevs.length !== 0) {
            return new TreeCursor(this.parent,
                                  this.prevs[this.prevs.length - 1],
                                  this.prevs.slice(0, this.prevs.length - 1),
                                  this.nexts,
                                  this.openF,
                                  this.closeF,
                                  this.atomicF);
        } else {
            parent = this.parent;
            return new TreeCursor(parent.parent,
                                  this.closeF(parent.node, []),
                                  parent.prevs,
                                  parent.nexts,
                                  this.openF,
                                  this.closeF,
                                  this.atomicF);
        }
    };






    //////////////////////////////////////////////////////////////////////



    TreeCursor.adaptTreeCursor = function(node, openF, closeF, atomicF) {
        return new TreeCursor(undefined,
                              node,
                              [],
                              [],
                              openF,
                              closeF,
                              atomicF);
    };


    TreeCursor.arrayToCursor = function(anArray) {
        var arrayOpenF = function(n) { 
            if (n instanceof Array) { 
                return n;
            } else {
                return [];
            }
        };
        var arrayCloseF = function(n, children) { 
            if (n instanceof Array) {
                return children;
            } else {
                return n;
            }
        };

        var arrayAtomicF = function(n) {
            return !(n instanceof Array);
        };
        return TreeCursor.adaptTreeCursor(anArray,
                                          arrayOpenF,
                                          arrayCloseF,
                                          arrayAtomicF);
    };


    TreeCursor.domToCursor = function(dom) {
        var domOpenF = 
            // To go down, just take the children.
            function(n) { 
                return [].slice.call(n.childNodes, 0);
            };
        var domCloseF = 
            // To go back up, take the node, do a shallow cloning, and replace the children.
            function(node, children) { 
                var i;
                var newNode = node.cloneNode(false);
                for (i = 0; i < children.length; i++) {
                    newNode.appendChild(children[i].cloneNode(true));
                }
                return newNode; 
            };
        var domAtomicF =
            function(node) {
                return node.nodeType !== 1;
            };
        return TreeCursor.adaptTreeCursor(dom.cloneNode(true),
                                          domOpenF,
                                          domCloseF,
                                          domAtomicF);
    };


    return TreeCursor;
}());