
/*
 * aciTree jQuery Plugin v4.5.0-rc.3
 * http://acoderinsights.ro
 *
 * Copyright (c) 2014 Dragos Ursu
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Require jQuery Library >= v1.9.0 http://jquery.com
 * + aciPlugin >= v1.5.1 https://github.com/dragosu/jquery-aciPlugin
 */

/*
 * The aciTree low-level DOM functions.
 *
 * A collection of functions optimised for aciTree DOM structure.
 *
 * Need to be included before the aciTree core and after aciPlugin.
 */

aciPluginClass.plugins.aciTree_dom = {
    // get the UL container from a LI
    // `node` must be valid LI DOM node
    // can return NULL
    container: function(node) {
        var container = node.lastChild;
        if (container && (container.nodeName == 'UL')) {
            return container;
        }
        return null;
    },
    // get the first children from a LI (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node
    // can return NULL
    firstChild: function(node, callback) {
        var container = this.container(node);
        if (container) {
            var firstChild = container.firstChild;
            if (callback) {
                while (firstChild && !callback.call(this, firstChild)) {
                    firstChild = firstChild.nextSibling;
                }
            }
            return firstChild;
        }
        return null;
    },
    // get the last children from a LI (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node
    // can return NULL
    lastChild: function(node, callback) {
        var container = this.container(node);
        if (container) {
            var lastChild = container.lastChild;
            if (callback) {
                while (lastChild && !callback.call(this, lastChild)) {
                    lastChild = lastChild.previousSibling;
                }
            }
            return lastChild;
        }
        return null;
    },
    // get the previous LI sibling (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node
    // can return NULL
    prev: function(node, callback) {
        var previous = node.previousSibling;
        if (callback) {
            while (previous && !callback.call(this, previous)) {
                previous = previous.previousSibling;
            }
        }
        return previous;
    },
    // get the next LI sibling (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node
    // can return NULL
    next: function(node, callback) {
        var next = node.nextSibling;
        if (callback) {
            while (next && !callback.call(this, next)) {
                next = next.nextSibling;
            }
        }
        return next;
    },
    // get the previous LI in tree order (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
    // can return NULL
    prevAll: function(node, callback) {
        var previous, lastChild, drillDown, match, prev, parent;
        while (true) {
            previous = this.prev(node);
            if (previous) {
                if (callback) {
                    match = callback.call(this, previous);
                    if (match === null) {
                        node = previous;
                        continue;
                    }
                }
                lastChild = this.lastChild(previous);
                if (lastChild) {
                    if (callback && (callback.call(this, lastChild) === null)) {
                        node = lastChild;
                        continue;
                    }
                    prev = false;
                    while (drillDown = this.lastChild(lastChild)) {
                        lastChild = drillDown;
                        if (callback) {
                            match = callback.call(this, lastChild);
                            if (match === null) {
                                node = lastChild;
                                prev = true;
                                break;
                            }
                        }
                    }
                    if (prev) {
                        continue;
                    }
                    if (callback) {
                        match = callback.call(this, lastChild);
                        if (match) {
                            return lastChild;
                        } else if (match !== null) {
                            node = lastChild;
                            continue;
                        }
                    } else {
                        return lastChild;
                    }
                } else {
                    if (!callback || match) {
                        return previous;
                    } else {
                        node = previous;
                        continue;
                    }
                }
            }
            parent = this.parent(node);
            if (parent) {
                if (callback) {
                    match = callback.call(this, parent);
                    if (match) {
                        return parent;
                    } else {
                        node = parent;
                    }
                } else {
                    return parent;
                }
            } else {
                return null;
            }
        }
        return null;
    },
    // get the next LI in tree order (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
    // can return NULL
    nextAll: function(node, callback) {
        var firstChild, match, next, parent, child;
        while (true) {
            firstChild = this.firstChild(node);
            if (firstChild) {
                if (callback) {
                    match = callback.call(this, firstChild);
                    if (match) {
                        return firstChild;
                    } else {
                        node = firstChild;
                        if (match !== null) {
                            continue;
                        }
                    }
                } else {
                    return firstChild;
                }
            }
            while (true) {
                next = this.next(node);
                if (next) {
                    if (callback) {
                        match = callback.call(this, next);
                        if (match) {
                            return next;
                        } else {
                            node = next;
                            if (match !== null) {
                                break;
                            } else {
                                continue;
                            }
                        }
                    } else {
                        return next;
                    }
                } else {
                    parent = node;
                    child = null;
                    while (parent = this.parent(parent)) {
                        next = this.next(parent);
                        if (next) {
                            if (callback) {
                                match = callback.call(this, next);
                                if (match) {
                                    return next;
                                } else {
                                    node = next;
                                    if (match !== null) {
                                        child = true;
                                    } else {
                                        child = false;
                                    }
                                    break;
                                }
                            } else {
                                return next;
                            }
                        }
                    }
                    if (child !== null) {
                        if (child) {
                            break;
                        } else {
                            continue;
                        }
                    }
                    return null;
                }
            }
        }
        return null;
    },
    // get the first LI in tree order (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
    // can return NULL
    first: function(node, callback) {
        var container = this.container(node);
        if (container) {
            var firstChild = container.firstChild;
            if (firstChild) {
                if (callback && !callback.call(this, firstChild)) {
                    return this.nextAll(firstChild, callback);
                }
                return firstChild;
            }
        }
        return null;
    },
    // get the last LI in tree order (with filtering)
    // `node` must be valid LI DOM node
    // `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
    // can return NULL
    last: function(node, callback) {
        var container = this.container(node);
        if (container) {
            var lastChild = container.lastChild;
            if (lastChild) {
                if (callback && (callback.call(this, lastChild) === null)) {
                    return this.prevAll(lastChild, callback);
                } else {
                    var drillDown;
                    while (drillDown = this.lastChild(lastChild)) {
                        lastChild = drillDown;
                    }
                    if (callback && !callback.call(this, lastChild)) {
                        return this.prevAll(lastChild, callback);
                    }
                    return lastChild;
                }
            }
        }
        return null;
    },
    // get the children LI from the node
    // `node` must be valid LI DOM node
    // `drillDown` if TRUE all children are returned
    // `callback` can return FALSE to skip a node or NULL to prevent drill down/skip the node
    children: function(node, drillDown, callback) {
        var children = [], levels = [], match, next, skip;
        var firstChild = this.firstChild(node);
        if (firstChild) {
            while (true) {
                skip = false;
                do {
                    if (callback) {
                        match = callback.call(this, firstChild);
                        if (match) {
                            children.push(firstChild);
                        }
                        if (drillDown && (match !== null)) {
                            next = this.firstChild(firstChild);
                            if (next) {
                                levels.push(firstChild);
                                firstChild = next;
                                skip = true;
                                break;
                            }
                        }
                    } else {
                        children.push(firstChild);
                        if (drillDown) {
                            next = this.firstChild(firstChild);
                            if (next) {
                                levels.push(firstChild);
                                firstChild = next;
                                skip = true;
                                break;
                            }
                        }
                    }
                } while (firstChild = firstChild.nextSibling);
                if (!skip) {
                    while (firstChild = levels.pop()) {
                        firstChild = firstChild.nextSibling;
                        if (firstChild) {
                            break;
                        }
                    }
                    if (!firstChild) {
                        break;
                    }
                }
            }
        }
        return children;
    },
    // get a children from the node
    // `node` must be valid DOM node
    // `callback` can return FALSE to skip a node or NULL to stop the search
    // can return NULL
    childrenTill: function(node, callback) {
        var levels = [], match, next, skip;
        var firstChild = node.firstChild;
        if (firstChild) {
            while (true) {
                skip = false;
                do {
                    match = callback.call(this, firstChild);
                    if (match) {
                        return firstChild;
                    } else if (match === null) {
                        return null;
                    }
                    next = firstChild.firstChild;
                    if (next) {
                        levels.push(firstChild);
                        firstChild = next;
                        skip = true;
                        break;
                    }
                } while (firstChild = firstChild.nextSibling);
                if (!skip) {
                    while (firstChild = levels.pop()) {
                        firstChild = firstChild.nextSibling;
                        if (firstChild) {
                            break;
                        }
                    }
                    if (!firstChild) {
                        break;
                    }
                }
            }
        }
        return null;
    },
    // get a children from the node having a class
    // `node` must be valid DOM node
    // `className` String or Array to check for
    // can return NULL
    childrenByClass: function(node, className) {
        if (node.getElementsByClassName) {
            var list = node.getElementsByClassName(className instanceof Array ? className.join(' ') : className);
            return list ? list[0] : null;
        } else {
            return this.childrenTill(node, function(node) {
                return this.hasClass(node, className);
            });
        }
    },
    // get the parent LI from the children LI
    // `node` must be valid LI DOM node
    // can return NULL
    parent: function(node) {
        var parent = node.parentNode.parentNode;
        if (parent && (parent.nodeName == 'LI')) {
            return parent;
        }
        return null;
    },
    // get the parent LI from any children
    // `node` must be valid children of a LI DOM node
    // can return NULL
    parentFrom: function(node) {
        while (node.nodeName != 'LI') {
            node = node.parentNode;
            if (!node) {
                return null;
            }
        }
        return node;
    },
    // get a parent from the node
    // `node` must be valid DOM node
    // `callback` can return FALSE to skip a node or NULL to stop the search
    // can return NULL
    parentTill: function(node, callback) {
        var match;
        while (node = node.parentNode) {
            match = callback.call(this, node);
            if (match) {
                return node;
            } else if (match === null) {
                return null;
            }
        }
        return null;
    },
    // get a parent from the node having a class
    // `node` must be valid DOM node
    // `className` String or Array to check for
    // can return NULL
    parentByClass: function(node, className) {
        return this.parentTill(node, function(node) {
            return this.hasClass(node, className);
        });
    },
    // test if node has class(es)
    // `className` String or Array to check for
    // `withOut` String or Array to exclude with
    hasClass: function(node, className, withOut) {
        var oldClass = ' ' + node.className + ' ';
        if (withOut instanceof Array) {
            for (var i = 0; i < withOut.length; i++) {
                if (oldClass.indexOf(' ' + withOut[i] + ' ') != -1) {
                    return false;
                }
            }
        } else {
            if (withOut && oldClass.indexOf(' ' + withOut + ' ') != -1) {
                return false;
            }
        }
        if (className instanceof Array) {
            for (var i = 0; i < className.length; i++) {
                if (oldClass.indexOf(' ' + className[i] + ' ') == -1) {
                    return false;
                }
            }
        } else {
            if (oldClass.indexOf(' ' + className + ' ') == -1) {
                return false;
            }
        }
        return true;
    },
    // filter nodes with class(es)
    // `nodes` Array of DOM nodes
    // @see `hasClass`
    withClass: function(nodes, className, withOut) {
        var filter = [];
        for (var i = 0; i < nodes.length; i++) {
            if (this.hasClass(nodes[i], className, withOut)) {
                filter.push(nodes[i]);
            }
        }
        return filter;
    },
    // test if node has any class(es)
    // `className` String or Array to check for (any class)
    // `withOut` String or Array to exclude with
    hasAnyClass: function(node, className, withOut) {
        var oldClass = ' ' + node.className + ' ';
        if (withOut instanceof Array) {
            for (var i = 0; i < withOut.length; i++) {
                if (oldClass.indexOf(' ' + withOut[i] + ' ') != -1) {
                    return false;
                }
            }
        } else {
            if (withOut && oldClass.indexOf(' ' + withOut + ' ') != -1) {
                return false;
            }
        }
        if (className instanceof Array) {
            for (var i = 0; i < className.length; i++) {
                if (oldClass.indexOf(' ' + className[i] + ' ') != -1) {
                    return true;
                }
            }
        } else {
            if (oldClass.indexOf(' ' + className + ' ') != -1) {
                return true;
            }
        }
        return false;
    },
    // filter nodes with any class(es)
    // `nodes` Array of DOM nodes
    // @see `hasAnyClass`
    withAnyClass: function(nodes, className, withOut) {
        var filter = [];
        for (var i = 0; i < nodes.length; i++) {
            if (this.hasAnyClass(nodes[i], className, withOut)) {
                filter.push(nodes[i]);
            }
        }
        return filter;
    },
    // add class(es) to node
    // `node` must be valid DOM node
    // `className` String or Array to add
    // return TRUE if className changed
    addClass: function(node, className) {
        var oldClass = ' ' + node.className + ' ', append = '';
        if (className instanceof Array) {
            for (var i = 0; i < className.length; i++) {
                if (oldClass.indexOf(' ' + className[i] + ' ') == -1) {
                    append += ' ' + className[i];
                }
            }
        } else {
            if (oldClass.indexOf(' ' + className + ' ') == -1) {
                append += ' ' + className;
            }
        }
        if (append) {
            node.className = node.className + append;
            return true;
        }
        return false;
    },
    // add class(es) to nodes
    // `nodes` Array of DOM nodes
    // @see `addClass`
    addListClass: function(nodes, className, callback) {
        for (var i = 0; i < nodes.length; i++) {
            this.addClass(nodes[i], className);
            if (callback) {
                callback.call(this, nodes[i]);
            }
        }
    },
    // remove class(es) from node
    // `node` must be valid DOM node
    // `className` String or Array to remove
    // return TRUE if className changed
    removeClass: function(node, className) {
        var oldClass = ' ' + node.className + ' ';
        if (className instanceof Array) {
            for (var i = 0; i < className.length; i++) {
                oldClass = oldClass.replace(' ' + className[i] + ' ', ' ');
            }
        } else {
            oldClass = oldClass.replace(' ' + className + ' ', ' ');
        }
        oldClass = oldClass.substr(1, oldClass.length - 2);
        if (node.className != oldClass) {
            node.className = oldClass;
            return true;
        }
        return false;
    },
    // remove class(es) from nodes
    // `nodes` Array of DOM nodes
    // @see `removeClass`
    removeListClass: function(nodes, className, callback) {
        for (var i = 0; i < nodes.length; i++) {
            this.removeClass(nodes[i], className);
            if (callback) {
                callback.call(this, nodes[i]);
            }
        }
    },
    // toggle node class(es)
    // `node` must be valid DOM node
    // `className` String or Array to toggle
    // `add` TRUE to add them
    // return TRUE if className changed
    toggleClass: function(node, className, add) {
        if (add) {
            return this.addClass(node, className);
        } else {
            return this.removeClass(node, className);
        }
    },
    // toggle nodes class(es)
    // `nodes` Array of DOM nodes
    // @see `toggleClass`
    toggleListClass: function(nodes, className, add, callback) {
        for (var i = 0; i < nodes.length; i++) {
            this.toggleClass(nodes[i], className, add);
            if (callback) {
                callback.call(this, nodes[i]);
            }
        }
    },
    // add/remove and keep old class(es)
    // `node` must be valid DOM node
    // `addClass` String or Array to add
    // `removeClass` String or Array to remove
    // return TRUE if className changed
    addRemoveClass: function(node, addClass, removeClass) {
        var oldClass = ' ' + node.className + ' ';
        if (removeClass) {
            if (removeClass instanceof Array) {
                for (var i = 0; i < removeClass.length; i++) {
                    oldClass = oldClass.replace(' ' + removeClass[i] + ' ', ' ');
                }
            } else {
                oldClass = oldClass.replace(' ' + removeClass + ' ', ' ');
            }
        }
        if (addClass) {
            var append = '';
            if (addClass instanceof Array) {
                for (var i = 0; i < addClass.length; i++) {
                    if (oldClass.indexOf(' ' + addClass[i] + ' ') == -1) {
                        append += addClass[i] + ' ';
                    }
                }
            } else {
                if (oldClass.indexOf(' ' + addClass + ' ') == -1) {
                    append += addClass + ' ';
                }
            }
            oldClass += append;
        }
        oldClass = oldClass.substr(1, oldClass.length - 2);
        if (node.className != oldClass) {
            node.className = oldClass;
            return true;
        }
        return false;
    },
    // add/remove and keep old class(es)
    // `nodes` Array of DOM nodes
    // @see `addRemoveClass`
    addRemoveListClass: function(nodes, addClass, removeClass, callback) {
        for (var i = 0; i < nodes.length; i++) {
            this.addRemoveClass(nodes[i], addClass, removeClass);
            if (callback) {
                callback.call(this, nodes[i]);
            }
        }
    }
};
