/**
 * @class Ext.data.Tree
 * @extends Ext.util.Observable
 * Represents a tree data structure and bubbles all the events for its nodes. The nodes
 * in the tree have most standard DOM functionality.
 * @constructor
 * @param {Node} root (optional) The root node
 */

Ext.data.Tree = function(root){
   
this.nodeHash = {};
   
/**
    * The root node for this tree
    * @type Node
    */

   
this.root = null;
   
if(root){
       
this.setRootNode(root);
   
}
   
this.addEvents(
       
/**
        * @event append
        * Fires when a new child node is appended to a node in this tree.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The newly appended node
        * @param {Number} index The index of the newly appended node
        */

       
"append",
       
/**
        * @event remove
        * Fires when a child node is removed from a node in this tree.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The child node removed
        */

       
"remove",
       
/**
        * @event move
        * Fires when a node is moved to a new location in the tree
        * @param {Tree} tree The owner tree
        * @param {Node} node The node moved
        * @param {Node} oldParent The old parent of this node
        * @param {Node} newParent The new parent of this node
        * @param {Number} index The index it was moved to
        */

       
"move",
       
/**
        * @event insert
        * Fires when a new child node is inserted in a node in this tree.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The child node inserted
        * @param {Node} refNode The child node the node was inserted before
        */

       
"insert",
       
/**
        * @event beforeappend
        * Fires before a new child is appended to a node in this tree, return false to cancel the append.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The child node to be appended
        */

       
"beforeappend",
       
/**
        * @event beforeremove
        * Fires before a child is removed from a node in this tree, return false to cancel the remove.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The child node to be removed
        */

       
"beforeremove",
       
/**
        * @event beforemove
        * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
        * @param {Tree} tree The owner tree
        * @param {Node} node The node being moved
        * @param {Node} oldParent The parent of the node
        * @param {Node} newParent The new parent the node is moving to
        * @param {Number} index The index it is being moved to
        */

       
"beforemove",
       
/**
        * @event beforeinsert
        * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
        * @param {Tree} tree The owner tree
        * @param {Node} parent The parent node
        * @param {Node} node The child node to be inserted
        * @param {Node} refNode The child node the node is being inserted before
        */

       
"beforeinsert"
   
);

   
Ext.data.Tree.superclass.constructor.call(this);
};

Ext.extend(Ext.data.Tree, Ext.util.Observable, {
   
/**
     * @cfg {String} pathSeparator
     * The token used to separate paths in node ids (defaults to '/').
     */

    pathSeparator
: "/",

   
// private
    proxyNodeEvent
: function(){
       
return this.fireEvent.apply(this, arguments);
   
},

   
/**
     * Returns the root node for this tree.
     * @return {Node}
     */

    getRootNode
: function(){
       
return this.root;
   
},

   
/**
     * Sets the root node for this tree.
     * @param {Node} node
     * @return {Node}
     */

    setRootNode
: function(node){
       
this.root = node;
        node
.ownerTree = this;
        node
.isRoot = true;
       
this.registerNode(node);
       
return node;
   
},

   
/**
     * Gets a node in this tree by its id.
     * @param {String} id
     * @return {Node}
     */

    getNodeById
: function(id){
       
return this.nodeHash[id];
   
},

   
// private
    registerNode
: function(node){
       
this.nodeHash[node.id] = node;
   
},

   
// private
    unregisterNode
: function(node){
       
delete this.nodeHash[node.id];
   
},

   
toString : function(){
       
return "[Tree"+(this.id?" "+this.id:"")+"]";
   
}
});

/**
 * @class Ext.data.Node
 * @extends Ext.util.Observable
 * @cfg {Boolean} leaf true if this node is a leaf and does not have children
 * @cfg {String} id The id for this node. If one is not specified, one is generated.
 * @constructor
 * @param {Object} attributes The attributes/config for the node
 */

