/*
Copyright 2012, KISSY UI Library v1.30rc
MIT Licensed
build time: Jul 11 21:28
*/
/**
 * modified from ckeditor for kissy editor ,walker implementation
 * refer: http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal#TreeWalker
 * @author yiminghe@gmail.com
 */
/*
 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
 For licensing, see LICENSE.html or http://ckeditor.com/license
 */
KISSY.add("editor/core/walker", function (S, Editor) {

    var TRUE = true,
        FALSE = false,
        NULL = null,
        UA = S.UA,
        DOM = S.DOM,
        dtd = Editor.XHTML_DTD,
        Node = S.Node;


    function iterate(rtl, breakOnFalseRetFalse) {
        var self = this;
        // Return NULL if we have reached the end.
        if (self._.end) {
            return NULL;
        }
        var node,
            range = self.range,
            guard,
            userGuard = self.guard,
            type = self.type,
            getSourceNodeFn = ( rtl ? '_4e_previousSourceNode' :
                '_4e_nextSourceNode' );

        // This is the first call. Initialize it.
        if (!self._.start) {
            self._.start = 1;

            // Trim text nodes and optimize the range boundaries. DOM changes
            // may happen at this point.
            range.trim();

            // A collapsed range must return NULL at first call.
            if (range.collapsed) {
                self.end();
                return NULL;
            }
        }

        // Create the LTR guard function, if necessary.
        if (!rtl && !self._.guardLTR) {
            // Gets the node that stops the walker when going LTR.
            var limitLTR = range.endContainer[0],
                blockerLTR = limitLTR.childNodes[range.endOffset];
            // 从左到右保证在 range 区间内获取 nextSourceNode
            this._.guardLTR = function (node, movingOut) {
                // 从endContainer移出去，失败返回false
                if (movingOut && (limitLTR == node || DOM.nodeName(node) == "body")) {
                    return false;
                }
                // 达到边界的下一个节点,注意 null 的情况
                // node 永远不能为 null
                return node != blockerLTR;

            };
        }

        // Create the RTL guard function, if necessary.
        if (rtl && !self._.guardRTL) {
            // Gets the node that stops the walker when going LTR.
            var limitRTL = range.startContainer[0],
                blockerRTL = ( range.startOffset > 0 ) &&
                    limitRTL.childNodes[range.startOffset - 1] || null;

            self._.guardRTL = function (node, movingOut) {
                // 从endContainer移出去，失败返回false
                if (movingOut && (limitRTL == node || DOM.nodeName(node) == "body")) {
                    return false;
                }
                // 达到边界的下一个节点,注意 null 的情况
                // node 永远不能为 null
                return node != blockerRTL;
            };
        }

        // Define which guard function to use.
        var stopGuard = rtl ? self._.guardRTL : self._.guardLTR;

        // Make the user defined guard function participate in the process,
        // otherwise simply use the boundary guard.
        if (userGuard) {
            guard = function (node, movingOut) {
                if (stopGuard(node, movingOut) === FALSE) {
                    return FALSE;
                }
                return userGuard(node, movingOut);
            };
        }
        else {
            guard = stopGuard;
        }

        if (self.current) {
            node = this.current[ getSourceNodeFn ](FALSE, type, guard);
        } else {
            // Get the first node to be returned.

            if (rtl) {
                node = range.endContainer;
                if (range.endOffset > 0) {
                    node = new Node(node[0].childNodes[range.endOffset - 1]);
                    if (guard(node[0]) === FALSE) {
                        node = NULL;
                    }
                } else {
                    node = ( guard(node, TRUE) === FALSE ) ?
                        NULL : node._4e_previousSourceNode(TRUE, type, guard, undefined);
                }
            }
            else {
                node = range.startContainer;
                node = new Node(node[0].childNodes[range.startOffset]);

                if (node.length) {
                    if (guard(node[0]) === FALSE) {
                        node = NULL;
                    }
                } else {
                    node = ( guard(range.startContainer, TRUE) === FALSE ) ?
                        NULL : range.startContainer._4e_nextSourceNode(TRUE, type, guard, undefined);
                }
            }
        }

        while (node && !self._.end) {
            self.current = node;
            if (!self.evaluator || self.evaluator(node[0]) !== FALSE) {
                if (!breakOnFalseRetFalse) {
                    return node;
                }
            } else if (breakOnFalseRetFalse && self.evaluator) {
                return FALSE;
            }
            node = node[ getSourceNodeFn ](FALSE, type, guard);
        }

        self.end();
        return self.current = NULL;
    }

    function iterateToLast(rtl) {
        var node,
            last = NULL;
        while (node = iterate.call(this, rtl)) {
            last = node;
        }
        return last;
    }

    /**
     * @name Walker
     * @param {Editor.Range} range
     * @class
     * Walker for DOM.
     * @memberOf Editor
     */
    function Walker(range) {
        this.range = range;

        /**
         * A function executed for every matched node, to check whether
         * it's to be considered into the walk or not. If not provided, all
         * matched nodes are considered good.
         * If the function returns "FALSE" the node is ignored.
         * @type Function
         * @memberOf Editor.Walker#
         */
        this.evaluator = NULL;// 当前 range 范围内深度遍历的元素调用

        /**
         * A function executed for every node the walk pass by to check
         * whether the walk is to be finished. It's called when both
         * entering and exiting nodes, as well as for the matched nodes.
         * If this function returns "FALSE", the walking ends and no more
         * nodes are evaluated.
         * @type Function
         * @memberOf Editor.Walker#
         */
        this.guard = NULL;// 人为缩小当前 range 范围


        /** @private */
        this._ = {};
    }


    S.augment(Walker,
        /**
         * @lends Editor.Walker#
         */
        {
            /**
             * Stop walking. No more nodes are retrieved if this function gets
             * called.
             */
            end:function () {
                this._.end = 1;
            },

            /**
             * Retrieves the next node (at right).
             * @returns {Boolean} The next node or NULL if no more
             *        nodes are available.
             */
            next:function () {
                return iterate.call(this);
            },

            /**
             * Retrieves the previous node (at left).
             * @returns {Boolean} The previous node or NULL if no more
             *        nodes are available.
             */
            previous:function () {
                return iterate.call(this, TRUE);
            },

            /**
             * Check all nodes at right, executing the evaluation function.
             * @returns {Boolean} "FALSE" if the evaluator function returned
             *        "FALSE" for any of the matched nodes. Otherwise "TRUE".
             */
            checkForward:function () {
                return iterate.call(this, FALSE, TRUE) !== FALSE;
            },

            /**
             * Check all nodes at left, executing the evaluation function.
             * 是不是 (不能后退了)
             * @returns {Boolean} "FALSE" if the evaluator function returned
             *        "FALSE" for any of the matched nodes. Otherwise "TRUE".
             */
            checkBackward:function () {
                // 在当前 range 范围内不会出现 evaluator 返回 false 的情况
                return iterate.call(this, TRUE, TRUE) !== FALSE;
            },

            /**
             * Executes a full walk forward (to the right), until no more nodes
             * are available, returning the last valid node.
             * @returns {Boolean} The last node at the right or NULL
             *        if no valid nodes are available.
             */
            lastForward:function () {
                return iterateToLast.call(this);
            },

            /**
             * Executes a full walk backwards (to the left), until no more nodes
             * are available, returning the last valid node.
             * @returns {Boolean} The last node at the left or NULL
             *        if no valid nodes are available.
             */
            lastBackward:function () {
                return iterateToLast.call(this, TRUE);
            },

            reset:function () {
                delete this.current;
                this._ = {};
            },

            // for unit test
            _iterator:iterate

        });


    S.mix(Walker,
        /**
         * @lends Editor.Walker
         */
        {
            /**
             * Whether the to-be-evaluated node is not a block node and does not match given node name map.
             * @param {Object} customNodeNames Given node name map.
             * @return {Function} Function for evaluation.
             */
            blockBoundary:function (customNodeNames) {
                return function (node) {
                    return !(node.nodeType == DOM.ELEMENT_NODE &&
                        DOM._4e_isBlockBoundary(node, customNodeNames) );
                };
            },

            /**
             * Whether the to-be-evaluated node is a bookmark node OR bookmark node
             * inner contents.
             * @param {Boolean} [contentOnly] Whether only test againt the text content of
             * bookmark node instead of the element itself(default).
             * @param {Boolean} [isReject] Whether should return 'FALSE' for the bookmark
             * node instead of 'TRUE'(default).
             * @return {Function} Function for evaluation.
             */
            bookmark:function (contentOnly, isReject) {
                function isBookmarkNode(node) {
                    return  DOM.nodeName(node) == 'span' &&
                        DOM.attr(node, '_ke_bookmark');
                }

                return function (node) {
                    var isBookmark, parent;
                    // Is bookmark inner text node?
                    isBookmark = ( node.nodeType == DOM.TEXT_NODE &&
                        ( parent = node.parentNode ) &&
                        isBookmarkNode(parent) );
                    // Is bookmark node?
                    isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode(node);
                    // !! 2012-05-15
                    // evaluator check ===false, must turn it to boolean false
                    return !!(isReject ^ isBookmark);
                };
            },

            /**
             * Whether the node is a text node containing only whitespaces characters.
             * @param {Boolean} [isReject]
             */
            whitespaces:function (isReject) {
                return function (node) {
                    var isWhitespace = node.nodeType == DOM.TEXT_NODE &&
                        !S.trim(node.nodeValue);
                    return !!(isReject ^ isWhitespace);
                };
            },
            /**
             * Whether the node is invisible in wysiwyg mode.
             * @param isReject
             */
            invisible:function (isReject) {
                var whitespace = Walker.whitespaces();
                return function (node) {
                    // Nodes that take no spaces in wysiwyg:
                    // 1. White-spaces but not including NBSP;
                    // 2. Empty inline elements, e.g. <b></b> we're checking here
                    // 'offsetHeight' instead of 'offsetWidth' for properly excluding
                    // all sorts of empty paragraph, e.g. <br />.
                    var isInvisible = whitespace(node) ||
                        node.nodeType == DOM.ELEMENT_NODE && !node.offsetHeight;
                    return !!(isReject ^ isInvisible);
                };
            }
        });

    var tailNbspRegex = /^[\t\r\n ]*(?:&nbsp;|\xa0)$/,
        isWhitespaces = Walker.whitespaces(),
        isBookmark = Walker.bookmark(),
        toSkip = function (node) {
            var name = DOM.nodeName(node);
            return isBookmark(node) ||
                isWhitespaces(node) ||
                node.nodeType == 1 &&
                    name in dtd.$inline &&
                    !( name in dtd.$empty );
        };

    // Check if there's a filler node at the end of an element, and return it.
    function getBogus(tail) {
        // Bogus are not always at the end, e.g. <p><a>text<br /></a></p>
        do {
            tail = tail._4e_previousSourceNode();
        } while (tail && toSkip(tail[0]));

        if (tail && ( !UA.ie ? tail.nodeName() == "br"
            : tail[0].nodeType == 3 && tailNbspRegex.test(tail.text()) )) {
            return tail[0];
        }
        return false;
    }

    Editor.Utils.injectDom({
        _4e_getBogus:function (el) {
            return getBogus(new Node(el));
        }
    });

    Editor.Walker = Walker;

    return Walker;
}, {
    requires:['./base', './utils', './dom']
});
