/**
 * @fileoverview EasyGUI TreeCtrl
 *
 * This Class creates a TreeCtrl
 *
 * @author Gaetan Lauff <glauff@plansoft.de>
 * @package 	js.widgets
 * 
 * @version	$Id: class.treectrl.js 286 2007-12-13 15:30:41Z glauff $
 */
var activeButton = null;

/**
 * @class	EasyGUI_TreeCtrl
 * @constructor
 * @param	{string}	id	ID of Widget to create
 * @returns	void
 * @access	public
 */
var EasyGUI_TreeCtrl = Class.create();

Object.extend( Object.extend( EasyGUI_TreeCtrl.prototype, EasyGUI_widget.prototype), {
	/**
	 * Type of widget
	 * @type	string
	 * @private
	 */
	type: 'easygui-treectrl',
	
	/**
	 * @type	boolean
	 * @private
	 */
	closeSiblings: true,
	
	/**
	 * @type	boolean
	 * @private
	 */
	closeChilds: true,
	
	/**
	 * Holds all Items of the widget
	 * @type	array
	 * @private
	 */
	items: null,
	
	/**
	 * Target Frame or Window for submission
	 * @type	string
	 * @private
	 */
	target: '_self',
	
	/**
	 * Holds the Style Definitions for the widget
	 * @type	array
	 * @private
	 */
	styleDefinitions: null,
	
	/**
	 * Target Dialog ID
	 * @type	string
	 * @private
	 */
	targetDlg: null,
	
	/**
	 * Selected ID of Tree Item
	 * @type	string
	 * @private
	 */
	selected: null,
	
	/**
	 * @type	array
	 * @private
	 */
	treeitems: [],
	
	/**
	 * @type
	 * @private
	 */
	search: null,
	
	/**
	 * @type	boolean
	 * @private
	 */
	searchInitialized: false,
	
	/**
	 * @type	boolean
	 * @private
	 */
	currentSearchExpr: false,
	
	/**
	 * @type	integer
	 * @private
	 */
	currentSearchIdx: 0,
	
	/**
	 * @type	integer
	 * @private
	 */
	currentDepth: 0,
	
	root: null,
	
	handlers: [],
	
	evt: null,
	
	setEvent: function( evt ) {
		this.evt = evt;
	},
	
	/**
	 * Inits the whole Tree
	 */
	render: function()
	{
		if (!this.element.childNodes.length) {
			return;
		}

		this.styleDefinitions = { defaults: 'easygui-treectrl-node-default', leaf: null, levels:[] };
		
		this.items 				= Element.getElementsByTagName( this.element, 'DIV' );
		
		var defs			= Element.getAttributeValue( this.element, 'styledefinitions', null );
		styleDefinitions 	= Util.getJSObject( defs );
		
		if ( !styleDefinitions ) {
			this.setStyleDefinitions( defs );
		} else {
			this.styleDefinitions = styleDefinitions;
		}
		
		this.target 			= Element.getAttributeValue( this.element, 'target', this.target );
		this.targetDlg 			= Element.getAttributeValue( this.element, 'targetdlg' );
		this.focusFirstItem		= Element.getAttributeValue( this.element, 'focusfirstitem', false );
		this.openRoot		    = Element.getAttributeValue( this.element, 'openroot', false );
		this.closeChilds		= Element.getAttributeValue( this.element, 'closechilds', true );
		this.prevSelChange 		= Element.getAttributeValue( this.element, 'preventselectionchange', false );
		
		this.onSelectionChange = Element.getAttributeValue( this.element, 'onselectionchange', 'navigate' );
		
		if ( this.onSelectionChange ) 
			this.onSelectionChange = eval( this.onSelectionChange );
		
		Event.observe( this.element, 'click', this.defaultHandler.bindAsEventListener( this ), false );
		Event.observe( this.element, 'click', this.focusItem.bindAsEventListener( this ) );

		this.element.style.display 	= 'block';
		this.element.state 			= 'opened';

		this.root = Element.getFirstChild( this.element );
		
		Form.insertHidden( this.id + '_EK_WIDGET_VALUE' );
		
		this.openTreeItem( this.element );
		
        this.selected = Element.getAttributeValue( this.element, 'selected' );
        
        if ( this.focusFirstItem )
            this.selected = Element.getAttributeValue( this.element, 'selected', Element.getFirstChild( this.root ).id );
		
		if ( ( this.selected && this.selected != this.id ) || this.focusFirstItem ) {
			this.focusItem( this.selected );
			
			if ( this.target != '_self' && !this.prevSelChange ) {
				this.onSelectionChange.call( this, this.selected );
			}
				
			this.setSelected( this.selected );
		} else {
            if ( this.openRoot ) {
                this.openTreeItem( this.root );
            }
        }
	},
	
	setStyleDefinitions: function( defs ) {	
		if ( defs == null ) {
			return;
		}
			
		var definition;
		var pattern = /\[([^\]]*)\]/g;
		
		while ((definition = pattern.exec(defs)) != null) {
			var def		= definition[1].split(':');	
			var defName = parseInt( def[0] );
			
			if ( isNaN( defName ) ) {
				//alert( def[0] );
				if ( def[0] == 'default' ) {
					this.styleDefinitions['defaults'] = def[1];
				}
				else {
					this.styleDefinitions[def[0]] = def[1];
				}
			}
			else {
				this.styleDefinitions.levels[defName] = def[1];
			}
		}
	},
	
	/**
	 * Get A Style Definition for a tree
	 * @param	{mixed}		level		level can be string or numeric value to denote the level for which you want to get the definition.
	 * @param	{integer}	childNodes	Number of child nodes
	 * @returns A Style Definition for Tree
	 * @private
	 */
	getStyleDefinition: function(level, childNodes, treeitem )
	{
		var customstyle = Element.getAttributeValue( treeitem, 'customstyle' );
		
		if (!customstyle) {
			if (this.styleDefinitions['leaf'] != null && childNodes == 0) {
				if ( this.styleDefinitions['leaf'] != null ) {
					return this.styleDefinitions['leaf'];
				}
			}
				
			if (typeof(level) == 'number') {
				if ( this.styleDefinitions.levels ) {
					while (this.styleDefinitions.levels[level] == null && level > 0) {
						level--;
					}
					
					if (this.styleDefinitions.levels[level] != null) {
						return this.styleDefinitions.levels[level];
					}
				}
			}
			
			return this.styleDefinitions.defaults;
		} else {
			return customstyle;
		}
	},
	
	onDblClick: function( evt ) {
		alert( 'Detected a Dbl Click on ' + $GE(evt).id );
	},
	
	/**
	 * Prepare the Subtree
	 */
	prepareSubTree: function( subtree )
	{
		if (subtree == null) {
			subtree = this.element;
		}
		
		var prepared = Element.getAttributeValue( subtree, 'prepared' );
		
		if (prepared == 'true' || prepared) {			
			return ;
		}
		
		for (var i=0; i < subtree.childNodes.length; i++ ) 
		{
			if ( subtree.childNodes[i].nodeName == 'DIV' ) 
			{
				var treeitem 	= Element.getFirstChildByTagName( subtree.childNodes[i], 'A');
				var depth	 	= Element.getDepth( subtree.childNodes[i], 'DIV', 'TreeCtrl' );
				
				var cn			= subtree.childNodes[i].getElementsByTagName('DIV');
				var selector	= this.getStyleDefinition( depth, parseInt( cn.length ), treeitem );
				
				var paddingLeft = parseInt( Dialog.Style.getStyle( selector, 'paddingLeft', 'custom') );

				//console.log("Selector: " + selector + " paddingLeft: " + paddingLeft );
				
				if (isNaN(paddingLeft)) {
					paddingLeft = 0;
				}
				
				var marginLeft = parseInt( Dialog.Style.getStyle( selector, 'marginLeft', 'custom' ) );
			
				if (isNaN(marginLeft)) {
					marginLeft = 0;
				}
				
				var bgPos = Dialog.Style.getStyle( selector, 'backgroundPosition', 'custom' );
				
				if (!bgPos) {
					bgPos = "0 0";
				}
				
				var bgPos = bgPos.split(' ');
				
				vPos = parseInt(bgPos[0]);
				hPos = parseInt(bgPos[1]);
				
				if (isNaN(hPos)) {
					hPos = bgPos[1];
				} else {
					hPos += 'px';
				}
				
				if (isNaN(vPos)) {
					vPos = bgPos[0];
				} else {
					vPos = (((depth * paddingLeft) - (depth * marginLeft)) + vPos)
					vPos += 'px';
				}
				
				subtree.childNodes[i].style.marginLeft = marginLeft + 'px';
				treeitem.className = selector + ' ' + selector + '-closed';
				
				/*
				if ( treeitem.onclick ) {
					this.handlers[treeitem.id] = treeitem.onclick;
					//treeitem.onclick = this.onTreeItemClick.bindAsEventListener(this);
					Event.observe( treeitem, 'click', this.onTreeItemClick.bindAsEventListener( this ) );
					treeitem.onclick = null;
				}
				*/
				
				//treeitem.onfocus = function() { this.blur(); };
				Event.observe(treeitem, "focus", this.onFocus.bindAsEventListener(this) );
				treeitem.style.marginLeft = '0px';
				var paddingLeft = (( (depth * paddingLeft) - (depth * marginLeft) ) + paddingLeft);
			
				treeitem.style.paddingLeft = paddingLeft + 'px';
				treeitem.style.backgroundPosition =  vPos + ' ' + hPos;
			}
		}
		
		subtree.setAttribute( 'prepared', true );
	},
	
	onFocus : function( evt )
	{
		var item = $GE( evt );
		item.blur();;
	},
	
	onTreeItemClick: function( evt ) {
		var element = $GE(evt);
		this.setSelected( element.id );
		this.setEvent( evt );
		
		if (this.handlers[element.id] )
			this.handlers[element.id].call( this, evt );
	},
	
	defaultHandler: function( evt ) {
		var element = Event.element(evt);
		
		if ( element === this.element ) 
			return;
			
		this.onSelectionChange.call( this, evt );
		Event.stop( evt );
	},
			
	/**
	 * Sets the selected MenuTreeItem to the given id
	 * @param	{string}	id 	ID of TreeItem
	 * @returns	void
	 * @access	public
	 */
	setSelected: function( id )
	{
		Form.Element.setValue( this.id + '_EK_WIDGET_VALUE', id );
		
		this.element.selected 	= id;
		this.selected			= id;
	},
	
	/**
	 * Returns the selected Node
	 *
	 * @returns	string
	 * @access	public
	 */
	getSelected: function()
	{
		return this.selected;
	},
		
	/**
	 * Opens Tree Item
	 * @param	{object} item DOM Element
	 * @returns	void
	 * @private
	 */
	openTreeItem: function( item )
	{
		this.prepareSubTree( item );
		var cNodes = item.childNodes;
		
		for (var i=0; i < cNodes.length; i++) {
			if (cNodes[i].nodeName == 'DIV' ) {
				cNodes[i].style.display = 'block';
			}
		}
	},
	
	/**
	 * Closes TreeItem
	 * @param	item	object	DOM Element
	 * @returns	void
	 * @private
	 */
	closeTreeItem: function( item )
	{
		if ( item.nodeName == 'DIV' ) {
			var cNodes = item.childNodes;
			
			if (cNodes.length == 0 ) {
				return;
			}
			
			for (var i=0; i < cNodes.length; i++) {
				if (cNodes[i].nodeName == 'DIV') {
					if (this.closeChilds && (cNodes[i].state == 'opened-sibling' || cNodes[i].state == 'opened' || cNodes[i].state == 'focused') ) {
						this.closeTreeItem( cNodes[i] );
					}
					
					this.setTreeStyle( cNodes[i], 'closed' );
					cNodes[i].style.display = 'none';
				}
			}
		}
	},
	
	/**
	 * Hides a Tree Item
	 * @param	item	object	DOM Element
	 * @returns	void
	 * @public
	 */
	hideTreeItem: function( item )
	{
		var element = Element.getParentElementByTagName( $(item), "DIV" );
		element.setAttribute( 'display', 'false' );
		this.prepareSubTree( element.parentNode );
		
		if ( element.parentNode.state == "focused" )
			element.style.display = "none";
	},
	
	/**
	 * Displays a hidden Tree Item
	 * @param	item	object	DOM Element
	 * @returns	void
	 * @public
	 */
	displayTreeItem: function( item )
	{
		var element = Element.getParentElementByTagName( $(item), "DIV" );
		element.setAttribute( 'display', 'true' );
		this.prepareSubTree( element.parentNode );
		
		if ( element.parentNode.state == "focused" )
			element.style.display = "block";
	},

	/**
	 * Sets a specific Style to Tree
	 * @param	item	object	DOM Element
	 * @param	state	string	State
	 * @returns	void
	 * @private
	 */
	setTreeStyle: function(item, state)
	{
		if ( item.nodeName == 'DIV' ) {
			var treeitem = Element.getFirstChildByTagName(item, 'A');
		
			styleClass = treeitem.className.split(' ');
			treeitem.className = styleClass[0] + ' ' + styleClass[0] + '-' + state;
		}
	},

	/**
	 * Resets the Tree
	 * @returns	void
	 * @private
	 */
	resetTree: function()
	{
		var currentItem 	= $( this.selected ).parentNode;
		var currentDepth 	= Element.getDepth( currentItem, 'DIV', 'TreeCtrl' );
	
		while ( currentDepth > 1 ) {
			if ( currentItem.nodeName == 'DIV' ) {
				currentDepth = Element.getDepth( currentItem, 'DIV', 'TreeCtrl' );
			}
			
			currentItem = currentItem.parentNode;
		}
		
		if (currentItem) 
			this.closeTreeItem( currentItem );
	},
	
	/**
	 * Searches the Tree for a specific string
	 *
	 * Returns true if the expression is found
	 * If the function is called consequently the next match is highlighted
	 * If the expression is not found false is returned so the caller can react
	 * on this
	 *
	 * @param	{string}	expr	Expression to search for
	 *
	 * @return	boolean		TRUE if something is found, FALSE otherwise
	 *
	 * @access	public
	 */
	searchTree: function( expr )
	{
		var match = '';
		
		if ( expr != this.currentSearchExpr ) {
			this.currentSearchIdx 	= 0;
			this.currentSearchExpr 	= expr;
			this.search 			= [];
			
			for ( var i=0; i < this.items.length; i++ ) {
				var treeitem 	= Element.getFirstChildByTagName( this.items[i], 'A');
				var text 		= Element.getInnerText( treeitem );
				
				if ( text.indexOf( expr ) != -1 ) {
					this.search[this.search.length] = treeitem.id;
				}
			}
		} else {
			this.currentSearchIdx++;
			
			if ( (this.currentSearchIdx + 1)  > this.search.length) {
				this.currentSearchIdx = 0;
			}
		}
	
		if ( this.search.length > 0 ) {
			this.focusItem( this.search[ this.currentSearchIdx ] );
			
			var treeitem = $( this.search[ this.currentSearchIdx ] );
			treeitem.parentNode.parentNode.scrollIntoView( true );
			
			return true;
		} 
			
		return false;
	},
	
	/** 
	 * Handles focused Item in Tree Mode
	 *
	 * Call this method if you want to focus a treeitem
	 * Note that the method does not check if you are doing the right thing
	 * if you call focusItem on a menuitem i don't know what happens
	 * So please be careful
	 *
	 * @param	object	el	DOM Element
	 * @returns	void
	 * @access	public
	 */
	focusItem: function( el )
	{
		var el = $GE(el);
		
		if ( el === this.element ) 
			return;
			
		el = Element.getParentElementByTagName( el, 'A', true );
		el.blur();
		this.setSelected( el.id );
		
		var targetDlg = Element.getAttributeValue( el, 'targetdlg' );

		var evtEl = el;
		
		var id 	= el.id;
		
		el = el.parentNode;

		if (!targetDlg) {
			if ( this.targetDlg )
				targetDlg = this.targetDlg;
			else targetDlg = '';
		} 
		
		evtEl.setAttribute( 'targetdlg', targetDlg );
		
		this.resetTree();
		this.openTreeItem( el );
		
		el.state = 'focused';
		
		sourceElement = el;
		var parentItem = sourceElement.parentNode;
		
		this.prepareSubTree( parentItem );
		this.setTreeStyle( el, el.state );
		
		while( el = el.nextSibling ) {
			if (el.nodeName == 'DIV') {
				el.state = 'focused-sibling';
				this.closeTreeItem( el );
				this.setTreeStyle( el, el.state );
			}
		}
		
		el = sourceElement;
		
		while ( el = el.previousSibling ) {
			if (el.nodeName == 'DIV' ) {
				el.state = 'focused-sibling';
			
				this.closeTreeItem( el );
				this.setTreeStyle( el, el.state );
				lastel = el;
			}
		}
		
		while (parentItem.nodeType != 9 ) {
			var ektype = Element.getAttributeValue( parentItem, 'ektype' );
			
			if ( ektype == 'TreeCtrl') break;
			
			if (parentItem.nodeName == 'DIV') {
				parentItem.state = 'opened';
				
				this.openTreeItem( parentItem );
				this.setTreeStyle( parentItem, parentItem.state );
				
				sibling = parentItem.nextSibling;
				
				while ( sibling != null ) {
					if ( sibling.nodeName == 'DIV' ) {
						sibling.state = 'opened-sibling';
						this.setTreeStyle( sibling, sibling.state );
					}
					
					sibling = sibling.nextSibling;
				}
				
				sibling = parentItem.previousSibling;
				
				while( sibling != null ) {
					if (sibling.nodeName == 'DIV' ) {
						sibling.state = 'opened-sibling';
						this.setTreeStyle( sibling, sibling.state );
					}
					
					sibling = sibling.previousSibling;
				}
				
			}
			
			parentItem = parentItem.parentNode;
		}
	}
});