Ext.data.Node = function(attributes){
   
/**
     * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
     * @type {Object}
     */

   
this.attributes = attributes || {};
   
this.leaf = this.attributes.leaf;
   
/**
     * The node id. @type String
     */

   
this.id = this.attributes.id;
   
if(!this.id){
       
this.id = Ext.id(null, "xnode-");
       
this.attributes.id = this.id;
   
}
   
/**
     * All child nodes of this node. @type Array
     */

   
this.childNodes = [];
   
if(!this.childNodes.indexOf){ // indexOf is a must
       
this.childNodes.indexOf = function(o){
           
for(var i = 0, len = this.length; i < len; i++){
               
if(this[i] == o){
                   
return i;
               
}
           
}
           
return -1;
       
};
   
}
   
/**
     * The parent node for this node. @type Node
     */

   
this.parentNode = null;
   
/**
     * The first direct child node of this node, or null if this node has no child nodes. @type Node
     */

   
this.firstChild = null;
   
/**
     * The last direct child node of this node, or null if this node has no child nodes. @type Node
     */

   
this.lastChild = null;
   
/**
     * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
     */

   
this.previousSibling = null;
   
/**
     * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
     */

   
this.nextSibling = null;

   
this.addEvents({
       
/**
        * @event append
        * Fires when a new child node is appended
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The newly appended node
        * @param {Number} index The index of the newly appended node
        */

       
"append" : true,
       
/**
        * @event remove
        * Fires when a child node is removed
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The removed node
        */

       
"remove" : true,
       
/**
        * @event move
        * Fires when this node is moved to a new location in the tree
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} oldParent The old parent of this node
        * @param {Node} newParent The new parent of this node
        * @param {Number} index The index it was moved to
        */

       
"move" : true,
       
/**
        * @event insert
        * Fires when a new child node is inserted.
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The child node inserted
        * @param {Node} refNode The child node the node was inserted before
        */

       
"insert" : true,
       
/**
        * @event beforeappend
        * Fires before a new child is appended, return false to cancel the append.
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The child node to be appended
        */

       
"beforeappend" : true,
       
/**
        * @event beforeremove
        * Fires before a child is removed, return false to cancel the remove.
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The child node to be removed
        */

       
"beforeremove" : true,
       
/**
        * @event beforemove
        * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} oldParent The parent of this node
        * @param {Node} newParent The new parent this node is moving to
        * @param {Number} index The index it is being moved to
        */

       
"beforemove" : true,
       
/**
        * @event beforeinsert
        * Fires before a new child is inserted, return false to cancel the insert.
        * @param {Tree} tree The owner tree
        * @param {Node} this This node
        * @param {Node} node The child node to be inserted
        * @param {Node} refNode The child node the node is being inserted before
        */

       
"beforeinsert" : true
   
});
   
this.listeners = this.attributes.listeners;
   
Ext.data.Node.superclass.constructor.call(this);
};

