		$NS( 'IPC.UI', 'UI.CONFIG', 'UI.DOM', 'UI.CONTROL', 'UI.widgets' );
		
/***
	UI configuration
		- CSS classes 
		- JS class names
		- UI control options
***/
		IPC.UI.CONFIG = {
			behaviour : ['Accordian', 'Tab', 'Tree', 'Window'], 
			css : {
				ui : {
					container : ['div', 'ui'], 
					pane : ['div', 'ui-pane'], 
					heading : ['h3', 'ui-h'], 
					body : ['div', 'ui-b'], 
					footer : ['div', 'ui-f'], 
					tabList : ['ul', 'ui-tab'],
					tab : ['li', ''], 
					actions : ['div', 'ui-actions'], 
					button : ['a','ui-btn'], 
					'close' : ['a', 'ui-close'], 
					'max' : ['a', 'ui-max'], 
					'min' : ['a', 'ui-min']
				}, 
				state : {
					active : 'ui-active', 
					disabled : 'ui-disabled', 
					inactive : 'ui-inactive'
				}
			}, 
			options : {
				accordian : {
					offset : false, 
					startWithOpenPane : true
				}, 
				tab : {
					constrainViewPort : false, // { height : '100px', width : '640px' }
					equalViewPortHeight : true, 
					showHeadings : false, 
					tabList : false
				}, 
				tree : {
				}, 
				'window' : {
				}
			}
		};
		
/***
	UI DOM nodes
***/
		IPC.UI.DOM = {
			tab : {
				list : '<' + IPC.UI.CONFIG.css.ui.tabList[0] + ' class="' + IPC.UI.CONFIG.css.ui.tabList[1] + '" />', 
				li : '<' + IPC.UI.CONFIG.css.ui.tab[0] + ' />', 
				a : '<a href="#"></a>'
			}, 
			'window' : {
				actions : '<' + IPC.UI.CONFIG.css.ui.actions[0] + ' class="' + IPC.UI.CONFIG.css.ui.actions[1] + '" />', 
				'close' :  '<' + IPC.UI.CONFIG.css.ui.button[0] + ' class="' + IPC.UI.CONFIG.css.ui.button[1] + ' ' + IPC.UI.CONFIG.css.ui.close[1] + '" href="#"><span>close</span></' + IPC.UI.CONFIG.css.ui.button[0] + '>', 
				'max' :  '<' + IPC.UI.CONFIG.css.ui.button[0] + ' class="' + IPC.UI.CONFIG.css.ui.button[1] + ' ' + IPC.UI.CONFIG.css.ui.max[1] + '" href="#"><span>maximise</span></' + IPC.UI.CONFIG.css.ui.button[0] + '>', 
				'min' :  '<' + IPC.UI.CONFIG.css.ui.button[0] + ' class="' + IPC.UI.CONFIG.css.ui.button[1] + ' ' + IPC.UI.CONFIG.css.ui.min[1] + '" href="#"><span>minimise</span></' + IPC.UI.CONFIG.css.ui.button[0] + '>' 
			}
		};
	
