/**
 * Simple popup panel class. Designed to be as light weight as possible, and as such has absolutely no UI components.
 * This class simply represents an empty div which can receives content from another source. Delegates to PanelManager functions for
 * adding and removing panel to document
 * 
 * Customisable with inline parameters or by stylesheets. It is recomended to define new panel styles in a stylesheet and simply set
 * the className parameter. However for certain cases eg when the panel dimensions are not known in advance it may be prefererable to
 * set some parameters inline.
 * 
 * Using id's or other unique elements in a template can cause problems as when the template is copied it will result 
 * in two copies of the unique element in the DOM. To work round this set the consume parameter to true. This
 * will remove the orriginal template text at creation time.
 * 
 * parameters
 * 
 * dimensions 		:	x/y hash with px dimensions for the panel
 * positon			:	x/y hash with absolute / fixed pixel position for the element
 * offset			:	x/y hash with a number between 0 and 1 inclusive. Used in auto positioning and 
 * 						auto margin calculation. @see FloatingPanel.autoMargin()
 * autoMargin		:	x/y hash used in conjunction with offset specifies wether to calcuate margins.
 * 						useful for panels which need to be centered but do not have fixed dimensions.
 * bindings			:	hash of elementId : binding where elementId is the element to bind to and binding is the 
 *						function to be executed
 * className		:	Class for the panel element. Will be added to standard class names.
 * modal			:	boolean display as a modal panel with a veil overing the rest of the page
 * insertRef		:	The element used as a reference point to insert the panel. If insertPosition is set to 'inplace'
 * 						then this parameter will be ignored
 * insertPosition 	:	the position relative to the insertRef that the panel should be inserted. can be one of top, 
 * 						bottom, before, after or special case inplace. inplace will insert the panel in the same
 * 						position as the template, or immediately before it if the consume parameter is not set.
 * consume			:	If true then the template text will be consumed at creation time. This can be used in cases 
 * 						where we want to use ids in the markup for the panel or when the panel contains expensive 
 * 						elements such as applets or flash objects. There is no way to recreate the panel if
 * 						this parameter is set so a reference must be maintained if it is to be reused.
 * 
 * 
 *TODO - allow percentage dimensions
 */