Ext.extend(Ext.data.Node, Ext.util.Observable, {
   
// private
    fireEvent
: function(evtName){
       
// first do standard event for this node
       
if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
           
return false;
       
}
       
// then bubble it up to the tree if the event wasn't cancelled
       
var ot = this.getOwnerTree();
       
if(ot){
           
if(ot.proxyNodeEvent.apply(ot, arguments) === false){
               
return false;
           
}
       
}
       
return true;
   
},

   
/**
     * Returns true if this node is a leaf
     * @return {Boolean}
     */

    isLeaf
: function(){
       
return this.leaf === true;
   
},

   
// private
    setFirstChild
: function(node){
       
this.firstChild = node;
   
},

   
//private
    setLastChild
: function(node){
       
this.lastChild = node;
   
},


   
/**
     * Returns true if this node is the last child of its parent
     * @return {Boolean}
     */

    isLast
: function(){
       
return (!this.parentNode ? true : this.parentNode.lastChild == this);
   
},

   
/**
     * Returns true if this node is the first child of its parent
     * @return {Boolean}
     */

    isFirst
: function(){
       
return (!this.parentNode ? true : this.parentNode.firstChild == this);
   
},

   
/**
     * Returns true if this node has one or more child nodes, else false.
     * @return {Boolean}
     */

    hasChildNodes
: function(){
       
return !this.isLeaf() && this.childNodes.length > 0;
   
},
   
   
/**
     * Returns true if this node has one or more child nodes, or if the
expandable
     * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
     * @return {Boolean}
     */

    isExpandable
: function(){
       
return this.attributes.expandable || this.hasChildNodes();
   
},

   
/**
     * Insert node(s) as the last child node of this node.
     * @param {Node/Array} node The node or Array of nodes to append
     * @return {Node} The appended node if single append, or null if an array was passed
     */

    appendChild
: function(node){
       
var multi = false;
       
if(Ext.isArray(node)){
            multi
= node;
       
}else if(arguments.length > 1){
            multi
= arguments;
       
}
       
// if passed an array or multiple args do them one by one
       
if(multi){
           
for(var i = 0, len = multi.length; i < len; i++) {
               
this.appendChild(multi[i]);
           
}
       
}else{
           
if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
               
return false;
           
}
           
var index = this.childNodes.length;
           
var oldParent = node.parentNode;
           
// it's a move, make sure we move it cleanly
           
if(oldParent){
               
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
                   
return false;
               
}
                oldParent
.removeChild(node);
           
}
            index
= this.childNodes.length;
           
if(index === 0){
               
this.setFirstChild(node);
           
}
           
this.childNodes.push(node);
            node
.parentNode = this;
           
var ps = this.childNodes[index-1];
           
if(ps){
                node
.previousSibling = ps;
                ps
.nextSibling = node;
           
}else{
                node
.previousSibling = null;
           
}
            node
.nextSibling = null;
           
this.setLastChild(node);
            node
.setOwnerTree(this.getOwnerTree());
           
this.fireEvent("append", this.ownerTree, this, node, index);
           
if(oldParent){
                node
.fireEvent("move", this.ownerTree, node, oldParent, this, index);
           
}
           
return node;
       
}
   
},

   
/**
     * Removes a child node from this node.
     * @param {Node} node The node to remove
     * @return {Node} The removed node
     */

    removeChild
: function(node){
       
var index = this.childNodes.indexOf(node);
       
if(index == -1){
           
return false;
       
}
       
if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
           
return false;
       
}

       
// remove it from childNodes collection
       
this.childNodes.splice(index, 1);

       
// update siblings
       
if(node.previousSibling){
            node
.previousSibling.nextSibling = node.nextSibling;
       
}
       
if(node.nextSibling){
            node
.nextSibling.previousSibling = node.previousSibling;
       
}

       
// update child refs
       
if(this.firstChild == node){
           
this.setFirstChild(node.nextSibling);
       
}
       
if(this.lastChild == node){
           
this.setLastChild(node.previousSibling);
       
}

        node
.setOwnerTree(null);
       
// clear any references from the node
        node
.parentNode = null;
        node
.previousSibling = null;
        node
.nextSibling = null;
       
this.fireEvent("remove", this.ownerTree, this, node);
       
return node;
   
},

   
/**
     * Inserts the first node before the second node in this nodes childNodes collection.
     * @param {Node} node The node to insert
     * @param {Node} refNode The node to insert before (if null the node is appended)
     * @return {Node} The inserted node
     */

    insertBefore
: function(node, refNode){
       
if(!refNode){ // like standard Dom, refNode can be null for append
           
return this.appendChild(node);
       
}
       
// nothing to do
       
if(node == refNode){
           
return false;
       
}

       
if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
           
return false;
       
}
       
var index = this.childNodes.indexOf(refNode);
       
var oldParent = node.parentNode;
       
var refIndex = index;

       
// when moving internally, indexes will change after remove
       
if(oldParent == this && this.childNodes.indexOf(node) < index){
            refIndex
--;
       
}

       
// it's a move, make sure we move it cleanly
       
if(oldParent){
           
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
               
return false;
           
}
            oldParent
.removeChild(node);
       
}
       