/***
	Abstract UI Control
***/
		IPC.UI.CONTROL.Abstract = function() {};
		IPC.UI.CONTROL.Abstract.prototype = {
			CONTAINER : null, 
			CSS : Object.clone( IPC.UI.CONFIG.css ), 
			events : [], 
			panes : [], 
			_initialize : function( container, options ) {
				this.CONTAINER = $( container );	
				this.options = $H( IPC.UI.CONFIG.options[this.construct.toLowerCase()] ).merge( options || {} );
				this.togglePanesEvent = this.togglePanes.bindAsEventListener( this );
				this._preventDefaultEvent = this._preventDefault.bindAsEventListener( this );
				this._formatUI();
				this._registerPanes();
				this._registerEvents();
				Event.observeMethod( this, 'addPane', this._registerPanes );
				Event.observeMethod( this, 'removePane', this._registerPanes );
			}, 
	/**	PUBLIC METHODS	**/
			addPane : function( pane, insertion ) {
				if ( insertion ) {
					insertion = $H( insertion );
					new IPC.Insertion[insertion.keys()[0]]( pane, insertion.values()[0] );
				}
				else this.CONTAINER.appendChild( pane );
				if ( !pane.hasClassName( this.CSS.state.disabled ) )
					this._registerPaneEvents( pane );
			}, 
			addPanes : function( panes, insertion ) {
				if ( panes instanceof Array )
					panes.each( function( pane ) { 
						this.addPane( pane, insertion );
					}.bind( this ) );
				else this.addPane( panes, insertion );
			}, 
			destroy : function( annihilate ) {
				this.disablePanes();
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.pane[1] ).each( Element.show );
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.heading[1] ).each( Element.show );
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.body[1] ).each( Element.show );
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.footer[1] ).each( Element.show );
				Event.purgeMethodListeners( this );
				if ( annihilate )
					this.CONTAINER.remove();
			}, 
			disablePane : function( pane ) {
				pane.removeClassName( this.CSS.state.active );
				pane.addClassName( this.CSS.state.disabled );
				this._unregisterPaneEvents( pane );
			}, 
			disablePanes : function( panes ) {
				if ( panes )
					panes.each( this.disablePane.bind( this ) );
				else
					this.panes.each( this.disablePane.bind( this ) );
			}, 
			enablePane : function( pane, makeActive ) {
				if ( pane.hasClassName( this.CSS.state.disabled ) ) {
					pane.removeClassName( this.CSS.state.disabled );
					this._registerPaneEvents( pane );
					if ( makeActive === true ) 
						this._togglePanes( pane );
				}
			}, 
			enablePanes : function( panes ) {
				if ( panes )
					panes.each( this.enablePane.bind( this ) );
				else
					this.panes.each( this.enablePane.bind( this ) );
				this._togglePanes( this.currentPane );
			}, 
			removePane : function( pane ) {
				this._unregisterPaneEvents( pane );
				pane.remove();
			}, 
			handleAjaxRequest : function() {
				var response, insertion;
				$A( arguments ).each( function( arg ) {
					try {
						if ( arg.responseText )
							response = arg;
						else if ( arg instanceof Object && !arg.responseText )
							insertion = arg;
					}
					catch ( e ) {}
				} );
				var ct  = response.getResponseHeader( 'Content-Type' );
				if ( /html|xml/i.test( ct ) )
					this._handleHTML( response, insertion );
				else if ( /script|json/i.test( ct ) )
					this._handleJSON( response, insertion );
			}, 
			togglePanes : function( evt )
			{
				this._togglePanes( Event.element( evt ).up( '*.' + this.CSS.ui.pane[1] ) );
				Event.stop( evt );
			}, 
	/**	PRIVATE METHODS	**/
			_buildDOM : function() {}, 
			_buildEvents : function() {}, 
			_formatUI : function() {}, 
			_handleHTML : function( response, insertion ) {
				this.addPanes( $B.eval( response.responseText ).getElementsBySelector( '*.' + this.CSS.ui.pane[1] ), insertion );
			}, 
			_handleJSON : function( response, insertion ) {
				var obj = eval( '(' + response.responseText + ')' );
				var panes = [];
				obj.each( function( o ) {
					var tmp = {}; tmp[this.CSS.ui.pane[0]] = {
						_a : { 'class' : this.CSS.ui.pane[1] }
					};
					var pane = $B.node( tmp );
					if ( o.state )
						pane.addClassName( o.state );
					if ( o.heading ) {
						tmp = {}; tmp[this.CSS.ui.heading[0]] = {
							_a : { 'class' : this.CSS.ui.heading[1] }, 
							_c : o.heading
						};
						pane.appendChild( $B.node( tmp ) );
					}
					if ( o.body ) {
						tmp = {}; tmp[this.CSS.ui.body[0]] = {
							_a : { 'class' : this.CSS.ui.body[1] }, 
							_c : o.body
						};
						pane.appendChild( $B.node( tmp ) );
					}
					if ( o.footer ) {
						tmp = {}; tmp[this.CSS.ui.footer[0]] = {
							_a : { 'class' : this.CSS.ui.footer[1] }, 
							_c : o.footer
						};
						pane.appendChild( $B.node( tmp ) );
					}
					panes.push( pane );
				}.bind( this ) );
				this.addPanes( panes, insertion );
			}, 
			_playPaneEffect : function() {}, 
			_preventDefault : function( evt ) {
				Event.stop( evt );
			}, 
			_registerEvents : function() {
				this.panes.each( function( pane ) {
					if ( pane.hasClassName( this.CSS.state.disabled ) )
						this.disablePane( pane );
					else 
						this._registerPaneEvents( pane );
				}.bind( this ) );
			}, 
			_registerPaneEvents : function( pane ) {}, 
			_registerPanes : function() {
				this.panes = this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.pane[1] );
				this.currentPane = this.CONTAINER.down( this.CSS.ui.pane[0] + '*.' + this.CSS.state.active ) || this.panes[0];
				this.currentPane.addClassName( this.CSS.state.active );
		//	for msie
				this.panes.each( function( pane ) {
					Element.extend( pane );
					pane.getElementsBySelector( '*[class^="' + this.CSS.ui.container[1] + '"]' ).each( Element.extend );
				}.bind( this ) );
				
				return this.currentPane;
			}, 
			_togglePanes : function( currentPane ) {
				this.currentPane = currentPane;
				var previousPane;
				this.panes.each( function( pane ) {
					if ( pane.hasClassName( this.CSS.state.active ) ) {
						previousPane = pane;
						previousPane.removeClassName( this.CSS.state.active );
					}
				}.bind( this ) );
				if ( this.currentPane != previousPane )
					this._playPaneEffect( previousPane );
				this.currentPane.addClassName( this.CSS.state.active );
			}, 
			_unregisterPaneEvents : function( pane ) {},  
			_unregisterEvents : function() {
				this.panes.each( this._unregisterPaneEvents.bind( this ) );
			} 
		};
		
