/**
 * jQuery UI : panel
 *
 * @author  Marc Mignonsin
 * @version 2010-12-07
 *
 */

(function( $ ) {
	$.widget("ui.panel", {
		// default options
		options: {			
			srcElem : null,
			ID : null,
			parentID : null,
			title : '&#160;',
			index : 0,
			position : 0,
			width : 1,
			optimalWidth : 1,
			content : null,
			onRefresh : null,
			afterLayout : null,
			afterContent : null
		},
	
		// internal : widget creation
		_create: function() {	
			//window.log('ui.panel._create', this.options.ID, this.options);
			
			// --- internal configuration ---
			
			// CSS classes involved
			this.historySelector = '#history';
			this.panelClass = 'panel';
			this.detailsPanelClass = 'panel-details';
			this.newPanelClass = 'panel-new';
			this.panelLoaderContainerClass = 'panel-loader';
			this.panelLoaderClass = 'flower';
			this.titleBarClass = 'panel-title-bar';
			this.titleClass = 'title';
			this.headerHeightProportion = 0.33;
			this.headerClass = 'panel-header';
			this.headerLoaderContainerClass = 'panel-header-loader';
			this.headerLoaderClass = 'flower';
			this.bodyClass = 'panel-body';
			this.bodyLoaderContainerClass = 'panel-body-loader';
			this.bodyLoaderClass = 'flower';
			this.navItemsClass = 'items';
			
			this.titleBarOptionsClass = 'panel-title-bar-options';
			this.leftToolbarClass = 'left-toolbar';
			this.rightToolbarClass = 'right-toolbar';
			this.toolbarToolClass = 'tool';
			this.toolbarToolOffClass = 'tool-off';
			this.toolbarNavPrevClass = 'nav-prev';
			this.toolbarNavNextClass = 'nav-next';
			
			this.toolbarMaximizeClass = 'maximize';
			this.toolbarNormalSizeClass = 'normalsize';
			
			/*this.toolbarReloadClass = 'reload';
			this.toolbarNewTabClass = 'new-tab';
			this.toolbarQueryClass = 'query';
			this.toolbarResponseClass = 'response';
			this.toolbarParmsClass = 'params';
			this.toolbarRaweditClass = 'rawedit';*/
			
			// animation parms
			this.resizeSpeed = 500; // ms
			this.resizeEasing = 'easeOutExpo';
			this.scrollSpeed = 500; // ms
			this.scrollEasing = 'easeOutExpo';
		
			// --- state variables ---
			
			// DOM elements : panel container & panel element
			this.uiContainer = this.element.parent();
			this.uiPanel = this.element.attr('ID', this.options.ID);
			this.padding = parseInt(this.uiPanel.css('paddingLeft')) + parseInt(this.uiPanel.css('paddingRight'));

			this.state = {
				position : this.options.position,
				width : this.options.width,
				optimalWidth : this.options.optimalWidth,
				optimalWidthInPx : this.options.optimalWidth*this.uiContainer.panelslider('getColumnWidth'),
				animationInProgress : false,
				newContentAvailable : false
			};
			
			// help solve animation issues
			this.futureState = {
				position : this.state.position,
				width : this.state.width
			};
			
			// --- init ---

			// place+resize panel via CSS
			this.setPosition(this.state.position);
			this.setWidth(this.state.width);	
			
			// update parent's child ID
			this.childID = null;
			if (this.options.parentID != null)
			{
				this.uiContainer.find('#'+this.options.parentID+'.'+this.panelClass).panel('setChildID', this.options.ID);
			}

			// JS objects used by this panel
			this.externalResources = [];
			
			// AJAX
			this.contentRequest = new AjaxRequest(this.options.ID, this.uiContainer, this.options.content.url, 'POST', 'html');			
			// display errors in dialogs
			this.contentRequest.errorDialog(true, false);
			this.contentRequest.internalErrorDialog(true, false);			
			this.contentRequest.onSuccess(delegate(this, function(response) {
				response.ui = this;
				if (response == '') // aborting an AJAX request leads to this place with an empty content
				{
					window.warn('Empty content received! (AJAX request aborted?)', this.options.ID, this.options.content.ajaxData.panelURI);
					this.displayPanelLoader(false);
					//this.contentRequest.notifyApplicationError('Empty content received!<br /><br />('+this.options.content.ajaxData.panelURI+')');
					return;
				}
				this.content(response);
			}));			
			this.contentRequest.onError(delegate(this, function(response) {
				response.ui = this;
				this.displayPanelLoader(false);
			}));
			
			// content
			this.currentContent = '';
			this.uiActiveItem = null; // active (selected) item
			
			// default panel layout : title, body and panel loader
			this._createTitleBarElem();
			this._createBodyElem();
			this._createLoaderElems('panel');
						
			// set appropriate height
			this._doLayout();
			
			// display panel loader
			this.displayPanelLoader(true);

			// default events
			this._bindPanelEvents();
		},
		
		// internal : options
		_setOption: function(key, value) {
			//window.log('uiPanel._setOption', arguments, this.options.ID);
			if (key === 'disabled')
			{				
				// override default behaviour : no jQuery "disabled" class on the element
				this._enableDisable(value); 
			}
			
			if (key == 'content')
			{
				// change AJAX script URL
				this.contentRequest.setScript(value.url);
			}
			
			if (key == 'title')
			{
				this.uiTitle.html(value);
				$(this.historySelector).panelhistory('updateLabel', this.options.ID, value);
			}
		
			if (key == 'width')
			{
				this.setWidth(value);
			}
			
			if (key == 'optimalWidth')
			{
				this.setOptimalWidth(value);
			}
			
			this.options[key] = value;
		},
		
		// internal : events 
		_bindPanelEvents: function() {
			//window.log('uiPanel._bindPanelEvents', this.options.ID);

			// layout adjustments (heights)
			this.uiPanel.bind('panel-layout', delegate(this, function(eventObject, which){
				//window.log('layout event received', this.options.ID);
				this._doLayout(which);
			}));
			
			// content loading (navigation item click, ...)
			this.uiPanel.bind('panel-load', delegate(this, function(eventObject){
				//window.log('load event received', this.options.ID);
				this.load();
			}));
			
			// content reloading (toolbar button click)
			this.uiPanel.bind('panel-reload', delegate(this, function(eventObject, addParms){
				//window.log('reload event received', this.options.ID);
				this.load(addParms);
			}));
			
			// panel scrolling + width resizing
			this.uiPanel.bind('panel-scrollresize', delegate(this, function(eventObject, posInc, widthInc, callback){
				//window.log('scrollresize event received', this.options.ID);
				this.scrollResize(posInc, widthInc, callback);
			}));
			
			// strategy : new panel placement/go to next panel placement
			this.uiPanel.bind('panel-nextplacement', delegate(this, function(eventObject, posInc, callback){
				//window.log('next placement event received', this.options.ID);
				this._nextPlacement(posInc, callback);
			}));
			
			// strategy : go to prev panel placement
			this.uiPanel.bind('panel-prevplacement', delegate(this, function(eventObject, posInc, callback){
				//window.log('previous placement event received', this.options.ID);
				this._prevPlacement(posInc, callback);
			}));
			
			// panel content refreshing after an update, ...)
			this.uiPanel.bind('panel-refresh', delegate(this, function(eventObject, addParms){
				//window.log('refresh event received', this.options.ID);
				this.refresh(addParms);
			}));
			
			// panel destruction
			this.uiPanel.bind('panel-destroy', delegate(this, function(eventObject){
				//window.log('destroy event received', this.options.ID);
				this.destroy();
			}));
			
			// display body loader
			this.uiPanel.bind('panel-displayloader', delegate(this, function(eventObject, display, where){
				//window.log('display loader event received', this.options.ID, arguments);
				switch (where)
				{
					case 'header':
						this.displayHeaderLoader(display);
						break;
						
					case 'body':
						this.displayBodyLoader(display);
						break;
						
					case 'panel':
					default:
						this.displayPanelLoader(display);
						break;
				}
			}));
			
			// update block content
			this.uiPanel.bind('panel-blockcontent', delegate(this, function(eventObject, blockSelector, newContent){
				//window.log('block content event received', this.options.ID, arguments);
				this._setBodyContent(blockSelector, newContent);
			}));
			
			/* *** */
			
			// header + body loader layout : centering
			this.uiHeader.scroll(delegate(this, function(eventObject){
				//window.log('scroll event received on header', this.options.ID);
				this._doHeaderLoaderLayout();
			}));
			
			this.uiHeader.jScrollTouch();
			
			this.uiBody.scroll(delegate(this, function(eventObject){
				//window.log('scroll event received on body', this.options.ID);
				this._doBodyLoaderLayout();
			}));
			
			this.uiBody.jScrollTouch();
		},
	
		// internal : select all visible panels preceding the current panel
		_getPrevVisiblePanels: function() {
			//window.log(this.uiPanel.getPrevVisiblePanels('div.'+this.panelClass+':visible'));
			return $(this.uiPanel).prevAll('div.'+this.panelClass+':visible');
		},
		
		// internal : select all visible panels after the current panel
		_getNextVisiblePanels: function() {
			//window.log(this.uiPanel.getNextVisiblePanels('div.'+this.panelClass+':visible'));
			return $(this.uiPanel).nextAll('div.'+this.panelClass+':visible');
		},
		
		// internal : title bar creation
		_createTitleBarElem: function() {
			//window.log('uiPanel._createTitleBarElem');	
			this.uiTitleBar = $('<div></div>').addClass(this.titleBarClass).appendTo(this.uiPanel);
			this.uiTitle = $('<div></div>').addClass(this.titleClass).html(this.options.title).appendTo(this.uiTitleBar);
		},
		
		// internal : header creation
		_createHeaderElem: function() {
			//window.log('uiPanel._createHeaderElem');	
			this.uiHeader = $('<div></div>').addClass(this.headerClass).prependTo(this.uiPanel);
		},
		
		// internal : body creation
		_createBodyElem: function() {
			//window.log('uiPanel._createBodyElem');	
			this.uiBody = $('<div></div>').addClass(this.bodyClass).appendTo(this.uiPanel);
		},
		
		// internal : body loader creation
		_createLoaderElems: function(where) {
			//window.log('uiPanel._createLoaderElems', arguments);

			switch (where)
			{
				case 'header':
					// container with opaque background
					this.uiHeaderLoaderContainer = $('<div></div>').addClass(this.headerLoaderContainerClass).prependTo(this.uiHeader);
					// loader : flower animation
					this.uiHeaderLoader = $('<div></div>').addClass(this.headerLoaderClass).appendTo(this.uiHeaderLoaderContainer);			
					break;
				
				case 'body':
					// container with opaque background
					this.uiBodyLoaderContainer = $('<div></div>').addClass(this.bodyLoaderContainerClass).prependTo(this.uiBody);
					// loader : flower animation
					this.uiBodyLoader = $('<div></div>').addClass(this.bodyLoaderClass).appendTo(this.uiBodyLoaderContainer);	
					break;
					
				case 'panel':
				default:
					// container with opaque background
					this.uiLoaderContainer = $('<div></div>').addClass(this.panelLoaderContainerClass).prependTo(this.uiPanel);
					// loader : flower animation
					this.uiLoader = $('<div></div>').addClass(this.panelLoaderClass).appendTo(this.uiLoaderContainer);			
					break;
			}
		},
		
		// internal : finders
		_findLoaderElems: function(where) {
			//window.log('uiPanel._findLoaderElems', arguments);			
			switch (where)
			{
				case 'header':
					this.uiHeaderLoaderContainer = this.uiPanel.find('div.'+this.headerLoaderContainerClass);
					this.uiHeaderLoader = this.uiHeaderLoaderContainer.find('div.'+this.headerLoaderClass);
					break;
					
				case 'body':
					this.uiBodyLoaderContainer = this.uiPanel.find('div.'+this.bodyLoaderContainerClass);
					this.uiBodyLoader = this.uiBodyLoaderContainer.find('div.'+this.bodyLoaderClass);
					break;
					
				case 'panel':
				default:
					this.uiLoaderContainer = this.uiPanel.find('div.'+this.panelLoaderContainerClass);
					this.uiLoader = this.uiLoaderContainer.find('div.'+this.panelLoaderClass);
					break;
			}
		},
		
		_findTitleBarElems: function() {
			this.uiTitleBar = this.uiPanel.find('div.'+this.titleBarClass);
			this.uiTitle = this.uiTitleBar.find('div.'+this.titleClass);
		},
				
		_findHeaderElem: function() {
			this.uiHeader = this.uiPanel.find('div.'+this.headerClass);
		},
		
		_findBodyElem: function() {
			this.uiBody = this.uiPanel.find('div.'+this.bodyClass);
		},
		
		_findStructureElems: function() {
			//window.log('uiPanel._findStructureElems');
			this._findLoaderElems('panel');
			this._findTitleBarElems();
			this._findHeaderElem();
			this._findLoaderElems('header');
			this._findBodyElem();
			this._findLoaderElems('body');
		},
				
		// internal : ...
		_bindNavItemMouseEvents: function(navItem, event) {
			// mouseover
			var navItemsClass = this.navItemsClass;
			
			this.uiPanel.mouseover(function(event) {
				var navItem = target = $(event.target);
				if (!target.hasClass(navItemsClass))
				{
					navItem = target.closest('.'+navItemsClass);
					if (navItem.length == 0) return true;
				}
				
	            if (navItem.attr('onclick')) // hover only for the items that have the onclick handlers
	            {
	                navItem.addClass('hover');
	                navItem.children().addClass('hover');
	            }
	            
	            // actions events
	            if (!target.parent().hasClass('actions-block')) return true;
	            if (target.hasClass('confirm')) return true;
	            
	            var actionsLabel = navItem.find('div.actions-label');
	            
	            if (target.hasClass('delete-action') || 
            		target.hasClass('link-action') || 
            		target.hasClass('unlink-action')
        		)
	            {
					if (actionsLabel.html() != target.attr('hover-title')) actionsLabel.attr('default-value', actionsLabel.html());
					actionsLabel.html(target.attr('hover-title'));
	            }
			});
			
			// mouseout
			this.uiPanel.mouseout(function(event) {
				//$(event.target).log();
				var navItem = target = $(event.target);
				if (!target.hasClass(navItemsClass))
				{
					navItem = target.closest('.'+navItemsClass);
					if (navItem.length == 0) return true;
				}
				
				if (navItem.attr('onclick')) // hover only for the items that have the onclick handlers
	            {
	                navItem.removeClass('hover');
	                navItem.children().removeClass('hover');
	            }
	            
	            // actions events
	            if (!target.parent().hasClass('actions-block')) return true;
	            if (target.hasClass('confirm')) return true;
	            
	            var actionsLabel = navItem.find('div.actions-label');
	            
	            if (target.hasClass('delete-action') || 
	            		target.hasClass('link-action') || 
	            		target.hasClass('unlink-action')
	        		)
	            {	            	
					actionsLabel.html(actionsLabel.attr('default-value'));
	            }
			});
		},
		
		// internal : set the HTML content in the panel element
		_setContent: function() {
			//window.log('uiPanel._setContent', this.options.ID);
			//window.log(this.currentContent);
			// lock
			if ((this.state.newContentAvailable == false) || (this.state.animationInProgress == true))
			{
				//window.warn('Set content discarded!', this.options.ID);
				return false;
			}
			
			// cleanup all handlers + content cleanup 
			this.uiPanel.unbind();
			this.uiPanel.empty();

			// new content
			this.uiPanel.html(this.currentContent);			
			this.state.newContentAvailable = false;
			//window.log('content set');
			
			// restore panel state
			this._enableDisable(this.options.disabled);
			
			// layout
			this._doLayout();

			// restore panel events
			this._bindPanelEvents();			
			
			// navigation items: mouse events
			this._bindNavItemMouseEvents();
			
			if ($.isFunction(this.options.afterContent))
			{
				this.options.afterContent(this.element);
			}
			
			return true;
		},
		
		// internal : set body content (used in quicksearch panels, etc.)
		_setBodyContent: function(blockSelector, newContent) {
			//window.log('uiPanel._setBodyContent', this.options.ID);
			//window.log(newContent);
			
			// cleanup
			var targetBlock = this.uiBody.find(blockSelector);
			targetBlock.unbind();
			targetBlock.empty();

			targetBlock.html(newContent);
			//window.log('content set');
			
			// layout
			this._doLayout();
			
			// navigation items: mouse events
			this._bindNavItemMouseEvents();
		},
		
		// internal : layout
		_doLayout: function(which) {
			//window.log('uiPanel._doLayout', arguments);
			//this.log();
			
			// discard while animation or if this panel is invisible			
			if (this.state.animationInProgress == true)  
			{
				//window.warn('Panel layout discarded (animation in progress)', this.options.ID);
				return;
			}
			if (this.uiPanel.is(':hidden'))
			{
				//window.warn('Panel layout discarded (hidden)', this.options.ID);
				return;
			}
			
			// find all elements of structure
			this._findStructureElems();
			
			// place+resize panel via CSS
			this.setPosition(this.state.position);
			this.setWidth(this.state.width);
			
			// display/hide toolbar buttons according to this new position
			this._doToolbarLayout();
									
			// adjust header/body heights
			var sliderHeight = this.uiContainer.panelslider('getHeight');
		
			switch(which)
			{
				case 'header':
					this._doHeaderLayout(sliderHeight);
					break;
					
				case 'body':
					this._doBodyLayout(sliderHeight);
					break;
					
				default:
					this._doHeaderLayout(sliderHeight);
					this._doBodyLayout(sliderHeight);
					break;
			}
	    	
	    	/*window.log('slider height: '+sliderHeight);
	    	window.log('header max height: '+maxHeaderHeight);
	    	window.log('title bar height: '+this.uiTitleBar.outerHeight(true));
	    	window.log('header height: '+this.uiHeader.outerHeight(true));
	    	window.log('=> final body height: '+bodyHeight);*/
			
			if ($.isFunction(this.options.afterLayout))
			{
				return this.options.afterLayout(this.element);
			}
		},
		
		_doHeaderLayout: function(sliderHeight) {
			//window.log('uiPanel._doHeaderLayout', 'ID='+this.options.ID);
	    	// adjust header max height
	        var maxHeaderHeight = Math.ceil(sliderHeight * this.headerHeightProportion);
	        this.uiHeader.css('max-height', maxHeaderHeight+'px');	
	        
	        // form inputs layout
	        this.uiHeader.find('div.input-container').each(function(){
	        	var inputWidth = parseInt($(this).css('width'));	        	
	        	$(this).find(':not(.input)').each(function(){
	        		inputWidth -=  parseInt($(this).css('width'));
	        	});
	        	inputWidth -= 8; // safety for Safari glow fx on inputs
	        	$(this).find('input.input').css('width', inputWidth+'px');
	        });
		},
		
		_doBodyLayout: function(sliderHeight) {
			//window.log('uiPanel._doBodyLayout', 'ID='+this.options.ID);
			// adjust body height
	        var bodyHeight = sliderHeight;
	    	bodyHeight -= this.uiTitleBar.outerHeight(true); // substract title bar height	    	
	    	bodyHeight -= this.uiHeader.outerHeight(true); // substract header height
	    	this.uiBody.css('height', bodyHeight+'px');
	    	
	    	// adapt new/details navigation to narrow screens
	    	if (this.uiPanel.hasClass(this.detailsPanelClass) || this.uiPanel.hasClass(this.newPanelClass))
	    	{
		    	//window.log(this.uiPanel.outerWidth(false), this.state.optimalWidthInPx);	    	
		    	if (this.uiPanel.outerWidth(false) < this.state.optimalWidthInPx)
		    	{
		    		this.uiBody.find('div.block-details-preview').hide();
		    		this.uiBody.find('div.block-details').addClass('extra-wide');
		    		this.uiBody.find('div.block-details-nav').addClass('block block-nav block-nav-bottom').removeClass('block-details-nav');
		    	}
		    	else
		    	{
		    		this.uiBody.find('div.block-details').removeClass('extra-wide');
		    		this.uiBody.find('div.block-nav-bottom').removeClass('block block-nav block-nav-bottom').addClass('block-details-nav');
		    		this.uiBody.find('div.block-details-preview').show();
		    	}
	    	}
	    	
	    	this._doNavItemsLayout();
		},
		
		_doNavItemsLayout: function()
		{
			//window.log('uiPanel._doNavItemsLayout', 'ID='+this.options.ID);						
	    	this.uiBody.find('div.'+this.navItemsClass+':visible').each(function(){
	    		var children = $(this).children();	    		
	    		var labelElem = children.filter('.label');
	    		if (labelElem.length == 0) return;
	    		
	    		var labelOffset;
	    		
	    		// details navitems, right-aligned labels
	    		if ($(this).parent('div').hasClass('block-details-nav'))
	    		{
	    			labelOffset = children.filter('.icon').width();
	    		}
	    		else // "normal case"
	    		{
	    			labelOffset = children.filter('.icon').width() || labelElem.position().left;
	    			//window.log($(this).width(), labelOffset, children.filter('.info').width(), children.filter('.nav-arrow').width());
	    		}
	
	    		var maxWidth = $(this).width() - labelOffset - children.filter('.info').width() - children.filter('.nav-arrow').width();
	    		labelElem.css('max-width', Math.round(maxWidth)+'px');
			});
		},
				
		// internal : ...
		_disableToolbar: function() {
			//window.log('uiPanel._disableToolbar', this.options.ID);	
			this.uiTitleBar.unbind();
			
			this.uiTitleBar.find('a.'+this.toolbarToolClass).addClass(this.toolbarToolOffClass);
			this.uiTitleBar.find('a.'+this.toolbarToolClas).addClass(this.toolbarToolOffClass);
			
			this.uiTitleBar.find('a.'+this.toolbarNavPrevClass).hide();
			this.uiTitleBar.find('a.'+this.toolbarNavNextClass).hide();
			
			this.uiTitleBar.find('a.'+this.toolbarMaximizeClass).hide();
			this.uiTitleBar.find('a.'+this.toolbarNormalSizeClass).hide();
		},
		
		// internal : ...
		_toggleToolbarOptions: function() {
			//window.log('uiPanel._toggleToolbarOptions', this.options.ID, this.uiTitleBar.hasClass(this.titleBarOptionsClass));	
			if (this.uiTitleBar.hasClass(this.titleBarOptionsClass))
			{
				this.uiTitleBar.removeClass(this.titleBarOptionsClass).attr('title', 'Click to display options'+' ('+this.uiTitle.attr('panel-rel-path')+')');
			}
			else
			{
				this.uiTitleBar.addClass(this.titleBarOptionsClass).attr('title', 'Click to hide options'+' ('+this.uiTitle.attr('panel-rel-path')+')');
			}
		},
		
		// internal : display/hide toolbar buttons (reload, ...) according to panel position
		_doToolbarLayout: function() {
			//window.log('uiPanel._doToolbarLayout', this.options.ID, this.state.position, this.state.width);			
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			/* display/hide options (click on title bar) */
			
			this.uiTitleBar.unbind(); // just in case... race conditions (resize/set content) might occur
			if (this.uiTitleBar.hasClass(this.titleBarOptionsClass))	
			{
				this.uiTitleBar.click(delegate(this, function(event){
			        var e = jQuery.event.fix( arguments[0] || window.event );
			        if ($(e.target).hasClass(this.toolbarToolClass)) return false;
			        this._toggleToolbarOptions();
				})).attr('title', 'Click to hide options'+' ('+this.uiTitle.attr('panel-rel-path')+')');
			}
			else
			{
				this.uiTitleBar.click(delegate(this, function(event){
			        var e = jQuery.event.fix( arguments[0] || window.event );
			        if ($(e.target).hasClass(this.toolbarToolClass)) return false;
			        this._toggleToolbarOptions();
				})).attr('title', 'Click to display options'+' ('+this.uiTitle.attr('panel-rel-path')+')');
			}
			
			/* action buttons */
			
			this.uiTitleBar.find('a.'+this.toolbarToolClass).removeClass(this.toolbarToolOffClass);

			if (this.state.width < columnsCount) // maximmize
			{
				this.uiTitleBar.find('a.'+this.toolbarMaximizeClass).removeClass(this.toolbarToolOffClass).show();
				this.uiTitleBar.find('a.'+this.toolbarNormalSizeClass).addClass(this.toolbarToolOffClass).hide();
				
				/*this.uiTitleBar.dblclick(delegate(this, function(event){
			        var e = jQuery.event.fix( arguments[0] || window.event );
			        if ($(e.target).hasClass(this.toolbarToolClass)) return false;
					this.uiContainer.panelslider('maximizePanel', this.options.ID);
				})).attr('title', 'Double-click for full screen'+' ('+this.uiTitle.attr('panel-rel-path')+')');*/
			}
			else 
			{
				if (this.state.optimalWidth < columnsCount) // normal size
				{
					this.uiTitleBar.find('a.'+this.toolbarMaximizeClass).addClass(this.toolbarToolOffClass).hide();
					this.uiTitleBar.find('a.'+this.toolbarNormalSizeClass).removeClass(this.toolbarToolOffClass).show();
					
					/*this.uiTitleBar.dblclick(delegate(this, function(event){
				        var e = jQuery.event.fix( arguments[0] || window.event );
				        if ($(e.target).hasClass(this.toolbarToolClass)) return false;
						this.uiContainer.panelslider('gotoPanel', this.options.ID);
					})).attr('title', 'Double-click for normal size'+' ('+this.uiTitle.attr('panel-rel-path')+')');*/
				}
				else // none : width = optimal width = full screen
				{
					this.uiTitleBar.find('a.'+this.toolbarMaximizeClass).addClass(this.toolbarToolOffClass).hide();
					this.uiTitleBar.find('a.'+this.toolbarNormalSizeClass).addClass(this.toolbarToolOffClass).hide();
				}
			}
			
			/* navigation buttons */
			
			this.uiTitleBar.find('a.'+this.toolbarNavPrevClass).removeClass(this.toolbarToolOffClass).hide();
			this.uiTitleBar.find('a.'+this.toolbarNavNextClass).removeClass(this.toolbarToolOffClass).hide();
						
			// left edge
			if ((this.state.position == 0) && (this.options.parentID != null)) 
			{
				this.uiTitleBar.find('a.'+this.toolbarNavNextClass).addClass(this.toolbarToolOffClass).hide();
				this.uiTitleBar.find('a.'+this.toolbarNavPrevClass).removeClass(this.toolbarToolOffClass).show();
				
				if ((this.state.width == columnsCount) && (this.childID != null))
				{
					this.uiTitleBar.find('a.'+this.toolbarNavNextClass).removeClass(this.toolbarToolOffClass).show();
				}
				return;
			}
			
			// right edge
			if (((this.state.position + this.state.width) == columnsCount) && (this.childID != null))
			{
				this.uiTitleBar.find('a.'+this.toolbarNavPrevClass).addClass(this.toolbarToolOffClass).hide();
				this.uiTitleBar.find('a.'+this.toolbarNavNextClass).removeClass(this.toolbarToolOffClass).show();
				return;
			}
			
			// middle
			/*this.uiTitleBar.find('a.'+this.toolbarNavPrevClass).addClass(this.toolbarToolOffClass);
			this.uiTitleBar.find('a.'+this.toolbarNavNextClass).addClass(this.toolbarToolOffClass);*/
		},
		
		_doHeaderLoaderLayout: function() {
			//window.log('uiPanel._doHeaderLoaderLayout');
	    	if (this.uiHeaderLoader.length == 0)
	    	{
	    		return;
	    	}
	    	
	    	var scrollTop = this.uiHeader.attr('scrollTop');
	    	//window.log(scrollTop);
	    	this.uiHeaderLoaderContainer.css('top', scrollTop+'px');
		},
		
		_doBodyLoaderLayout: function() {
			//window.log('uiPanel._doBodyLoaderLayout');
	    	if ((this.uiBodyLoader.length == 0) || (this.uiBodyLoader.is(':hidden')))
	    	{
	    		//window.warn('Body loader layout discarded!', this.options.ID);
	    		return;
	    	}
	    	
	    	var scrollTop = this.uiBody.attr('scrollTop');
	    	//window.log(scrollTop);
	    	this.uiBodyLoaderContainer.css('top', scrollTop+'px');
		},
		
		_enableDisable: function(disabled) {
			//window.log('uiPanel._enableDisable', arguments);
			//window.log('current option='+this.options.disabled);			
			if (this.options.disabled == disabled) return;
			
			if (disabled == true)
			{
				this._disable();					
			}
			else if (disabled == false)
			{
				this._enable();
			}
		},
		
		_disable: function() {		
			this._findLoaderElems('panel');
			if (this.uiLoaderContainer.length == 0) // no opaque container : create it
			{
				this._createLoaderElems('panel');
			}
			
			this.uiLoaderContainer.show(); // opaque background over the panel...
			this.uiLoader.hide(); // ...but no loader
		},

		_enable: function() {
			this._findLoaderElems('panel');
			if (this.uiLoaderContainer.length == 0) // no opaque container : create it
			{
				this._createLoaderElems('panel');
			}
			
			this.uiLoaderContainer.hide(); // opaque background over the panel...
			this.uiLoader.show(); // ...but no loader
		},
		
		// internal : new panel placement strategy/go to next panel strategy
		// NB : we use future state in case this is called when an animation is in progress
		_nextPlacement: function(posInc, callback) {			
			var futureState = this.futureState;			
			var targetPos = futureState.position + posInc;
			//window.log('uiPanel._nextPlacement', arguments, this.options.ID, targetPos);
			if (targetPos < 0)
			{
				var targetRightEdge = targetPos + futureState.width;
				
				// check if panel overlaps on the left edge of the visible area
				if (targetRightEdge > 0)
				{
					if (futureState.position > 0) // go to left edge?
					{
						this.scrollResize(-futureState.position, targetPos); // go to left edge and resize
					}
					else // already @ left edge (position=0)
					{
						this.scrollResize(null, posInc); // stay there and resize
					}					
					return; // done
				}
				else if (targetRightEdge < 0) // animation cosmetics
				{
					// exit position : go hide just at the left of the left edge			
					posInc = -this.getEdgePosition(); 
				}
			}

			// scroll
			this.scrollResize(posInc, null);
		},
		
		// internal : go to prev panel placement strategy
		// NB : we use future state in case this is called when an animation is in progress
		_prevPlacement: function(posInc, callback) {
			//window.log('uiPanel._prevPlacement', arguments, this.options.ID);
			var futureState = this.futureState;	
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			var targetPos = futureState.position + posInc;
			var targetRightEdge = targetPos + futureState.width;
						
			if (targetRightEdge > columnsCount)
			{
				// check if panel overlaps on the right edge of the visible area
				if (targetPos < columnsCount)
				{
					var rightEdge =  this.getEdgePosition();
					// go to right edge or already @ right edge?
					var resizeInc = (rightEdge < columnsCount) ? -(targetRightEdge - columnsCount) : -posInc;
					this.scrollResize(posInc, resizeInc);
					return; // done
				}
				else if (targetPos >= columnsCount) // animation cosmetics
				{
					// exit position : go hide just at the right of the right edge			
					posInc = columnsCount - futureState.position; 
				}
			}
			
			// scroll
			this.scrollResize(posInc, null);
		},
		
		// logging
		log: function() {
			var state = this.state;
			var futureState = this.futureState;
			window.group('Panel ID='+this.options.ID, this.options.title, this.options);
			this.uiPanel.log();
			window.log('State: p='+state.position+' | w='+state.width+' | optw='+state.optimalWidth);
			window.log('Future state: p='+futureState.position+' | w='+futureState.width);
			window.log('Animation='+state.animationInProgress+' | New content='+state.newContentAvailable);
			window.groupEnd();
		},
		
		// method : setters
		setPosition: function(newPosition, hide) {
			//window.log('uiPanel.setPosition', arguments, this.options.ID);
			var columnWidth = this.uiContainer.panelslider('getColumnWidth');
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			this.futureState.position = this.state.position = newPosition;			
			this.uiPanel.attr('pos', this.state.position);
			
			if (hide)
			{
				if (this.uiContainer.panelslider('isPanelVisible', this.options.ID) == false)
				{
					this.hide();
				}
				else
				{
					this.show();
				}
			}
			
			var left = this.state.position * columnWidth;
			this.uiPanel.css({
				left : left+'px'
			});
		},
		
		incPosition: function(posInc, hide) {
			//window.log('uiPanel.incPosition', arguments, this.options.ID);
			this.setPosition(this.state.position+posInc, hide);
		},

		setWidth: function(colsCount) {
			//window.log('uiPanel.setWidth', arguments, this.options.ID);
			var columnWidth = this.uiContainer.panelslider('getColumnWidth');
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			if (colsCount > columnsCount) // upper limitation
			{
				colsCount = columnsCount;
			}
			
			this.futureState.width = this.state.width = colsCount;
			
			var width = (this.state.width * columnWidth) - this.padding;
			this.uiPanel.css({
				width : width+'px'
			});
		},
		
		incWidth: function(widthInc) {
			//window.log('uiPanel.incWidth', arguments, this.options.ID);
			this.setWidth(this.state.width+widthInc);
		},
		
		setOptimalWidth: function(colsCount) {
			//window.log('uiPanel.setOptimalWidth', arguments, this.options.ID);
			this.state.optimalWidth = colsCount;
		},
		
		setOptimalWidthInPx: function(widthInPx) {
			//window.log('uiPanel.setOptimalWidthInPx', arguments, this.options.ID);
			this.state.optimalWidthInPx = widthInPx;
		},
		
		setChildID: function(panelID) {
			this.childID = panelID;
		},
		
		// methods : getters
		getConfig: function() {
			return this.options;
		},
		
		getID: function() {
			return this.options.ID;
		},

		getParentID: function() {
			return this.options.parentID;
		},
		
		getChildID: function() {
			return this.childID;
		},

		getIndex: function() {
			return this.options.index;
		},
		
		getPosition: function() {
			return this.state.position;
		},

		getFuturePosition: function() {
			return this.futureState.position;
		},

		getWidth: function() {
			return this.state.width;
		},

		getFutureWidth: function() {
			return this.futureState.width;
		},
		
		getEdgePosition: function() {
			return (this.state.position+this.state.width);
		},

		getFutureEdgePosition: function() {
			var futureState = this.futureState;
			return (futureState.position+futureState.width);
		},
		
		getOptimalWidth: function() {
			return this.state.optimalWidth;
		},
		
		getNonBodyElemsHeight: function() {
			this._findTitleBarElems();
			this._findHeaderElem();
	    	return (this.uiTitleBar.outerHeight(true)+this.uiHeader.outerHeight(true));
		},
		
		// methods : display loaders
		displayPanelLoader: function(display) {
			//window.log('uiPanel.displayPanelLoader', arguments, this.options.ID);	
			if (this.uiLoaderContainer.length == 0) // no loader : create it
			{
				this._createLoaderElems('panel');
			}
			
			if (display == true)
			{
				this.uiLoaderContainer.show();
				this.uiLoader.show();
			}
			else
			{
				this.uiLoaderContainer.hide();
				this.uiLoader.hide();
			}
		},
		
		displayHeaderLoader: function(display) {
			//window.log('uiPanel.displayHeaderLoader', arguments, this.options.ID);		
			if (this.uiHeader.length == 0) // no header : create it
			{
				this._createHeaderElem();
				this._createLoaderElems('header');
			}
			else // body exists
			{
				if (this.uiHeaderLoaderContainer.length == 0) // no loader : create it
				{
					this._createLoaderElems('header');
				}
			}
			
			if (display == true)
			{
				this.uiHeaderLoaderContainer.show();
				this.uiHeaderLoader.show();
			}
			else
			{
				this.uiHeaderLoaderContainer.hide();
				this.uiHeaderLoader.hide();
			}
		},
		
		displayBodyLoader: function(display) {
			//window.log('uiPanel.displayBodyLoader', arguments, this.options.ID);
			//window.log(this.uiBody);
			//window.log(this.uiBodyLoaderContainer);
			//window.log(this.uiBodyLoader);						
			if (this.uiBody.length == 0) // no body : create it
			{
				this._createBodyElem();
				this._createLoaderElems('body');
			}
			else // body exists
			{
				if (this.uiBodyLoaderContainer.length == 0) // no loader : create it
				{
					this._createLoaderElems('body');
				}
			}
			
			if (display == true)
			{				
				this.uiBodyLoaderContainer.show();
				this.uiBodyLoader.show();
				this._doBodyLoaderLayout(); // if we have scrolled @ bottom of a list, it should be displayed there
			}
			else
			{
				this.uiBodyLoaderContainer.hide();
				this.uiBodyLoader.hide();
			}
			
			//window.log(this.uiBodyLoaderContainer);
			//window.log(this.uiBodyLoader);
		},
		
		// method : maximize panel width
		maximize: function() {
			//window.log('uiPanel.maximize', arguments, this.options.ID);
			this.scrollResize(-this.state.position, this.uiContainer.panelslider('getColumnsCount')-this.state.width);
		},

		// method : scroll + resize
		scrollResize: function(posInc, widthInc, callback) {
			//window.log('uiPanel.scrollResize', arguments, this.options.ID);
			this.state.animationInProgress = true; // lock
			
			// disable toolbar during the animation
			this._disableToolbar();
			
			var columnWidth = this.uiContainer.panelslider('getColumnWidth');
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			var animParms = { };
						
			if (posInc)
			{
				var scrollInc = posInc * columnWidth;
				animParms.left = '+='+scrollInc; // > 0 moves to the right
				
				// help solve animation issues
				this.futureState.position = this.futureState.position + posInc;
				//window.log('future position='+this.futureState.position, this.options.ID);
			}
			if (widthInc)
			{
				var resizeInc = widthInc * columnWidth;
				animParms.width = '+='+resizeInc;
				
				// help solve animation issues
				this.futureState.width = this.futureState.width + widthInc;
				//window.log('future width='+this.futureState.width, this.options.ID);
			}
			
			this.uiPanel.animate(
				animParms, 
				this.scrollSpeed, 
				this.scrollEasing, 
				delegate(this, function() {
					//this.log();
					
					// integrity : some race conditions might occur...
					// e.g.: scenario is : ModuleDetails > NewElement > CreateAndNew > CreateAndNew
					// [C1 C2 C3]
					// ModuleDetails on C1 & C2, new on C3
					var columnsCount = this.uiContainer.panelslider('getColumnsCount');
					if ((this.futureState.position + this.futureState.width) > columnsCount)
					{
						this.futureState.width -= (this.futureState.position + this.futureState.width) - columnsCount;
					}
					
					// update state
					if (posInc)
					{						
						this.setPosition(this.futureState.position, true);
					}
					if (widthInc)
					{
						this.setWidth(this.futureState.width);
					}
					
					// unlock
					this.state.animationInProgress = false;
					
					// try to set content
					var contentSetted = this._setContent();
					
					if ($.isFunction(callback))
					{
						callback();
					}
					
					// content was not setted, layout was not done
					if (!contentSetted) this._doLayout();
				})
			);
		},
		
		// method : load content via AJAX
		load: function(addParms) {
			//window.log('uiPanel.load', this.options.ID);
			//window.log(content);
			// just in case
			this.contentRequest.abort();
			
			this.freeExternalResources();
			
			this.displayPanelLoader(true);
						
			// prepare AJAX data
			var data2Send = this.options.content.ajaxData;
			data2Send.panelID = this.options.ID;
			data2Send.parentID = this.options.parentID;
			data2Send.title = this.options.title;
			data2Send.icon = this.options.icon;
			for (var i in addParms)
			{
				data2Send[i] = addParms[i];
			}
			
			this.contentRequest.send(data2Send);
		},
		
		// method : set html content
		content: function(html) {			
			//window.log('uiPanel.content', this.options.ID);
			//window.log(html);
	        // store content for later : when panel is placed (no animation in progress)
			this.currentContent = html;		
			this.state.newContentAvailable = true;
			
			// retrieve custom panel width (see XSL)
			var columnWidth = this.uiContainer.panelslider('getColumnWidth');
			var columnsCount = this.uiContainer.panelslider('getColumnsCount');
			
			// default values
			this.setOptimalWidth(1);	
			this.setOptimalWidthInPx(columnWidth);
			
			var self = this;
			$(this.currentContent).each(function() {
				var optimalWidthInPx = $(this).attr('optimal-width');
				if (optimalWidthInPx)
				{
					if (optimalWidthInPx == 'fullscreen')
					{
						self.setOptimalWidth(columnsCount);
						self.setOptimalWidthInPx(columnsCount*columnWidth);
						return;
					}
					
					// the actual panel width must be a multiple of columnWidth ("snap to grid" width effect)
					optimalWidthInCols = Math.ceil(optimalWidthInPx / columnWidth);
					if (optimalWidthInCols > columnsCount) 
					{
						optimalWidthInCols = columnsCount; // fullscreen limitation
					}
					else if (optimalWidthInCols < 1) 
					{
						optimalWidthInCols = 1; // single column limitation
					}
					
					self.setOptimalWidth(optimalWidthInCols);
					self.setOptimalWidthInPx(Number(optimalWidthInPx));
				
					//window.log('Custom width from XSL: '+optimalWidthInPx, optimalWidthInCols);
					return;
				}				
			});			
			
			// resize to optimal width?
			// NB : we use future state because a previous scroll might be in progress (e.g. : loading a panel that comes into the visible area)
			var futureState = this.futureState;
			var widthDiff = this.state.optimalWidth - futureState.width;
			if (widthDiff > 0)
			{					
				if ((futureState.position + this.state.optimalWidth) > columnsCount) // overlaps the right edge?
				{						
					// resize this panel (cosmetics)
					this.setWidth(this.state.optimalWidth); // no cosmetics
					
					// trigger the placement strategy for all previous visible panels
					var scrollInc = columnsCount - this.state.optimalWidth - futureState.position;
					this.uiPanel.prevAll('div.'+this.panelClass+':visible').trigger('panel-nextplacement', [scrollInc]);
					
					// scroll
					this.scrollResize(scrollInc, null);		
				}
				else // resize & move panels@right
				{
					this.uiPanel.nextAll('div.'+this.panelClass+':visible').trigger('panel-prevplacement', [widthDiff]);
					
					this.scrollResize(null, widthDiff);
				}
			}
			else if (widthDiff < 0)
			{
				var totalWidth = columnsCount - (futureState.position + this.state.optimalWidth);
				var scrollInc = widthDiff;
				this.scrollResize(null, scrollInc);
				
				// place next panels in the remaining space
				var currentPanel = this.uiPanel.next('div.'+this.panelClass);
				while (currentPanel.length > 0)
				{
					var rEdge = currentPanel.panel('getFuturePosition') + scrollInc  + currentPanel.panel('getFutureWidth');
					if (rEdge >= columnsCount)
					{
						currentPanel.trigger('panel-scrollresize', [scrollInc, -(rEdge - columnsCount)]);
						break;
					}
					
					currentPanel.trigger('panel-scrollresize', [scrollInc, null]);
					currentPanel = currentPanel.next('div.'+this.panelClass);
				}
			}
			
			// try to set content
			this._setContent();
		},
		
		// method : refresh content
		refresh: function(addParms) {
			//window.log('uiPanel.refresh');			
			if ($.isFunction(this.options.onRefresh))
			{
				return this.options.onRefresh(this.element);
			}
			else
			{			
				window.info('No refresh callback provided for panel (ID='+this.getID()+'). Reloading...');
				return this.load(addParms); // default
			}
		},
		
		setActiveItem: function(itemSelector) {
			//window.log('uiPanel.setActiveItem');
		    if (this.uiActiveItem != null)
		    {
		    	this.uiActiveItem.removeClass('selected hover');
		        this.uiActiveItem.children().removeClass('selected hover');
		    }

		    var newItem = $(itemSelector);
		    if (newItem.length > 0)
		    {
		        if (newItem.hasClass(this.navItemsClass) == false)
		        {
		            // parent?
		        	newItem = newItem.parent('div.'+this.navItemsClass);
		        }

		        if (newItem.length > 0)
		        {
		        	newItem.removeClass('hover').addClass('selected');
		        	newItem.children().removeClass('hover').addClass('selected');
		        }
		    }
		    else
		    {
		    	newItem = null;
		    }

		    this.uiActiveItem = newItem;
		},
		
		// method : show
		show: function() {
			//window.log('uiPanel.show', this.options.ID);
			this.uiPanel.show();
			$(this.historySelector).panelhistory('updateVisibility', this.options.ID, true);
		},

		// method : hide
		hide: function() {
			//window.log('uiPanel.hide', this.options.ID);
			this.uiPanel.hide();
			$(this.historySelector).panelhistory('updateVisibility', this.options.ID, false);
		},
		
		// method : add JS object used by this panel
		// NB: the object must have the "destroy" method implemented
		addExternalResource : function(obj, key) {
			//window.log('uiPanel.addExternalResource', this.options.ID, key, obj);
			if (key)
			{
				this.externalResources[key] = obj;
			}
			else
			{
				this.externalResources.push(obj);
			}
		},
		
		hasExternalResource : function(key) {
			//window.log('uiPanel.hasExternalResource', this.options.ID, arguments);
			//window.log(key, this.externalResources[key]);
			if (this.externalResources[key])
			{
				 return true;
			}
			return false;
		},
		
		freeExternalResources : function()
		{
			//window.log('uiPanel.freeExternalResources', this.options.ID);			
			// destroy objects
			this.uiPanel.advancedsearcher('destroy'); // just in case
			
		    for (var i in this.externalResources)
		    {
		    	// "destroy" method implemented?
		    	if ($.isFunction(this.externalResources[i].destroy))
	    		{
		    		//window.log(i, this.externalResources[i]);
		    		this.externalResources[i].destroy();
	    		}
		    	else
		    	{
		    		window.warn('Destroy method is undefined!', this.options.ID);
		    		window.warn(this.externalResources[i]);
		    	}
		        delete this.externalResources[i];
		        this.externalResources[i] = null;
		    }

		    this.externalResources = [];
		},
		
		// method : destruction
		destroy: function() {
			//window.log('uiPanel.destroy', this.options.ID);
			
			// just in case
			if (this.contentRequest != null)
			{
				this.contentRequest.abort();
				delete this.contentRequest;
				this.contentRequest = null;
			}
			
			this.freeExternalResources();
		    delete this.externalResources;
		    this.externalResources = null;

			// custom cleanup
			this.uiPanel.unbind();
			//this.uiPanel.find('*').unbind();
			this.uiPanel.empty();
			this.uiPanel.remove();
			
			// remove parent's child ID
			if (this.options.parentID != null)
			{
				this.uiContainer.find('#'+this.options.parentID+'.'+this.panelClass).panel('setChildID', null);
			}
			
			// call default
			$.Widget.prototype.destroy.apply(this, arguments);
		}
	});
})(jQuery);