')
		.sortable({
			// general settings...
			forcePlaceholderSize: true,
			forceHelperSize: true,
			opacity: 0.7,
			connectWith: '.panel-content',
			helper: _prepareHelper,
			start: _startSortHandler,
			// - create a new panel when dropping outside of curent panel...
			// - remove empty panels...
			beforeStop: function(e, ui){
				// do this only when dropping outside the panel...
				if(ui.item.data('isoutside')
						// prevent draggingout the last panel...
						// NOTE: 2 because we are taking into account 
						// 		the placeholders...
						&& panel.find('.sub-panel').length > 2){
					wrapWithPanel(ui.item, panel.parent(), ui.offset)
				}
				panel.trigger('subPanelsUpdated')
				_resetSidePanels()
				_resetSortedElem(ui.item)
					.data('isoutside', false)
			},
			over: function(e, ui){
				ui.item.data('isoutside', false)
				ui.placeholder
					//.height(ui.helper.outerHeight())
					// NOTE: for some reason width does not allways get
					// 		set by jquery-ui...
					.width(ui.helper.outerWidth())
					.show()
			},
			out: function(e, ui){
				ui.item.data('isoutside', true)
				ui.placeholder.hide()
			},
		})
		.appendTo(panel)
	if(parent != false){
		panel.appendTo(parent)
	}
	// NOTE: no need to call the panelOpening event here as at this point
	// 		no one had the chance to bind a handler...
	return panel
}
// side can be:
// 	- left
// 	- right
//
// XXX in part this is exactly the same as makePanel
// XXX need to trigger open/close sub panel events...
function makeSidePanel(side, parent, autohide){
	autohide = autohide == null ? 'on' : 'off'
	parent = parent == null ? $(PANEL_ROOT) : parent
	var panel = $('.side-panel.'+side)
	// only one panel from each side can exist...
	if(panel.length != 0){
		return panel
	}
	panel = $('')
		.addClass('side-panel panel-content ' + side)
		.attr('autohide', autohide)
		// XXX trigger open/close events on hide/show..
		// XXX
		// toggle auto-hide...
		.dblclick(function(e){
			var elem = $(this)
			if(elem.attr('autohide') == 'off'){
				elem.attr('autohide', 'on')
			} else {
				elem.attr('autohide', 'off')
			}
			return false
		})
		// hide temporarily opened side-panels...
		.mouseout(function(){
			// XXX jQuery bug: this does not work...
			//panel.prop('open', false)
			panel.attr('open', null)
		})
		.sortable({
			forcePlaceholderSize: true,
			opacity: 0.7,
			connectWith: '.panel-content',
			helper: _prepareHelper,
			start: _startSortHandler,
			// - create a new panel when dropping outside of curent panel...
			// - remove empty panels...
			beforeStop: function(e, ui){
				// do this only when dropping outside the panel...
				if(ui.item.data('isoutside')){
					wrapWithPanel(ui.item, panel.parent(), ui.offset)
				}
				_resetSidePanels()
				_resetSortedElem(ui.item)
					.data('isoutside', false)
			},
			over: function(e, ui){
				ui.item.data('isoutside', false)
				ui.placeholder
					//.height(ui.helper.outerHeight())
					// NOTE: for some reason width does not allways get
					// 		set by jquery-ui...
					.width(ui.helper.outerWidth())
					.show()
			},
			out: function(e, ui){
				ui.item.data('isoutside', true)
				ui.placeholder.hide()
			},
		})
	if(parent != false){
		panel.appendTo(parent)
	}
	// NOTE: no need to call the panelOpening event here as at this point
	// 		no one had the chance to bind a handler...
	return panel
}
// NOTE: if parent is not given this will create a new panel...
// NOTE: title must be unique...
function makeSubPanel(title, content, parent, open, content_resizable, close_button){
	title = title == null || title.trim() == '' ? ' ' : title
	parent = parent == null ? makePanel() : parent
	close_button = close_button == null ? true : close_button
	open = open == null ? true : open
	content_resizable = content_resizable == null 
		? false 
		: content_resizable
	var content_elem = $('')
	if(content != null){
		content_elem
			.append(content)
	}
	var sub_panel = $('')).parent()
			.resizable({
				handles: 's',
			})
			.css({
				overflow: 'hidden',
			})
	}
	// NOTE: no need to call the panelOpening event here as at this point
	// 		no one had the chance to bind a handler...
	return sub_panel
}
/**********************************************************************
* Actions...
*/
// XXX this should take the state into consideration while opening panels
// 		and open panels in specific parents and locations, maybe even with
// 		other neighbor panels...
// XXX currently parent is ignored if panel is already created, is this 
// 		correct???
// XXX update panel state...
function openPanel(panel, parent, no_blink){
	var title = typeof(panel) == typeof('str') ? panel : null
	panel = typeof(panel) == typeof('str')
		? getPanel(panel)
		: panel
	title = title == null ? panel.attr('id') : title
	var open = false
	
	// create a new panel...
	if(panel.length == 0){
		if(title in PANELS){
			var builder = PANELS[title]
			panel = builder({ 
				open: true,
				parent: parent,
			})
		}
	// show/open the panel and all it's parents...
	} else {
		open = isPanelVisible(panel)
		// show panels...
		panel
			.css('display', '')
			.prop('open', true)
			.parents('.panel')
				.css('display', '')
				.prop('open', true)
		// show side panels...
		panel
			.parents('.side-panel').first()
				// XXX jQuery bug: this does not work...
				//.prop('open', true)
				.attr('open', 'yes')
	}
	// if the panel was not open trigger the event...
	if(!open){
		panel.trigger('panelOpening', panel)
	}
	return no_blink ? panel : blinkPanel(panel)
}
// Open a set of sub-panels in one parent panel...
//
// returns the parent panel.
//
// NOTE: if parent is given and already exists then this will append the
// 		new panels to it...
// NOTE: this will not re-group already opened panels...
function openGroupedPanels(panels, parent){
	panels = typeof(panels) == typeof('str') ? [panels] : panels
	parent = parent == null ? makePanel() : parent
	panels.forEach(function(title){
		openPanel(title, parent, true)
	})
	return parent
}
// XXX
// XXX update panel state...
function openPanels(){
	// XXX
}
// Close the panel...
//
// NOTE: this does not care if it's a panel or sub-panel...
// XXX do we need a panelRemoved event???
// 		...and a symmetrical panelCreated??
// XXX update panel state...
function closePanel(panel){
	panel = typeof(panel) == typeof('str')
		? getPanel(panel)
		: panel
	panel.find('.sub-panel:visible').each(function(){
		var p = $(this)
		if(p.prop('open')){
			p.trigger('panelClosing', p)
		}
	})
	return panel
		.prop('open', false)
		.trigger('panelClosing', panel)
}
// Remove the panel after firing close events on it and all sub-panels...
//
// XXX update panel state...
function removePanel(panel){
	panel = typeof(panel) == typeof('str')
		? getPanel(panel)
		: panel
	/*
	if(PANEL_CLOSE_METHOD == 'hide'){
		return closePanel(panel)
			.hide()
	} else {
		return closePanel(panel)
			.remove()
	}
	*/
	return closePanel(panel)
		.hide()
}
/**********************************************************************
* High level interface...
*/
//
//	content_builder()	- should build and setup panel content
//	panel_setup(panel)	- should register panel open/close event 
//							handlers
//
// NOTE: this will search an element by title, so if it is not unique 
// 		an existing element will be returned...
function Panel(title, content_builder, panel_setup, content_resizable){
	var controller = function(state){
		state = state == null ? {} : state
		var parent = state.parent
		var open = state.open
		// 1) search for panel and return it if it exists...
		var panel = getPanel(title)
		// 2) if no panel exists, create it
		// 		- content_builder() must return panel content
		if(panel.length == 0){
			panel = makeSubPanel(title, content_builder(), parent, open, content_resizable)
				.attr('id', title)
			panel_setup(panel)
			// trigger the open event...
			if(isPanelVisible(panel)){
				panel.trigger('panelOpening', panel)
			}
		} else {
			var v = isPanelVisible(panel)
			if(open && !v){
				openPanel(panel)
			} else if(!open && v){
				closePanel(panel)
			}
		}
		// XXX set panel position, size, ...
		return panel
	}
	PANELS[title] = controller
	return controller
}
// XXX also need:
// 		- togglePanels()
// 			show/hide all the panels (a-la Photoshop's Tab action)
/*********************************************************************/
function getPanelState(){
	var res = []
	var _getPanel = function(){
		var panel = $(this)
		var offset = panel.offset()
		var sub_panels = panel.find('.sub-panel')
		res.push({
			type: (panel.hasClass('panel') ? 'panel'
					: panel.hasClass('side-panel') 
						&& panel.hasClass('left') ? 'side-panel-left'
					: panel.hasClass('side-panel') 
						&& panel.hasClass('right') ? 'side-panel-right'
					: null),
			top: offset.top,
			left: offset.left,
			open: panel.prop('open') ? true : false,
			autohide: panel.attr('autohide'),
			content: sub_panels.map(function(){
				var p = $(this)
				return {
					title: p.find('summary').text(),
				}
			}).toArray(),
		})
	}
	$('.panel, .side-panel').each(_getPanel)
	return res
}
function setPanelState(data){
	// XXX
}
/**********************************************************************
* vim:set ts=4 sw=4 :                                                */