//	create classes for each type of UI control behaviour
		IPC.UI.CONFIG.behaviour.each( function( control ) {
			IPC.UI.CONTROL[control] = Class.create();
			Object.extend( IPC.UI.CONTROL[control].prototype, new IPC.UI.CONTROL.Abstract() );
			IPC.UI.CONTROL[control].prototype.construct = control;
		} );
		
/***
	Accordian UI Control
***/
		Object.extend( IPC.UI.CONTROL.Accordian.prototype, {
			initialize : function( container, options ) {
				Event.observeMethod( this, '_registerPanes', this._playInitial );
				Event.observeMethod( this, 'addPane', this._checkPaneProperties, Event.BEFORE );
				Event.observeMethod( this, 'disablePane', this._hidePaneBody );
				this.options = $H( {
					offset : false,
					startWithOpenPane : false 
				} ).merge( options || {} );
				this._initialize( container, this.options );
			}, 
	/**	PRIVATE METHODS	**/
			_checkPaneProperties : function( pane )
			{
				if ( !pane.hasClassName( this.CSS.state.active ) )
					this._hidePaneBody( pane );
				else {
					this.CONTAINER.getElementsBySelector( '*.' + this.CSS.state.active ).each( function( p ) {
						if ( p == this.currentPane ) {
							p.removeClassName( this.CSS.state.active );
							this._hidePaneBody( p );
						}
					}.bind( this ) );
				}
			}, 
			_formatUI : function() {
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.body[1] ).each( function( paneBody ) {
					if ( !paneBody.up( '*.' + this.CSS.ui.pane[1] ).hasClassName( this.CSS.state.active ) )
						paneBody.hide();
				}.bind( this ) );
			}, 
			_hidePaneBody : function( pane ) {
				pane.down( '*.' + this.CSS.ui.body[1] ).hide();
			}, 
			_playInitial : function( pane ) {
				if ( this.options.startWithOpenPane )
					this._playPaneEffect();
				else pane.removeClassName( this.CSS.state.active );
			}, 
			_playPaneEffect : function( previousPane ) {
				if ( previousPane ) {
					new Effect.BlindUp( previousPane.down( '*.' + this.CSS.ui.body[1] ), { 
						afterFinish : function() {
							new Effect.BlindDown( this.currentPane.down( '*.' + this.CSS.ui.body[1] ), { 
								afterFinish : function() {
									if ( this.options.offset !== false )
										new Effect.ScrollTo( this.currentPane, { 
											duration : .3, 
											offset : -24
										} );
								}.bind( this ), 
								duration : .5 
							} );
						}.bind( this ), 
						duration : .5 
					} ); 
				}
				else
					new Effect.BlindDown( this.currentPane.down( '*.' + this.CSS.ui.body[1] ) );	
			}, 
			_registerPaneEvents : function( pane ) {
				pane.down( '*.' + this.CSS.ui.heading[1] ).observe( 'click', this.togglePanesEvent );
			}, 
			_unregisterPaneEvents : function( pane ) {
				pane.down( '*.' + this.CSS.ui.heading[1] ).stopObserving( 'click', this.togglePanesEvent );
			}
		} );
		
