/**
 * jQuery UI : panel slider
 *
 * @author  Marc Mignonsin
 * @version 2010-11-19
 *
 */

(function( $ ) {
	$.widget("ui.panelslider", {
		// default options
		options: {		
			optimalColumnWidth : 480,
			hasTouchScreen : (navigator.userAgent.match(/iPod|iPhone|iPad/i)  != null)
		},
	
		// internal : widget creation
		_create: function() {	
			//window.log('ui.panelslider._create', this.options);
									
			// --- internal configuration ---
			
			this.tabSessionID = hex_md5(this.options.userID+new Date().getTime().toString());
			this.languageID = this.options.language;
			this.loadPanelScript = this.options.systemURL + 'load-panel.php';
			
			this.menuBarSelector = '#menu-bar';	
			this.historySelector = '#history';
			this.panelClass = 'panel';
			
			// animation parms
			this.scrollSpeed = 500; // ms
			this.scrollEasing = 'easeOutExpo';
						
			// --- state variables ---
			
			this.currentPanelID = 0;
			
			this.uiSlider = this.element;	
			this.sliderWidth = this.sliderHeight = 0;
			this.columnsCount = this.prevColumnsCount = 0;
			this.columnWidth = 0;
			this.visibleArea = {
					left : 0,
					right : this.columnsCount,
					width : $(window).width(),
					height : $(window).height() - $(this.menuBarSelector).outerHeight(true)
				};
			
			this._computeDimensions(); // compute above dimensions
			this.prevColumnsCount = this.columnsCount; // avoid expand strategy to be triggered in _doLayout
			
			// history navigation
			this.uiHistory = $(this.historySelector).panelhistory();
			
			this._doLayout();
						
			// slider events
			this._bindSliderEvents();
		},
		
		_init: function()
		{
			//window.log('uiSlider._init', arguments);
			
			/* initial panel */
			if (this.options.panelURI)
			{		
				var config = {};
				config.editor = this.options.editor;
				config.app = this.options.app;
				config.title = html_entity_decode(this.options.title);
				//config.icon = this.options.icon;
				config.icon = this.options.systemImagesURL+'/menu/history/appbar-home.png';
				
				var params = clone(this.options);
				
				delete params.optimalColumnWidth;
				delete params.hasTouchScreen;
				delete params.disabled;
				
				delete params.appFolder;
				delete params.appImagesURL;
				delete params.appName;
				delete params.appURL;
				delete params.appsURL;
				delete params.editorFolder;
				delete params.editorName;				
				delete params.editorURL;
				delete params.firstTime;
				delete params.now;
				delete params.optimalColumnWidth;
				delete params.parentID;
				delete params.now;
				delete params.rootURL;
				delete params.sharedImagesURL;
				delete params.sharedLibraryURL;
				delete params.sharedURL;
				delete params.systemImagesURL;
				delete params.systemToolsURL;
				delete params.systemURL;
				delete params.tabSessionID;
				delete params.title;
				delete params.userID;
				
				/*window.log(this.options);
				window.log(config);
				window.log(params);*/
				
				this.loadPanel(this.options.panelURI, config, params);
			}
		},
		
		// internal : options
		_setOption: function(key, value) {
			//window.log('uiSlider._setOption', arguments);
			if (key === 'optimalColumnWidth') 
			{
				$.Widget.prototype._setOption.apply(this, arguments);
			}		
		},
		
		// internal : events 
		_bindSliderEvents: function() {
			//window.log('uiSlider._bindSliderEvents');
			// viewport size change
			$(window).bind('resize orientationchange', delegate(this, function(eventObject){
				this._doLayout();
			}));
			
			// layout
			this.uiSlider.bind('slider-layout', delegate(this, function(eventObject, prevColsCount){
				this._doLayout(prevColsCount);
			}));
			
			// slider destruction
			this.uiSlider.bind('slider-destroy', delegate(this, this.destroy));
			
			// goto panel event
			this.uiSlider.bind('slider-panelgoto', delegate(this, function(eventObject, panelID){
				this.gotoPanel(panelID);
			}));
		},
		
		// internal : layout
		_computeDimensions: function() {
			//window.log('uiSlider._computeDimensions', arguments);			
			
			// visible area
			this.visibleArea.width =  $(window).width();
			this.visibleArea.height = $(window).height() - $(this.menuBarSelector).outerHeight(true);
			
			this.prevColumnsCount = this.columnsCount;
			
			this.columnsCount = Math.floor(this.visibleArea.width / this.options.optimalColumnWidth);		
			if (this.columnsCount == 0) this.columnsCount = 1; // lower limitation
			
			this.visibleArea.right =  this.columnsCount;
			
			if (this.visibleArea.width < this.options.optimalColumnWidth)
			{
				this.columnWidth = this.visibleArea.width;
			}
			else
			{
				this.columnWidth = Math.floor(this.visibleArea.width / this.columnsCount);
			}
			
			// whole slider
			this.sliderWidth = parseInt(this.uiSlider.css('width'));
			this.sliderHeight = parseInt(this.uiSlider.css('height'));
			
			//window.log('visible area dimensions: '+this.visibleArea.width+'x'+this.visibleArea.width);
			//window.log('columns count: '+this.columnsCount, 'column width in px: '+this.columnWidth);
		},
		
		_doLayout: function(prevColsCount) {
			//window.log('uiSlider._doLayout', arguments);
			this._computeDimensions();
						
			// adjust slider height
			this.uiSlider.css({ 
				height : this.visibleArea.height+'px'
			});
			
			// only if the shell has not hidden the slider
			if (this.uiSlider.is(':visible'))
			{
				if ((prevColsCount !== undefined) && (typeof prevColsCount === 'number'))
				{		
					this.prevColumnsCount = prevColsCount;
				}
				var colsDiff = this.columnsCount - this.prevColumnsCount;
				var anchorPanel = this.uiSlider.find('div.'+this.panelClass+':visible:first');
				
				// panels expansion/reduction startegies
				if (colsDiff > 0) // columns increase
				{	
					this._expandStrategy(anchorPanel, colsDiff);
				}
				else if (colsDiff < 0) // column decrease
				{
					var anchorPanel = this.uiSlider.find('div.'+this.panelClass+':visible:last');	
					// do we have to reduce?				
					if (anchorPanel.panel('getEdgePosition') > this.columnsCount)
					{
						this._reduceStrategy(anchorPanel, anchorPanel.panel('getEdgePosition')-this.columnsCount);
					}
				}
				
				// panels layout (pos/width + header/body heights)
				this._getVisiblePanels().trigger('panel-layout'); 
			}
			
			// adjust history navigation
			this.uiHistory.trigger('history-layout');
		},
		
		// internal : expansion strategy - recursive
		_expandStrategy: function(anchorPanel, colsDiff) {
			//window.log('uiSlider._expandStrategy', arguments);
			// can we expand the anchor panel..?
			var remainingCols = this._expandPanel(anchorPanel, colsDiff);
			if (remainingCols <= 0) // done
			{
				return;
			}
			
			// let's try to expand the panels@right...
			var visibleSiblings = this._getNextVisiblePanels(anchorPanel);
			for (var i=0; i<visibleSiblings.length; i++)
			{
				remainingCols = this._expandPanel(visibleSiblings.eq(i), remainingCols);
				if (remainingCols <= 0) // done
				{
					return;
				}
			}
				
			// ...or the hidden panels@right come back on stage
			var lastPos = this.uiSlider.find('div.'+this.panelClass+':visible:last').panel('getEdgePosition');			
			
			var nextHiddenSiblings = this._getNextHiddenPanels(anchorPanel);
			for (var i=0; i<nextHiddenSiblings.length; i++)
			{
				var targetPanel = nextHiddenSiblings.eq(i);
				var newWidth = remainingCols;
				var optimalWidth = targetPanel.panel('getOptimalWidth');				
				if (newWidth > optimalWidth) newWidth = optimalWidth; // upper limitation				
				
				targetPanel.panel('setWidth', newWidth);				
				targetPanel.panel('setPosition', lastPos);					
				targetPanel.panel('show');
								
				remainingCols -= newWidth;
				if (remainingCols <= 0)
				{
					return; 
				}
		
				lastPos += newWidth;
			}
			
			// ...or strategy goes backwards : panels@left come back on stage
			var expandWidth = 0;
			var prevHiddenSiblings = this._getPrevHiddenPanels(anchorPanel);
			for (var i=0; i<prevHiddenSiblings.length; i++)
			{
				var targetPanel = prevHiddenSiblings.eq(i);
				var optimalWidth = targetPanel.panel('getOptimalWidth');
				
				if ((expandWidth + optimalWidth) < remainingCols)
				{
					// include this panel
					targetPanel.panel('setWidth', optimalWidth);					
					expandWidth += optimalWidth;
					targetPanel.panel('setPosition', -expandWidth);	
				}
				else					
				{
					// include this panel and crop it
					var croppedWidth = remainingCols - expandWidth;
					targetPanel.panel('setWidth', croppedWidth);
					expandWidth = remainingCols;
					targetPanel.panel('setPosition', -expandWidth);
					break;
				}
			}
			
			if (expandWidth > 0)
			{
				// move visible panels to the right
				this._getVisiblePanels().each(function(){
					$(this).panel('incPosition', expandWidth);
				});
				
				// move hidden panels to the right
				if (i == prevHiddenSiblings.length) 
				{
					i--;
				}			
				for (i; i>=0; i--)
				{
					var targetPanel = prevHiddenSiblings.eq(i);
					targetPanel.panel('incPosition', expandWidth);
					targetPanel.panel('show');
				}
			}
		},
		
		_expandPanel: function(panel2Expand, colsDiff) {			
			var currentWidth = panel2Expand.panel('getWidth');
			var optimalWidth = panel2Expand.panel('getOptimalWidth');
			//window.log('uiSlider._expandPanel', arguments, currentWidth, optimalWidth);
			
			if (currentWidth < optimalWidth) // can we expand this panel?
			{				
				var newWidth = currentWidth + colsDiff;
				if (newWidth > optimalWidth) newWidth = optimalWidth; // upper limitation				
				
				panel2Expand.panel('setWidth', newWidth);
				var widthInc = newWidth - currentWidth;
				
				this._getNextVisiblePanels(panel2Expand).each(function() {
					$(this).panel('incPosition', widthInc);
				});
				
				return (colsDiff - widthInc); // return the remaining number of columns to expand
			}
			
			return colsDiff;
		},
		
		_reduceStrategy: function(anchorPanel, colsDiff) {
			//window.log('uiSlider._reduceStrategy', arguments);
			var remainingCols = colsDiff;
			
			// let's try to reduce the leftmost panels...
			var visibleSiblings = this._getPrevVisiblePanels(anchorPanel);
			for (var i=visibleSiblings.length-1; i>=0; i--)
			{
				remainingCols = this._reducePanel(visibleSiblings.eq(i), remainingCols);
				if (remainingCols <= 0) // done
				{
					return;
				}
			}
			
			// ...or hide them
			var totalPos = 0;
			for (var i=visibleSiblings.length-1; i>=0; i--)
			{
				var panel2Hide = visibleSiblings.eq(i);
				var panelWidth = panel2Hide.panel('getWidth');
				totalPos += panelWidth;
				//window.log(panelWidth, totalPos);
				if (totalPos >= remainingCols)
				{
					break;
				}
			}			
			remainingCols -= totalPos;
						
			this._getVisiblePanels().each(function() {
				$(this).panel('incPosition', -totalPos, true);
			});
			
			if (remainingCols <= 0)
			{
				return;
			}
			
			// ...or finally we can reduce the anchor panel
			remainingCols = this._reducePanel(anchorPanel, remainingCols);
		},
		
		_reducePanel: function(panel2Reduce, colsDiff) {
			var currentWidth = panel2Reduce.panel('getWidth');
			//window.log('uiSlider._reducePanel', arguments, currentWidth);
			
			if (currentWidth > 1) // can we reduce this panel?
			{				
				var newWidth = currentWidth - colsDiff;
				if (newWidth < 1) newWidth = 1; // lower limitation
				
				panel2Reduce.panel('setWidth', newWidth);
				var widthInc = currentWidth - newWidth;
				
				this._getNextVisiblePanels(panel2Reduce).each(function() {
					$(this).panel('incPosition', -widthInc);
				});
				
				return (colsDiff - widthInc); // return the remaining number of columns to reduce
			}
			
			return colsDiff;
		},
		
		// internal : new panel creation
		_createNewPanel: function(config, childPanel) {
			//window.log('uiSlider._createNewPanel', arguments);
			if (childPanel) // reuse child panel?
			{
				newPanel = childPanel;
				
				// must be, or must change the strategy below
				if (newPanel.panel('getPosition') >= this.columnsCount)
				{
					config.width = 1; 
				}
				
				newPanel.panel(config); // update panel options (ajaxData, title, etc.)
				
				config = newPanel.panel('getConfig'); // retrieve full config
				this.uiHistory.panelhistory('add', config);
				
				// keep only the "panel" class
				var classList = newPanel.attr('class').split(/\s+/);
				for (var i=0, l=classList.length; i<l; i++) 
				{
				   if (classList[i] != this.panelClass) 
				   {
					   newPanel.removeClass(classList[i]);
				   }
				}
				
				// special case : a previous gotoPanel has lead to an incorrect position (< 0)
				if (newPanel.panel('getPosition') < newPanel.prev('div.'+this.panelClass).panel('getPosition'))
				{					
					newPanel.panel('setPosition', this.visibleArea.right);					
				}

				newPanel.panel('show');
			}
			else // creation
			{
				config.position = this._getLastAvailablePos();
				config.width = 1;
				config.ID = ++this.currentPanelID;
				config.index = this.uiHistory.panelhistory('add', config);							
				var newPanel = $('<div></div>').addClass(this.panelClass).appendTo(this.uiSlider).panel(config);
			}
			
			/* strategy */
			//newPanel.panel('log');
			if (newPanel.panel('getFuturePosition') >= this.visibleArea.right)
			{	
				if (!childPanel || !childPanel.is(':animated'))
				{
					var prevPanels = this._getPrevVisiblePanels(newPanel);
					var leftMostPanel = prevPanels.last();
					if (leftMostPanel.panel('getFutureWidth') > 1) // check if leftmost panel can be resized
					{
						leftMostPanel.panel('scrollResize', null, -1);
						prevPanels.filter(':not(:last)').panel('scrollResize', -1, null);		
					}
					else // eject leftMost panel
					{
						prevPanels.panel('scrollResize', -1, null);
					}
					newPanel.panel('scrollResize', -1, null);
				}
			}
			
			newPanel.panel('load');
		},
		
		// internal : getters
		_getVisiblePanels: function() {
			return this.uiSlider.children('div.'+this.panelClass+':visible');
		},
		
		_getPrevVisiblePanels: function(selector) {
			//window.log('this.uiSlider._getPrevVisiblePanels', arguments);
			return $(selector).prevAll('div.'+this.panelClass+':visible');
		},
		
		_getPrevHiddenPanels: function(selector) {
			//window.log('this.uiSlider._getPrevHiddenPanels', arguments);
			return $(selector).prevAll('div.'+this.panelClass+':hidden');
		},
		
		_getPrevPanels: function(selector) {
			//window.log('this.uiSlider._getPrevPanels', arguments);
			return $(selector).prevAll('div.'+this.panelClass);
		},
		
		_getNextVisiblePanels: function(selector, andSelf) {
			//window.log('this.uiSlider._getNextVisiblePanels', arguments);
			if (andSelf == true)
			{
				return $(selector).nextAll('div.'+this.panelClass+':visible').andSelf();
			}
			return $(selector).nextAll('div.'+this.panelClass+':visible');
		},
		
		_getNextHiddenPanels: function(selector) {
			//window.log('this.uiSlider._getNextHiddenPanels', arguments);
			return $(selector).nextAll('div.'+this.panelClass+':hidden');
		},
		
		_getNextPanels: function(selector, andSelf) {
			//window.log('this.uiSlider._getNextPanels', arguments);
			if (andSelf == true)
			{
				return $(selector).nextAll('div.'+this.panelClass).andSelf();
			}
			
			return $(selector).nextAll('div.'+this.panelClass);
		},
		
		_getLastAvailablePos: function() {			
			var lastPos = 0;
			var lastPanelID = this.uiHistory.panelhistory('lastID');
			//window.log('this.uiSlider._getLastAvailablePos', lastPanelID);
			
			if (lastPanelID != -1) // at least one panel?
			{
				var uiLastPanel = this.uiSlider.find('#'+lastPanelID+'.'+this.panelClass);
				lastPos = uiLastPanel.panel('getFutureEdgePosition');
				if (lastPos > this.columnsCount) lastPos = this.columnsCount; // handles animations in progress
			}
	
			//window.log('pos found: '+lastPos);
			return lastPos;
		},
		
		isPanelVisible : function(panelID)
		{
			var panelPosition = $('#'+panelID+'.'+this.panelClass).panel('getPosition');
			//window.log('this.uiSlider.isPanelVisible', arguments, ((panelPosition >= this.visibleArea.left) && (panelPosition < this.visibleArea.right)));
			return ((panelPosition >= this.visibleArea.left) && (panelPosition < this.visibleArea.right));
		},
		
		// methods : setters
		
		// methods : getters
		getColumnWidth: function() {
			return this.columnWidth;
		},

		getColumnsCount: function() {
			return this.columnsCount;
		},
		
		getHeight: function() {
			return parseInt(this.uiSlider.css('height'));
		},
		
		getTabSessionID: function() {
			return this.tabSessionID;
		},
		
		getLanguageID: function() {
			return this.languageID;
		},
		
		// search manager
		getAppURL: function() {
			return this.options.appURL;
		},

		// search manager
		getSystemToolsURL: function() {
			return this.options.systemToolsURL;
		},
		
		// 
		hasTouchScreen: function() {
			return this.options.hasTouchScreen;
		},
		
		// method : remove panels up to panelID
		cutPanels: function(panelID)
		{
			//window.log('uiSlider.cutPanels', arguments);
			var cuttedIDs = this.uiHistory.panelhistory('cut', panelID);					
			for (var i in cuttedIDs)
			{
				$('#'+cuttedIDs[i]+'.'+this.panelClass).trigger('panel-destroy'); // panels destruction
			}
		},
		
		// method : remove children panels
		cutChildrenPanels: function(panelID)
		{
			//window.log('uiSlider.cutChildrenPanels', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			var childID = currentPanel.panel('getChildID');			
			if (childID)
			{
				this.cutPanels(childID); // TODO : trigger expand strategy to fill potential empty space?
			}
		},
		
		// method : create and load a new panel (navigation item click, ...)
		loadPanel: function(panelURI, config, params2Send)
		{
			//window.log('uiSlider.loadPanel('+panelURI+')', config, params2Send);	
			// navitem click?		
			if (config.srcElem) 
			{
				if ($(config.srcElem).hasClass('selected')) // already selected
				{
					var currentPanel = $('#'+config.parentID+'.'+this.panelClass);
					var childID = currentPanel.panel('getChildID'); 
					if ((childID != null) && (currentPanel.panel('getEdgePosition') == this.columnsCount))
					{
						this.nextPanel(config.parentID);
					}
					return;
				}
			}
			
			// create AJAX config
			var ajaxData = params2Send || {};
			ajaxData.editor = config.editor || ajaxData.editor || this.options.editorFolder;
			ajaxData.app = config.app || ajaxData.app || this.options.appFolder;
			ajaxData.panelURI = panelURI;
			
			config.content = { 
				url : this.loadPanelScript,
				ajaxData : ajaxData
			};
			
			// allow icon to be in params
			config.icon = config.icon || params2Send.icon;
			
			// has a parent?
			if (config.parentID) 
			{	
				var currentPanel = $('#'+config.parentID+'.'+this.panelClass);
				currentPanel.panel('setActiveItem', config.srcElem);
				
				// has a child?
				var childID = currentPanel.panel('getChildID');
				if (childID)
				{
					//this.cutPanels(childID);
					// reuse direct child panel to solve animation issues/optimize
					this.cutChildrenPanels(childID);
					this.uiHistory.panelhistory('cut', childID);
					
					var childPanel = $('#'+childID+'.'+this.panelClass);
					this._createNewPanel(config, childPanel);
					return;
				}
			}
					
			this._createNewPanel(config);
		},
		
		// method : reload panel (toolbar button click)
		reloadPanel: function(panelID, parentsReLoadLevel, addParms) { 
			//window.log('uiSlider.reloadPanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			var childID = currentPanel.panel('getChildID');
			if (childID)
			{
				this.cutPanels(childID);
			}

			currentPanel.trigger('panel-reload', [addParms]);
			
			if (parentsReLoadLevel > 0)
			{	
				this._getPrevPanels(currentPanel).each(function(i) {					
					if (i >= parentsReLoadLevel)
					{	
						return;
					}
					$(this).trigger('panel-reload');
				});
			}
		},
		
		// method : refresh panel (after an update, ...)
		refreshPanel: function(panelID, parentsReFreshLevel, addParms) { 
			//window.log('uiSlider.refreshPanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			currentPanel.trigger('panel-refresh', [addParms]);
			
			if (parentsReFreshLevel > 0)
			{	
				this._getPrevPanels(currentPanel).each(function(i) {					
					if (i >= parentsReFreshLevel)
					{	
						return;
					}
					$(this).trigger('panel-refresh');
				});
			}
		},
		
		// method : replace panel (after an element creation)
		replacePanel: function(panelID, panelURI, newTitle, params2Send, parentsReFreshLevel) { 
			//window.log('uiSlider.replacePanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			
			var config = { 
				srcElem : null,
				parentID : currentPanel.panel('getParentID'),
				title : newTitle
			};
			
			if (parentsReFreshLevel > 0)
			{	
				this._getPrevPanels(currentPanel).each(function(i) {					
					if (i >= parentsReFreshLevel)
					{	
						return;
					}
					$(this).trigger('panel-refresh');
				});
			}
			
			this.cutPanels(panelID);
			this.loadPanel(panelURI, config, params2Send);
		},
		
		// method : remove panel (after an element deletion)
		removePanel: function(panelID, parentsReFreshLevel) { 
			//window.log('uiSlider.removePanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			if (currentPanel.panel('getPosition') == 0) // there will be no visible panels...
			{
				this.previousPanel(panelID); // TODO : trigger expand strategy to fill potential empty space?
			}
			
			if (parentsReFreshLevel > 0)
			{	
				this._getPrevPanels(currentPanel).each(function(i) {					
					if (i >= parentsReFreshLevel)
					{	
						return;
					}
					$(this).trigger('panel-refresh');
				});
			}
			
			this.cutPanels(panelID);
		},
		
		// method : reload parent panel (search manager)
		reloadParentPanel: function(panelID) { 
			//window.log('uiSlider.reloadPanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			var parentID = currentPanel.panel('getParentID');
			if (parentID)
			{
				$('#'+parentID+'.'+this.panelClass).trigger('panel-reload');
			}
		},
		
		// method : go to previous panel
		previousPanel: function(panelID) {
			//window.log('uiSlider.previousPanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			
			var parentID = currentPanel.panel('getParentID');
			if (parentID)
			{
				var parentPanel = $('#'+parentID+'.'+this.panelClass);
				
				var currentWidth = parentPanel.panel('getWidth');
				var optimalWidth = parentPanel.panel('getOptimalWidth');
				if (optimalWidth > this.columnsCount) optimalWidth = this.columnsCount; // width limitation
				
				var wDiff = optimalWidth - currentWidth;	
				if (wDiff != 0) // restore optimal width
				{
					parentPanel.panel('incWidth', wDiff);
				}
				
				// prepare scroll
				parentPanel.panel('setPosition', -optimalWidth);
				parentPanel.panel('show');
				parentPanel.trigger('panel-layout');
								
				// scroll
				parentPanel.panel('scrollResize', optimalWidth, null);
				
				// trigger the placement strategy for all visible panels					
				var visiblePanels = this._getNextVisiblePanels(parentPanel);										
				visiblePanels.trigger('panel-prevplacement', [optimalWidth, this.columnsCount]);
			}
		},

		// method : go to next panel
		nextPanel: function(panelID) {
			//window.log('uiSlider.nextPanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			
			var childID = currentPanel.panel('getChildID');
			if (childID)
			{
				var childPanel = $('#'+childID+'.'+this.panelClass);
				
				var currentWidth = childPanel.panel('getWidth');
				var optimalWidth = childPanel.panel('getOptimalWidth');
				if (optimalWidth > this.columnsCount) optimalWidth = this.columnsCount; // width limitation
				
				var wDiff = optimalWidth - currentWidth;				
				if (wDiff != 0) // restore optimal width
				{
					childPanel.panel('incWidth', wDiff);
				}
				
				// prepare scroll
				childPanel.panel('setPosition', this.columnsCount);
				childPanel.panel('show');
				childPanel.trigger('panel-layout');
								
				// scroll
				childPanel.panel('scrollResize', -optimalWidth, null);
				
				// trigger the placement strategy for all visible panels					
				var visiblePanels = this._getPrevVisiblePanels(childPanel);										
				visiblePanels.trigger('panel-nextplacement', [-optimalWidth]);
			}
		},
		
		// method : ...
		maximizePanel : function(panelID)
		{
			//window.log('uiSlider.maximizePanel', arguments);
			var currentPanel = $('#'+panelID+'.'+this.panelClass);
			var currentPos = currentPanel.panel('getPosition');
			var currentWidth = currentPanel.panel('getWidth');
			
			this._getPrevVisiblePanels(currentPanel).trigger('panel-scrollresize', [-currentPos]);
			this._getNextVisiblePanels(currentPanel).trigger('panel-scrollresize', [this.columnsCount-(currentPos+currentWidth)]);
			currentPanel.panel('maximize');
		},
		
		// method : ...
		normalSizePanel : function(panelID)
		{
			//window.log('uiSlider.normalSizePanel', arguments);
			//var currentPanel = $('#'+panelID+'.'+this.panelClass);
			this.gotoPanel(panelID);
		},
		
		// method : go to panel
		gotoPanel: function(panelID) {
			//window.log('uiSlider.gotoPanel', arguments);	
			
			// discard if animation is in progress..
			if (this.uiSlider.children('div.'+this.panelClass+':animated').length > 0) return;
			
			// 1st : update hidden positions and widths
			var leftHiddens = this._getPrevHiddenPanels($('div.'+this.panelClass+'[pos=0]'));
			this._sortHiddenPanels(leftHiddens, 0); // panels outside of the visible area @ left
			 
			var rightHiddens = this._getNextHiddenPanels($('div.'+this.panelClass+'[pos=0]'));
			this._sortHiddenPanels(rightHiddens, this.columnsCount); // panels outside of the visible area @ right
			
			// 2nd : fit a max. number of panels in the visible area, the target being leftmost
			var anchorPanel = $('#'+panelID+'.'+this.panelClass);
			var currentPanel = anchorPanel;
			var totalWidth = this.columnsCount;
			var optimalWidth;
			var animsParams = [];
			var panelsIDs = [];
			// from the anchor, to the east...
			do
			{
				panelsIDs.push(currentPanel.attr('id')); // for later...
				
				optimalWidth = currentPanel.panel('getOptimalWidth');
				animsParams.push({
					panelElem : currentPanel,
					futureWidth : optimalWidth
				});
				totalWidth -= optimalWidth;
				
				currentPanel = currentPanel.next('div.'+this.panelClass);
			}
			while ((totalWidth > 0) && (currentPanel.length > 0));
			
			// crop last panel?
			if (totalWidth < 0)
			{
				animsParams[animsParams.length-1].futureWidth += totalWidth;			
			}
			else if (totalWidth > 0) 
			{
				// from the anchor, to the left...
				currentPanel = anchorPanel.prev('div.'+this.panelClass);
				while ((totalWidth > 0) && (currentPanel.length > 0))
				{
					panelsIDs.push(currentPanel.attr('id')); // for later...
					
					optimalWidth = currentPanel.panel('getOptimalWidth');
					animsParams.unshift({
						panelElem : currentPanel,
						futureWidth : optimalWidth
					});
					totalWidth -= optimalWidth;
					
					currentPanel = currentPanel.prev('div.'+this.panelClass);
				}
				// crop first panel?
				if (totalWidth < 0)
				{
					animsParams[0].futureWidth += totalWidth;
				}
			}
				
			//window.log(animsParams);
			
			// 3rd : animate	
			// a) visible panels not concerned by the previous computation : they have to go away
			var ps = [];
			this._getVisiblePanels().each(function(){
				if (!in_array($(this).attr('id'), panelsIDs))
				{
					ps.push($(this));
				}
			});
			//window.log(ps);
	
			// determine relative position to the anchor: eastside?
			if (ps[0] && ps[0].nextAll('#'+animsParams[0].panelElem.attr('id')+'.'+this.panelClass).length > 0)
			{
					for (var i=0, l=ps.length; i<l; i++)
					{
						ps[i].trigger('panel-scrollresize', [-ps[i].panel('getFutureEdgePosition')]); // just in case
					}
			}
			else // westside
			{
				for (var i=0, l=ps.length; i<l; i++)
				{
					ps[i].trigger('panel-scrollresize', [this.columnsCount-ps[i].panel('getFuturePosition')]); // just in case
				}
			}
			
			// b) panels concerned
			var futurePos = 0;
			for (var i=0, l=animsParams.length; i<l; i++)
			{				
				var ap = animsParams[i];
				var pos = ap.panelElem.panel('getFuturePosition'); // just in case
				var width = ap.panelElem.panel('getFutureWidth'); // just in case
				//window.log(ap.panelElem);
				//window.log('p='+pos, futurePos);
				//window.log('w='+width, ap.futureWidth);
				ap.panelElem.show().trigger('panel-scrollresize', [futurePos-pos, ap.futureWidth-width]);
				futurePos += ap.futureWidth;
			}
		},	
		
		// method : ...
		newTabPanel : function(panelID, fullAppURL, parms)
		{
			//window.log('uiSlider.newTabPanel', arguments);			
			fullAppURL += '?';
			for (var i in parms)
			{
				fullAppURL += i + '=' + parms[i] + '&';
			}
			window.open(fullAppURL, '');
		},
		
		_sortHiddenPanels: function(panelsSet, startPos)
		{
			//window.log('uiSlider._sortHiddenPanels', arguments);
			var columnWidth = this.columnWidth;
			var columnsCount = this.columnsCount;
			
			var currentPos = startPos;
			if (currentPos > 0)
			{
				panelsSet.each(function(){
					$(this).panel('setPosition', currentPos);
					var optimalWidth = $(this).panel('getOptimalWidth');
					$(this).panel('setWidth', optimalWidth);
					currentPos += optimalWidth;
				});
			}
			else
			{
				panelsSet.each(function(){
					var optimalWidth = $(this).panel('getOptimalWidth');
					currentPos -= optimalWidth;
					$(this).panel('setPosition', currentPos);					
					$(this).panel('setWidth', optimalWidth);
				});
			}
		},
		
		// method : destruction
		destroy: function() {
			window.log('uiSlider.destroy');
			
			// custom cleanup
			this.uiSlider.unbind();
			
			this.uiHistory.trigger('history-destroy'); // history
						
			$('div.'+this.panelClass).trigger('panel-destroy'); // panels
			
			// call default
			$.Widget.prototype.destroy.apply(this, arguments); 
		}
	});
})(jQuery);