if (typeof DEBUG === "undefined") var DEBUG = 0;
if (typeof db === "undefined") var db = function(){}
db.FloatingPanel = Class.create({
	
	/**
	 * Create a new panel
	 * @param contentId - id of the element containing content to copy
	 * @param params optionaly overwrite any of the default parameters here
	 * @param delayDraw - boolean do not call show() to draw the dialog immediately	
	 */
	initialize : function(contentId, params, delayDraw) {
		
		this._addClass("floatingPanel");
		
		this.contentId=contentId;
		this.params = Object.extend({}, this.defaults);
		if(typeof params !== "undefined") Object.extend(this.params, params);
		this.params.className.split(" ").inject(this.classNames, function(array, value){
			array.push(value);
			return array;
		});
		
		if (this.params.insertRef == null) this.params.insertRef = document.body;
		/*for inplace insertions the insert position is calculated relative to a sibling or parent of the panel template*/
		if (this.params.insertPosition=="inplace") {
			var prev = $(contentId).previous();
			if(prev != null) {
				this.params.insertRef = prev;
				this.params.insertPosition = "after";
			} else {
				this.params.insertRef = $(contentId).parentNode;
				this.params.insertPosition = "top";
			}
		}
		
		if (DEBUG) console.info("new floatingPanel initialized ",this.params);
		if(!delayDraw) this.show(); 
	},
	
	defaults	:	{
		dimensions		:	{x : null, y: null},
		position		:	{x : null, y : null},
		offset			:	{x : null, y : null},
		autoPosition	:	{x : false, y: false},
		autoMargin		:	{x : false, y: false},
		bindings		: 	null,
		className		:	"",
		modal			:	false,
		insertRef		:	null, 		//actually document.body but we can't set this until the DOM is loaded
		insertPosition	:	"bottom",
		consume			: 	false
	},
	
	contentId	:	null,
	element		:	null,
	updateAreas	:	null,
	shown		:  false,
	
	
	show	:	function() {
		if(!this.initialised) {
			this.element = document.createElement("DIV");
			this.element.style.visibility="hidden";
			this.element.innerHTML = $(this.contentId).innerHTML;
			if (this.params.consume) $(this.contentId).remove();
			this.setup();
			PanelManager.push(this);
		} else if(!this.shown) {
			PanelManager.push(this);
			this.shown = true;
		} else if (DEBUG) {
			console.info(this.element+" is already shown");
		}
	},
	
	hide	:	function() {
		PanelManager.remove(this);
	},
	
	setup	:	function(params) {
		if (typeof params !== "undefined") Object.extend(this.params, params);
		
		this.element.className = this.classNames.join(" ");
		if (DEBUG) console.info(this.element.className);
		
		if(this.params.dimensions.x != null) this.element.style.width = this.params.dimensions.x + (parseFloat(this.params.dimensions.x) ? "px" : "");
		if(this.params.dimensions.y != null) this.element.style.height = this.params.dimensions.y + (parseFloat(this.params.dimensions.y) ? "px" : "");
		
		if (this.params.position.x != null) {
			this.element.style.left = this.params.position.x + "px";
		}
		
		if (this.params.position.y != null) {
			this.element.style.top = this.params.position.y + "px";
		}
		
		if (this.params.bindings != null) {
			var bindings = $H(this.params.bindings);
			bindings.keys().each(function(k){
				$(k).observe("click", bindings.get(k).bindAsEventListener(this));
			})
		}
		
		if (this.shown) {
			this.autoMargins();
		}
		
		this.initialised = true;
		
	},
	
	getId	:	function() {
		return this.element.id;
	},
	
	setId	:	function(id) {
		this.element.id = id;
	},
	
	setLevel : function(level) {
		this.level = level;
		this.setId("panel_"+this.level);
	},
	
	getLevel : function() {
		return this.level;
	},
	
	/**
	 * If there is a specific content area which is to be updated then set it here with an optional new id (to differentiate it from a template)
	 * TODO: copy the template out of the DOM to avoid the id clash problem? possibly as an optional behaviour for maximum flexibility
	 */
	setUpdateArea	:	function(element, newId) {
		if (typeof newId !== "undefined") element.id = newId;
		
		this.updateArea = $(element);
	},
	
	getUpdateArea	:	function() {
		return this.updateArea==null ? this.element : this.updateArea;
	},
	
	/**automatically calculate negative margins from the current width of the element.
	 * 
	 * eg if the element is 600px wide and 400px high and the offset parameters are set to 0.5, 0.25 then 
	 * the margin-left will be set to -300px and the margin-top will be set to -100px
	 */
	autoMargins	:	function() {
		if (this.params.autoMargin.x)  {
			var offset = this.params.offset.x || 0;
			var margin = parseFloat(this.element.offsetWidth) * offset;
			this.element.style.marginLeft = "-" + margin + "px"
		}
			
		if (this.params.autoMargin.y)  {
			var offset = this.params.offset.y || 0;
			var margin = (parseFloat(this.element.offsetHeight) * offset) * -1;
			this.element.style.marginTop =  margin + "px"
		}
	},
	
	//internal function used to chain together base classes
	_addClass	:	function(cls) {
		if (typeof this.classNames === "undefined") this.classNames = [];
		this.classNames.push(cls);
	}
});

db.ModalPanel = Class.create(db.FloatingPanel, {
	
	initialize	:	function($super, contentId, params, delayDraw){
		this._addClass("modalPanel");
		
		var p = Object.extend({
				offset			:	{x : 0.5, y : 0.5},
				modal			:	true,
				autoMargin		:	{x : false, y : false},
				autoPosition	:	{x : false, y : true}
			},
			this.params
		);
		
		if(typeof params !== "undefined") Object.extend(p, params);
		
		$super(contentId, p, delayDraw);
	}
});

db.ActionPanel = Class.create(db.ModalPanel, {
	
	initialize	:	function($super, contentId, params, delayDraw){
		this._addClass("actionPanel");
		
		var p = Object.extend({
			offset			:	{x : 0.5, y : 0.25}
		}, 
		this.params
		);
		
		if(typeof params !== "undefined") Object.extend(p, params);
		
		$super(contentId, p, delayDraw);
	}
	
});

db.Veil = Class.create({
	
	params	:	{
		className	:	"veil",
		id			:	"modalVeil"
	},
	
	element	:	null,
	
	initialize	:	function(params) {
		if (typeof params !== "undefined") Object.extend(this.params, params);
		
		var element = document.createElement("DIV");
		element.className = this.params.className;
		element.id = this.params.id;

		this.element = element;
	},
	
	show	:	function(params) {
		//explicitly set the height of the veil to the height of the whole document. Used to simulate fixed positioning for ie6
		if (browser == "ie6" && typeof params !== "undefined") {
			this.element.style.height = params.scrollHeight + "px";
		}
		
		$(document.body).insert({top : this.element});
	},
	
	destroy	:	function() {
		$(this.element).remove();
	},
	
	setIndex	:	function(index) {
		this.element.style.zIndex = index;
	}
});

/**
 * popup panel management functions. Handles showing, hiding, positioning and stacking of floating panels
 */