if(refIndex === 0){
           
this.setFirstChild(node);
       
}
       
this.childNodes.splice(refIndex, 0, node);
        node
.parentNode = this;
       
var ps = this.childNodes[refIndex-1];
       
if(ps){
            node
.previousSibling = ps;
            ps
.nextSibling = node;
       
}else{
            node
.previousSibling = null;
       
}
        node
.nextSibling = refNode;
        refNode
.previousSibling = node;
        node
.setOwnerTree(this.getOwnerTree());
       
this.fireEvent("insert", this.ownerTree, this, node, refNode);
       
if(oldParent){
            node
.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
       
}
       
return node;
   
},

   
/**
     * Removes this node from its parent
     * @return {Node} this
     */

    remove
: function(){
       
this.parentNode.removeChild(this);
       
return this;
   
},

   
/**
     * Returns the child node at the specified index.
     * @param {Number} index
     * @return {Node}
     */

    item
: function(index){
       
return this.childNodes[index];
   
},

   
/**
     * Replaces one child node in this node with another.
     * @param {Node} newChild The replacement node
     * @param {Node} oldChild The node to replace
     * @return {Node} The replaced node
     */

    replaceChild
: function(newChild, oldChild){
       
var s = oldChild ? oldChild.nextSibling : null;
       
this.removeChild(oldChild);
       
this.insertBefore(newChild, s);
       
return oldChild;
   
},

   
/**
     * Returns the index of a child node
     * @param {Node} node
     * @return {Number} The index of the node or -1 if it was not found
     */

    indexOf
: function(child){
       
return this.childNodes.indexOf(child);
   
},

   
/**
     * Returns the tree this node is in.
     * @return {Tree}
     */

    getOwnerTree
: function(){
       
// if it doesn't have one, look for one
       
if(!this.ownerTree){
           
var p = this;
           
while(p){
               
if(p.ownerTree){
                   
this.ownerTree = p.ownerTree;
                   
break;
               
}
                p
= p.parentNode;
           
}
       
}
       
return this.ownerTree;
   
},

   
/**
     * Returns depth of this node (the root node has a depth of 0)
     * @return {Number}
     */

    getDepth
: function(){
       
var depth = 0;
       
var p = this;
       
while(p.parentNode){
           
++depth;
            p
= p.parentNode;
       
}
       
return depth;
   
},

   
// private
    setOwnerTree
: function(tree){
       
// if it is a move, we need to update everyone
       
if(tree != this.ownerTree){
           
if(this.ownerTree){
               
this.ownerTree.unregisterNode(this);
           
}
           
this.ownerTree = tree;
           
var cs = this.childNodes;
           
for(var i = 0, len = cs.length; i < len; i++) {
                cs
[i].setOwnerTree(tree);
           
}
           
if(tree){
                tree
.registerNode(this);
           
}
       
}
   
},
   
   
/**
     * Changes the id of this node.
     * @param {String} id The new id for the node.
     */

    setId
: function(id){
       
if(id !== this.id){
           
var t = this.ownerTree;
           
if(t){
                t
.unregisterNode(this);
           
}
           
this.id = id;
           
if(t){
                t
.registerNode(this);
           
}
           
this.onIdChange(id);
       
}
   
},
   
   
// private
    onIdChange
: Ext.emptyFn,

   
/**
     * Returns the path for this node. The path can be used to expand or select this node programmatically.
     * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
     * @return {String} The path
     */

    getPath
: function(attr){
        attr
= attr || "id";
       
var p = this.parentNode;
       
var b = [this.attributes[attr]];
       
while(p){
            b
.unshift(p.attributes[attr]);
            p
= p.parentNode;
       
}
       
var sep = this.getOwnerTree().pathSeparator;
       
return sep + b.join(sep);
   
},

   
/**
     * Bubbles up the tree from this node, calling the specified function with each node. The scope (
this) of
     * function call will be the scope provided or the current node. The arguments to the function
     * will be the args provided or the current node. If the function returns false at any point,
     * the bubble is stopped.
     * @param {Function} fn The function to call
     * @param {Object} scope (optional) The scope of the function (defaults to current node)
     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
     */

    bubble