/***
	Tab UI Control
***/
		Object.extend( IPC.UI.CONTROL.Tab.prototype, {
			tabs : [], 
			initialize : function( container, options ) {
				this.DOM = IPC.CreateDOM( IPC.UI.DOM.tab );
				this.options = $H( {
					constrainViewPort : false, // { height : '100px', width : '640px' }
					equalViewPortHeight : true, 
					showHeadings : false, 
					tabList : false
				} ).merge( options || {} );
				Event.observeMethod( this, 'destroy', this._destroy );
				Event.observeMethod( this, '_registerPanes', this._registerTabs );
				Event.observeMethod( this, 'disablePane', this._disableTab );
				Event.observeMethod( this, 'enablePane', this._enableTab, Event.BEFORE );
				Event.observeMethod( this, 'addPane', this._updateUI );
				Event.observeMethod( this, 'removePane', this._updateUI );
				this._initialize( container, options );
				this.currentTab = this._findMatchingTab( this.currentPane );
				this.currentPane.show();
			}, 
	/**	PUBLIC METHODS	**/
			addPane : function( pane, insertion ) {
				if ( insertion ) {
					insertion = $H( insertion );
					new IPC.Insertion[insertion.keys()[0]]( pane, insertion.values()[0] );
				}
				else this.CONTAINER.appendChild( pane );
			}, 
			togglePanes : function( evt )
			{
				var tab = Event.findElement( evt, 'li' ), 
					  pane = this._findMatchingPane( tab );
				this._toggleTabs( tab );
				this._togglePanes( pane );
				Event.stop( evt );
			}, 
	/**	PRIVATE METHODS	**/
			_addTab : function( tab ) {
				this.DOM.list.appendChild( tab );
			}, 
			_createTab : function( paneHeading ) {
				var tab = this.DOM.li.cloneNode( true );
	//	adding the span tag for easier styling - need to put this back in IPC.UI.DOM but for now if we do this it will throw errors
				var a = this.DOM.a.cloneNode( true ).update( '<span>' + paneHeading.firstChild.data + '</span>' );
				tab.appendChild( a );
				if ( paneHeading.parentNode.hasClassName( this.CSS.state.active ) )
					tab.addClassName( this.CSS.state.active );
				else if ( paneHeading.parentNode.hasClassName( this.CSS.state.disabled ) )
					tab.addClassName( this.CSS.state.disabled );
				return tab;
			}, 
			_destroy : function() { 
				this.DOM.list.remove();
			}, 
			_disableTab : function( pane ) {
				var tab = this._findMatchingTab( pane );
				tab.removeClassName( this.CSS.state.active );
				tab.addClassName( this.CSS.state.disabled );
				tab.down( 'a' ).observe( 'click', this._preventDefaultEvent );
			}, 
			_enableTab : function( pane, makeActive ) {
				var tab = this._findMatchingTab( pane );
				tab.down( 'a' ).stopObserving( 'click', this._preventDefaultEvent );
				tab.removeClassName( this.CSS.state.disabled );
				if ( makeActive === true )
					this._toggleTabs( tab );
				else this._toggleTabs( this.currentTab );
			}, 
			_findMatchingPane : function( tab ) {
				return this.panes[this.tabs.indexOf( tab )];
			}, 
			_findMatchingTab : function( pane ) {
				return this.tabs[this.panes.indexOf( pane )];
			}, 
			_formatUI : function() {
				var d, panes = this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.pane[1] );
				if ( this.options.constrainViewPort ) 
					d = this.options.constrainViewPort;
				else if ( this.options.equalViewPortHeight ) 
					d = this._getTallestViewPort( panes );
					
				panes.each( function( pane ) {
					var h = pane.down( '*.' + this.CSS.ui.heading[1] );
					if ( !this.options.tabList ) 
						this._addTab( this._createTab( h ) );
					if ( !this.options.showHeadings ) h.hide();
					if ( d ) this._setViewPort( pane, d );
				}.bind( this ) );
				
				if ( !this.options.tabList ) {
					if ( this.options.insertion )
						new IPC.Insertion[this.options.insertion.keys()[0]]( this.DOM.list, this.options.insertion.values()[0] );
					else
						new IPC.Insertion.Before( this.DOM.list, this.CONTAINER.firstChild );
				}
				else 
					this.DOM.list = this.options.tabList;
				
				panes.invoke( 'hide' );
			}, 
			_getTallestViewPort : function( panes ) {
				return panes.max( function( pane ) {
					return pane.down( '*.' + this.CSS.ui.body[1] ).getHeight();
				}.bind( this ) );
			}, 
			_playPaneEffect : function( previousPane ) {
				if ( previousPane ) previousPane.hide();
				this.currentPane.show();
			}, 
			_registerPaneEvents : function( pane ) {
				this._findMatchingTab( pane ).down( 'a' ).observe( 'click', this.togglePanesEvent );
			}, 
			_registerTabs : function() {
				this.tabs = this.DOM.list.getElementsBySelector( this.CSS.ui.tab[0] );
				this.currentTab = this.DOM.list.down( '*.' + this.CSS.state.active ) || this.tabs[0];
				this.currentTab.addClassName( this.CSS.state.active );
			}, 
			_removeTabs : function() {
				this.tabs.each( Element.remove );
				this.tabs = [];
			}, 
			_setViewPort : function( pane, d ) {
				var padding = $USER.browser == 'Explorer' ? 10 : 1;
				pane.setStyle( { 'clear' : 'both', 'overflow' : 'auto' } );
				if ( d instanceof Object ) {
					if ( d.height ) {
						pane.setStyle( { 'height' : ( parseInt( d.height ) + padding ) + 'px' } );
					}
					if ( this.options.constrainViewPort && d.width ) 
						pane.setStyle( { 'width' : ( parseInt( d.width ) ) + 'px' } );
				}
				else pane.setStyle( { 'height' : ( d + padding ) + 'px' } );
			}, 
			_toggleTabs : function( tab ) {
				this.currentTab = tab;
				this.tabs.each( function( t ) {
					t.removeClassName( this.CSS.state.active );
				}.bind( this ) );
				this.currentTab.addClassName( this.CSS.state.active );
			}, 
			_updateUI : function( pane ) {
				this._unregisterEvents();
				this._removeTabs();
				this._formatUI();
				this._registerPanes();
				if ( this.currentPane != pane )
					this.currentPane.show();
				else
					this._togglePanes( this.panes[0] );
				this._toggleTabs( this._findMatchingTab( this.currentPane ) );
				this._setViewPort( pane );
				this._registerEvents();
			}, 
			_unregisterPaneEvents : function( pane ) {
				this._findMatchingTab( pane ).down( 'a' ).stopObserving( 'click', this.togglePanesEvent );
			}
		} );
		