PanelManager = function(){}
//Add a modal panel to the stack. Called by panel.show
PanelManager.push = function(panel) {
	
	panel.setLevel(PanelManager.topLevel()+1);
	PanelManager.stack.push(panel);
	PanelManager.stack[panel.getId()] = panel;
	
	var dims =PanelManager.getDimensions();
	
	if (panel.params.modal) {
		if (PanelManager.veil == null) {
			PanelManager.veil = new db.Veil();
			PanelManager.veil.show(dims);
		}
		PanelManager.veil.setIndex(PanelManager.getZindex()-5);
	}
	
	var pos = {};
	pos[panel.params.insertPosition] = panel.element;
	$(panel.params.insertRef).insert(pos);
	
	//If no specific position has been specified then make a best guess
	if (panel.params.autoPosition.y && panel.params.position.y == null) {
		var margin = parseFloat($(panel.element).getStyle("marginTop"));
		var top = dims.scrollTop + (dims.docHeight * (panel.params.offset.y || 0.5));	
		//If the panel is positioned off-screen (after top margin has been calculated) then reset it's top position to counteract the margin
		if (margin < 0 && top + margin < 0) {
			top = Math.abs(margin);
		}
		panel.element.style.top =  top + "px";
	}
	
	panel.element.style.zIndex = PanelManager.getZindex();
	panel.autoMargins();
	panel.element.style.visibility = "visible";
	panel.shown = true;
}

/** Remove the top panel from the stack.
 * 
 * @deprecated - remove(panel) offers a greater level of control over panel removal;
 */
PanelManager.pop = function() {
	if(PanelManager.stack.length>0) return PanelManager._splice(PanelManager.stack[PanelManager.stack.length-1]);
	return false;
}
/**Removes the given element. If it is modal then it will only be removed if it is the top modal element on the stack.
*/
PanelManager.remove = function(panel) {
	if (!panel.params.modal || PanelManager.getTopModal() == panel) {
		PanelManager._splice(panel);
		return true;
	}
	return false;
}

/** Remove a panel from any position in the stack. Should never be called directly as provides no
 * 	checking that the panel is allowed to be removed.
 */
PanelManager._splice = function(panel) {
	var index = PanelManager.stack.indexOf(panel);
	PanelManager.stack.splice(index,1);
	PanelManager.stack[panel.getId()] = null;
	
	$(panel.element).remove();
	panel.shown = false;
	
	if (!PanelManager.veilRequired()){
		if (PanelManager.veil != null) {
			PanelManager.veil.destroy();
			PanelManager.veil = null;
		}
	}else if(panel.params.modal){
		PanelManager.veil.setIndex(PanelManager.getZindex());
	}
	
	return panel;
}

 /** If there are any modal panels on the stack then we need a veil
  */
PanelManager.veilRequired = function() {
	if (PanelManager.veil != null) {
		return $(PanelManager.stack).pluck("params").pluck("modal").any();
	}
	return false;
}

/** Calculate the next zIndex offset in the stack
 */
PanelManager.getZindex = function() {
	return PanelManager.getTopModal()==null ? 100 : (parseInt(PanelManager.getTopModal().getId().substring(6)) + 1) * 100;
}
 
/** Return the first pnale on the stack which is modal
 */
PanelManager.getTopModal = function() {
	var panel;
	for(var i = PanelManager.stack.length-1 ; i >= 0; i--) {
		var p = PanelManager.stack[i];
		if (p.params.modal) {
			panel = p;
			break;
		}
	}
	return panel;
}

/**Calculate a few useful dimensions:
 */
PanelManager.getDimensions = function() {
	var ie=document.all && !window.opera
	var dims = {};
	var ie=document.all && !window.opera;
	//var domclientWidth=document.documentElement && parseInt(document.documentElement.clientWidth) || 100000; //Preliminary doc width in non IE browsers
	var standardbody=(document.compatMode=="CSS1Compat")? document.documentElement : document.body; //create reference to common "body" across doctypes
	dims.scrollTop = (ie)? standardbody.scrollTop : window.pageYOffset;
	//dims.scrollLeft = (ie)? standardbody.scrollLeft : window.pageXOffset;
	//dims.docWidth = (ie)? standardbody.clientWidth : (/Safari/i.test(navigator.userAgent))? window.innerWidth : Math.min(domclientWidth, window.innerWidth-16);
	dims.docHeight = (ie)? standardbody.clientHeight: window.innerHeight;
	dims.scrollHeight = Math.max(standardbody.scrollHeight,standardbody.offsetHeight);
	
	if (DEBUG) {
		console.info("dims : ",dims);
	}
	return dims;
}
PanelManager.get = function(id) {
	return PanelManager.stack[id];
}

//get the current top stacking index 
PanelManager.topLevel = function() {
	return PanelManager.stack.length ? parseInt($A(PanelManager.stack).last().getLevel()) : 0;
}
PanelManager.stack = [];