: function(fn, scope, args){
       
var p = this;
       
while(p){
           
if(fn.apply(scope || p, args || [p]) === false){
               
break;
           
}
            p
= p.parentNode;
       
}
   
},

   
/**
     * Cascades down the tree from this node, calling the specified function with each node. The scope (
this) of
     * function call will be the scope provided or the current node. The arguments to the function
     * will be the args provided or the current node. If the function returns false at any point,
     * the cascade is stopped on that branch.
     * @param {Function} fn The function to call
     * @param {Object} scope (optional) The scope of the function (defaults to current node)
     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
     */

    cascade
: function(fn, scope, args){
       
if(fn.apply(scope || this, args || [this]) !== false){
           
var cs = this.childNodes;
           
for(var i = 0, len = cs.length; i < len; i++) {
                cs
[i].cascade(fn, scope, args);
           
}
       
}
   
},

   
/**
     * Interates the child nodes of this node, calling the specified function with each node. The scope (
this) of
     * function call will be the scope provided or the current node. The arguments to the function
     * will be the args provided or the current node. If the function returns false at any point,
     * the iteration stops.
     * @param {Function} fn The function to call
     * @param {Object} scope (optional) The scope of the function (defaults to current node)
     * @param {Array} args (optional) The args to call the function with (default to passing the current node)
     */

    eachChild
: function(fn, scope, args){
       
var cs = this.childNodes;
       
for(var i = 0, len = cs.length; i < len; i++) {
               
if(fn.apply(scope || this, args || [cs[i]]) === false){
                   
break;
               
}
       
}
   
},

   
/**
     * Finds the first child that has the attribute with the specified value.
     * @param {String} attribute The attribute name
     * @param {Mixed} value The value to search for
     * @return {Node} The found child or null if none was found
     */

    findChild
: function(attribute, value){
       
var cs = this.childNodes;
       
for(var i = 0, len = cs.length; i < len; i++) {
               
if(cs[i].attributes[attribute] == value){
                   
return cs[i];
               
}
       
}
       
return null;
   
},

   
/**
     * Finds the first child by a custom function. The child matches if the function passed
     * returns true.
     * @param {Function} fn
     * @param {Object} scope (optional)
     * @return {Node} The found child or null if none was found
     */

    findChildBy
: function(fn, scope){
       
var cs = this.childNodes;
       
for(var i = 0, len = cs.length; i < len; i++) {
               
if(fn.call(scope||cs[i], cs[i]) === true){
                   
return cs[i];
               
}
       
}
       
return null;
   
},

   
/**
     * Sorts this nodes children using the supplied sort function
     * @param {Function} fn
     * @param {Object} scope (optional)
     */

    sort
: function(fn, scope){
       
var cs = this.childNodes;
       
var len = cs.length;
       
if(len > 0){
           
var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
            cs
.sort(sortFn);
           
for(var i = 0; i < len; i++){
               
var n = cs[i];
                n
.previousSibling = cs[i-1];
                n
.nextSibling = cs[i+1];
               
if(i === 0){
                   
this.setFirstChild(n);
               
}
               
if(i == len-1){
                   
this.setLastChild(n);
               
}
           
}
       
}
   
},

   
/**
     * Returns true if this node is an ancestor (at any point) of the passed node.
     * @param {Node} node
     * @return {Boolean}
     */

    contains
: function(node){
       
return node.isAncestor(this);
   
},

   
/**
     * Returns true if the passed node is an ancestor (at any point) of this node.
     * @param {Node} node
     * @return {Boolean}
     */

    isAncestor
: function(node){
       
var p = this.parentNode;
       
while(p){
           
if(p == node){
               
return true;
           
}
            p
= p.parentNode;
       
}
       
return false;
   
},

   
toString : function(){
       
return "[Node"+(this.id?" "+this.id:"")+"]";
   
}
});