/***
	Window UI Control
***/
		Object.extend( IPC.UI.CONTROL.Window.prototype, {
			draggables : [], 
			initialize : function( container, options ) {
				this.DOM = IPC.CreateDOM( IPC.UI.DOM.window );
				Event.observeMethod( this, '_togglePanes', this._adjustZindex );
				this.closeWindow = this.close.bindAsEventListener( this );
				this.maximiseWindow = this.max.bindAsEventListener( this );
				this.minimiseWindow = this.min.bindAsEventListener( this );
				this._initialize( container, options );
			}, 
	/**	PUBLIC METHODS	**/
			close : function( evt ) {
				console.log( 'close window' );
				Event.stop( evt );
			}, 
			max : function( evt ) {
				console.log( 'maximise window' );
				Event.stop( evt );
			}, 
			min : function( evt ) {
				console.log( 'minimise window' );
				Event.stop( evt );
			}, 
	/**	PRIVATE METHODS	**/
			_adjustZindex : function( delta, evt ) { 
				console.log( 'adjust z-index' );
				console.log( delta );
				console.log( evt );
			}, 
			_createButtons : function( pane ) {
				var titleBar = pane.down( '*.' + this.CSS.ui.heading[1] );
				var actions = this.DOM.actions.cloneNode( true );
				titleBar.appendChild( actions );
				
				actions.appendChild( this.DOM.close.cloneNode( true ) ).observe( 'click', this.closeWindow );
				actions.appendChild( this.DOM.max.cloneNode( true ) ).observe( 'click', this.maximiseWindow );
				actions.appendChild( this.DOM.min.cloneNode( true ) ).observe( 'click', this.minimiseWindow );
			}, 
			_formatUI : function() {
				this.CONTAINER.getElementsBySelector( '*.' + this.CSS.ui.pane[1] ).each( function( pane ) {
					Position.absolutize( pane );
					if ( !pane.hasClassName( this.CSS.state.disabled ) )
						this.draggables.push( new Draggable( pane , { 
							handle : pane.down( '*.' + this.CSS.ui.heading[1] ), 
							onEnd : this._adjustZindex.bind( this, 1 ), 
							onStart : this._adjustZindex.bind( this, 0 )
						} ) );
					this._createButtons( pane );
				}.bind( this ) );
			}
		} );
		
		IPC.UI.LoadWidgets = Class.create();
		IPC.UI.LoadWidgets.prototype = {
			initialize : function( widget ) {
				$$( '*.ui' ).each( this.loadWidget );
			}, 
			loadWidget : function( widget ) {
				var widgetType = widget.className.match( /ui-(\w+)/ )[1];
				var options = null;
				widgetType = widgetType.charAt( 0 ).toUpperCase() + widgetType.substring( 1 );
				
				if ( IPC.UI.CONFIG.behaviour.find( function( b ) { return b == widgetType } ) ) {
					if ( !widget.id ) widget.id = $UID.create();
					IPC.UI.widgets[widget.id] = widget;
					if ( widget.hasAttribute( 'ui-options' ) ) {
						try { options = ['{', widget.getAttribute( 'ui-options' ), '}'].join( '' ).evalJSON(); }
						catch ( e ) { }
					}
					
					IPC.UI.widgets[widget.id].___controller = new IPC.UI.CONTROL[widgetType]( widget, options );
				}
			}
		};
		
		IPC.UI.Manager = Class.create();
		IPC.UI.Manager.prototype = {
			initialize : function() {
				new IPC.UI.LoadWidgets();
			}, 
			destroy : function() {
				$H( IPC.UI.widgets ).each( function( node ) {
					node.value.___controller.destroy();
				} );
			}
		};
		
		Event.observe( window, 'load', function() {
			window.uiManager = new IPC.UI.Manager()
		} );
		Event.observe( window, 'unload', function() {
			window.uiManager.destroy();
		} );
