/**
 * @author Ryan Johnson <http://syntacticx.com/>
 * @copyright 2008 PersonalGrid Corporation <http://personalgrid.com/>
 * @package LivePipe UI
 * @license MIT
 * @url http://livepipe.net/control/window
 * @require prototype.js, effects.js, draggable.js, resizable.js, livepipe.js
 */

//adds onDraw and constrainToViewport option to draggable
if(typeof(Draggable) != 'undefined'){
	//allows the point to be modified with an onDraw callback
	Draggable.prototype.draw = function(point) {
		var pos = Position.cumulativeOffset(this.element);
		if(this.options.ghosting) {
			var r = Position.realOffset(this.element);
			pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
		}
		
		var d = this.currentDelta();
		pos[0] -= d[0]; pos[1] -= d[1];
		
		if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
			pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
			pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
		}
		
		var p = [0,1].map(function(i){ 
			return (point[i]-pos[i]-this.offset[i]) 
		}.bind(this));
		
		if(this.options.snap) {
			if(typeof this.options.snap == 'function') {
				p = this.options.snap(p[0],p[1],this);
			} else {
				if(this.options.snap instanceof Array) {
					p = p.map( function(v, i) {return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
				} else {
					p = p.map( function(v) {return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
	  			}
			}
		}
		
		if(this.options.onDraw)
			this.options.onDraw.bind(this)(p);
		else{
			var style = this.element.style;
			if(this.options.constrainToViewport){
				var viewport_dimensions = document.viewport.getDimensions();
				var container_dimensions = this.element.getDimensions();
				var margin_top = parseInt(this.element.getStyle('margin-top'));
				var margin_left = parseInt(this.element.getStyle('margin-left'));
				var boundary = [[
					0 - margin_left,
					0 - margin_top
				],[
					(viewport_dimensions.width - container_dimensions.width) - margin_left,
					(viewport_dimensions.height - container_dimensions.height) - margin_top
				]];
				if((!this.options.constraint) || (this.options.constraint=='horizontal')){ 
					if((p[0] >= boundary[0][0]) && (p[0] <= boundary[1][0]))
						this.element.style.left = p[0] + "px";
					else
						this.element.style.left = ((p[0] < boundary[0][0]) ? boundary[0][0] : boundary[1][0]) + "px";
				} 
				if((!this.options.constraint) || (this.options.constraint=='vertical')){ 
					if((p[1] >= boundary[0][1] ) && (p[1] <= boundary[1][1]))
						this.element.style.top = p[1] + "px";
				  else
						this.element.style.top = ((p[1] <= boundary[0][1]) ? boundary[0][1] : boundary[1][1]) + "px";			   
				}
			}else{
				if((!this.options.constraint) || (this.options.constraint=='horizontal'))
				  style.left = p[0] + "px";
				if((!this.options.constraint) || (this.options.constraint=='vertical'))
				  style.top	 = p[1] + "px";
			}
			if(style.visibility=="hidden")
				style.visibility = ""; // fix gecko rendering
		}
	};
}

if(typeof(Prototype) == "undefined")
	throw "Control.Window requires Prototype to be loaded.";
if(typeof(IframeShim) == "undefined")
	throw "Control.Window requires IframeShim to be loaded.";
if(typeof(Object.Event) == "undefined")
	throw "Control.Window requires Object.Event to be loaded.";
/*
	known issues:
		- when iframe is clicked is does not gain focus
		- safari can't open multiple iframes properly
		- constrainToViewport: body must have no margin or padding for this to work properly
		- iframe will be mis positioned during fade in
		- document.viewport does not account for scrollbars (this will eventually be fixed in the prototype core)
	notes
		- setting constrainToViewport only works when the page is not scrollable
		- setting draggable: true will negate the effects of position: center
*/
Control.Window = Class.create({
	initialize: function(container,options){
		Control.Window.windows.push(this);
		
		//attribute initialization
		this.container = false;
		this.isOpen = false;
		this.href = false;
		this.sourceContainer = false; //this is optionally the container that will open the window
		this.ajaxRequest = false;
		this.remoteContentLoaded = false; //this is set when the code to load the remote content is run, onRemoteContentLoaded is fired when the connection is closed
		this.numberInSequence = Control.Window.windows.length + 1; //only useful for the effect scoping
		this.indicator = false;
		this.effects = {
			fade: false,
			appear: false
		};
		this.indicatorEffects = {
			fade: false,
			appear: false
		};
		
		//options
		this.options = Object.extend({
			//lifecycle
			beforeOpen: Prototype.emptyFunction,
			afterOpen: Prototype.emptyFunction,
			beforeClose: Prototype.emptyFunction,
			afterClose: Prototype.emptyFunction,
			//dimensions and modes
			height: null,
			width: null,
			className: false,
			position: 'center', //'center', 'relative', [x,y], [function(){return x;},function(){return y;}]
			offsetLeft: 0, //available only for anchors opening the window, or windows set to position: hover
			offsetTop: 0, //""
			iframe: false, //if the window has an href, this will display the href as an iframe instead of requesting the url as an an Ajax.Request
			hover: false, //element object to hover over, or if "true" only available for windows with sourceContainer (an anchor or any element already on the page with an href attribute)
			indicator: false, //element to show or hide when ajax requests, images and iframes are loading
			closeOnClick: false, //does not work with hover,can be: true (click anywhere), 'container' (will refer to this.container), or element (a specific element)
			iframeshim: true, //wether or not to position an iFrameShim underneath the window 
			//effects
			fade: false,
			fadeDuration: 0.75,
			//draggable
			draggable: false,
			onDrag: Prototype.emptyFunction,
			//resizable
			resizable: false,
			minHeight: false,
			minWidth: false,
			maxHeight: false,
			maxWidth: false,
			onResize: Prototype.emptyFunction,
			//draggable and resizable
			constrainToViewport: false,
			//ajax
			method: 'post',
			parameters: {},
			onComplete: Prototype.emptyFunction,
			onSuccess: Prototype.emptyFunction,
			onFailure: Prototype.emptyFunction,
			onException: Prototype.emptyFunction,
			//any element with an href (image,iframe,ajax) will call this after it is done loading
			onRemoteContentLoaded: Prototype.emptyFunction,
			insertRemoteContentAt: false //false will set this to this.container, can be string selector (first returned will be selected), or an Element that must be a child of this.container
		},options || {});
		
		//container setup
		this.indicator = this.options.indicator ? $(this.options.indicator) : false;
		if(container){
			if(typeof(container) == "string" && container.match(Control.Window.uriRegex))
				this.href = container;
			else{
				this.container = $(container);
				//need to create the container now for tooltips (or hover: element with no container already on the page)
				//second call made below will not create the container since the check is done inside createDefaultContainer()
				this.createDefaultContainer(container);
				//if an element with an href was passed in we use it to activate the window
				if(this.container && ((this.container.readAttribute('href') && this.container.readAttribute('href') != '') || (this.options.hover && this.options.hover !== true))){						
					if(this.options.hover && this.options.hover !== true)
						this.sourceContainer = $(this.options.hover);
					else{
						this.sourceContainer = this.container;
						this.href = this.container.readAttribute('href');
						var rel = this.href.match(/^#(.+)$/);
						if(rel && rel[1]){
							this.container = $(rel[1]);
							this.href = false;
						}else
							this.container = false;
					}
					//hover or click handling
					this.sourceContainerOpenHandler = function(event){
						this.open(event);
						event.stop();
						return false;
					}.bindAsEventListener(this);
					this.sourceContainerCloseHandler = function(event){
						this.close(event);
					}.bindAsEventListener(this);
					this.sourceContainerMouseMoveHandler = function(event){
						this.position(event);
					}.bindAsEventListener(this);
					if(this.options.hover){
						this.sourceContainer.observe('mouseenter',this.sourceContainerOpenHandler);
						this.sourceContainer.observe('mouseleave',this.sourceContainerCloseHandler);
						if(this.options.position == 'mouse')
							this.sourceContainer.observe('mousemove',this.sourceContainerMouseMoveHandler);
					}else
						this.sourceContainer.observe('click',this.sourceContainerOpenHandler);
				}
			}
		}
		this.createDefaultContainer(container);
		if(this.options.insertRemoteContentAt === false)
			this.options.insertRemoteContentAt = this.container;
		var styles = {
			margin: 0,
			position: (this.options.position == 'fixed' ? this.options.position : 'absolute'),
			zIndex: Control.Window.initialZIndexForWindow()
		};
		if(this.options.width)
			styles.width = $value(this.options.width) + 'px';
		if(this.options.height)
			styles.height = $value(this.options.height) + 'px';
		this.container.setStyle(styles);
		if(this.options.className)
			this.container.addClassName(this.options.className);
		this.positionHandler = this.position.bindAsEventListener(this);
		this.outOfBoundsPositionHandler = this.ensureInBounds.bindAsEventListener(this);
		this.bringToFrontHandler = this.bringToFront.bindAsEventListener(this);
		this.container.observe('mousedown',this.bringToFrontHandler);
		this.container.hide();
		this.closeHandler = this.close.bindAsEventListener(this);
		//iframeshim setup
		if(this.options.iframeshim){
			this.iFrameShim = new IframeShim();
			this.iFrameShim.hide();
		}
		//resizable support
		this.applyResizable();
		//draggable support
		this.applyDraggable();
		
		//makes sure the window can't go out of bounds
		Event.observe(window,'resize',this.outOfBoundsPositionHandler);
		
		this.notify('afterInitialize');
	},
	open: function(event){
		if(this.isOpen){
			this.bringToFront();
			return false;
		}
		if(this.notify('beforeOpen') === false)
			return false;
		//closeOnClick
		if(this.options.closeOnClick){
			if(this.options.closeOnClick === true)
				this.closeOnClickContainer = $(document.body);
			else if(this.options.closeOnClick == 'container')
				this.closeOnClickContainer = this.container;
			else if (this.options.closeOnClick == 'overlay'){
				Control.Overlay.load();
				this.closeOnClickContainer = Control.Overlay.container;
			}else
				this.closeOnClickContainer = $(this.options.closeOnClick);
			this.closeOnClickContainer.observe('click',this.closeHandler);
		}
		if(this.href && !this.options.iframe && !this.remoteContentLoaded){
			//link to image
			this.remoteContentLoaded = true;
			if(this.href.match(/\.(jpe?g|gif|png|tiff?)$/i)){
				var img = new Element('img');
				img.observe('load',function(img){
					this.getRemoteContentInsertionTarget().insert(img);
					this.position();
					if(this.notify('onRemoteContentLoaded') !== false){
						if(this.options.indicator)
							this.hideIndicator();
						this.finishOpen();
					}
				}.bind(this,img));
				img.writeAttribute('src',this.href);
			}else{
				//if this is an ajax window it will only open if the request is successful
				if(!this.ajaxRequest){
					if(this.options.indicator)
						this.showIndicator();
					this.ajaxRequest = new Ajax.Request(this.href,{
						method: this.options.method,
						parameters: this.options.parameters,
						onComplete: function(request){
							this.notify('onComplete',request);
							this.ajaxRequest = false;
						}.bind(this),
						onSuccess: function(request){
							this.getRemoteContentInsertionTarget().insert(request.responseText);
							this.notify('onSuccess',request);
							if(this.notify('onRemoteContentLoaded') !== false){
								if(this.options.indicator)
									this.hideIndicator();
								this.finishOpen();
							}
						}.bind(this),
						onFailure: function(request){
							this.notify('onFailure',request);
							if(this.options.indicator)
								this.hideIndicator();
						}.bind(this),
						onException: function(request,e){
							this.notify('onException',request,e);
							if(this.options.indicator)
								this.hideIndicator();
						}.bind(this)
					});
				}
			}
			return true;
		}else if(this.options.iframe && !this.remoteContentLoaded){
			//iframe
			this.remoteContentLoaded = true;
			if(this.options.indicator)
				this.showIndicator();
			this.getRemoteContentInsertionTarget().insert(Control.Window.iframeTemplate.evaluate({
				href: this.href
			}));
			var iframe = this.container.down('iframe');
			iframe.onload = function(){
				this.notify('onRemoteContentLoaded');
				if(this.options.indicator)
					this.hideIndicator();
				iframe.onload = null;
			}.bind(this);
		}
		this.finishOpen(event);
		return true
	},
	close: function(event){ //event may or may not be present
		if(!this.isOpen || this.notify('beforeClose',event) === false)
			return false;
		if(this.options.closeOnClick)
			this.closeOnClickContainer.stopObserving('click',this.closeHandler);
		if(this.options.fade){
			this.effects.fade = new Effect.Fade(this.container,{
				queue: {
					position: 'front',
					scope: 'Control.Window' + this.numberInSequence
				},
				from: 1,
				to: 0,
				duration: this.options.fadeDuration / 2,
				afterFinish: function(){
					if(this.iFrameShim)
						this.iFrameShim.hide();
					this.isOpen = false;
					this.notify('afterClose');
				}.bind(this)
			});
		}else{
			this.container.hide();
			if(this.iFrameShim)
				this.iFrameShim.hide();
		}
		if(this.ajaxRequest)
			this.ajaxRequest.transport.abort();
		if(!(this.options.draggable || this.options.resizable) && this.options.position == 'center')
			Event.stopObserving(window,'resize',this.positionHandler);
		if(!this.options.draggable && this.options.position == 'center')
			Event.stopObserving(window,'scroll',this.positionHandler);
		if(this.options.indicator)
			this.hideIndicator();
		if(!this.options.fade){
			this.isOpen = false;
			this.notify('afterClose');
        }
		return true;
	},
	position: function(event){
		//this is up top for performance reasons
		if(this.options.position == 'mouse'){
			var xy = [Event.pointerX(event),Event.pointerY(event)];
			this.container.setStyle({
				top: xy[1] + $value(this.options.offsetTop) + 'px',
				left: xy[0] + $value(this.options.offsetLeft) + 'px'
			});
			return;
		}
		var container_dimensions = this.container.getDimensions();
		var viewport_dimensions = document.viewport.getDimensions();
		Position.prepare();
		var offset_left = (Position.deltaX + Math.floor((viewport_dimensions.width - container_dimensions.width) / 2));
		var offset_top = (Position.deltaY + ((viewport_dimensions.height > container_dimensions.height) ? Math.floor((viewport_dimensions.height - container_dimensions.height) / 2) : 0));
		if(this.options.position == 'center'){
			this.container.setStyle({
				top: (container_dimensions.height <= viewport_dimensions.height) ? ((offset_top != null && offset_top > 0) ? offset_top : 0) + 'px' : 0,
				left: (container_dimensions.width <= viewport_dimensions.width) ? ((offset_left != null && offset_left > 0) ? offset_left : 0) + 'px' : 0
			});
		}else if(this.options.position == 'relative'){
			var xy = this.sourceContainer.cumulativeOffset();
			var top = xy[1] + $value(this.options.offsetTop);
			var left = xy[0] + $value(this.options.offsetLeft);
			this.container.setStyle({
				top: (container_dimensions.height <= viewport_dimensions.height) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.height - (container_dimensions.height),top)) : top) + 'px' : 0,
				left: (container_dimensions.width <= viewport_dimensions.width) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.width - (container_dimensions.width),left)) : left) + 'px' : 0
			});
		}else if(this.options.position == 'fixed'){
			this.container.setStyle({
				top: '10%',
				left: (container_dimensions.width <= viewport_dimensions.width) ? ((viewport_dimensions.width / 2)-(container_dimensions.width / 2)) + 'px' : 0
			});
		}else if(this.options.position.length){
			var top = $value(this.options.position[1]) + $value(this.options.offsetTop);
			var left = $value(this.options.position[0]) + $value(this.options.offsetLeft);
			this.container.setStyle({
				top: (container_dimensions.height <= viewport_dimensions.height) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.height - (container_dimensions.height),top)) : top) + 'px' : 0,
				left: (container_dimensions.width <= viewport_dimensions.width) ? (this.options.constrainToViewport ? Math.max(0,Math.min(viewport_dimensions.width - (container_dimensions.width),left)) : left) + 'px' : 0
			});
		}
		if(this.iFrameShim)
			this.updateIFrameShimZIndex();
	},
	ensureInBounds: function(){
		if(!this.isOpen)
			return;
		var viewport_dimensions = document.viewport.getDimensions();
		var container_offset = this.container.cumulativeOffset();
		var container_dimensions = this.container.getDimensions();
		if(this.options.position == 'fixed'){
			this.container.setStyle({
				left: (container_dimensions.width <= viewport_dimensions.width) ? ((viewport_dimensions.width / 2)-(container_dimensions.width / 2)) + 'px' : 0
			});
		}
		if(container_offset.left + container_dimensions.width > viewport_dimensions.width){
			this.container.setStyle({
				left: (Math.max(0,viewport_dimensions.width - container_dimensions.width)) + 'px'
			});
		}
		if(container_offset.top + container_dimensions.height > viewport_dimensions.height){
			this.container.setStyle({
				top: (Math.max(0,viewport_dimensions.height - container_dimensions.height)) + 'px'
			});
		}
	},
	bringToFront: function(){
		Control.Window.bringToFront(this);
		this.notify('bringToFront');
	},
	destroy: function(){
		this.container.stopObserving('mousedown',this.bringToFrontHandler);
		if(this.draggable){
			Resizables.removeObserver(this.container);
			this.draggable.handle.stopObserving('mousedown',this.bringToFrontHandler);
			this.draggable.destroy();
		}
		if(this.resizable){
			Resizables.removeObserver(this.container);
			this.resizable.handle.stopObserving('mousedown',this.bringToFrontHandler);
			this.resizable.destroy();
		}
		if(this.container && !this.sourceContainer)
			this.container.remove();
		if(this.sourceContainer){
			if(this.options.hover){
				this.sourceContainer.stopObserving('mouseenter',this.sourceContainerOpenHandler);
				this.sourceContainer.stopObserving('mouseleave',this.sourceContainerCloseHandler);
				if(this.options.position == 'mouse')
					this.sourceContainer.stopObserving('mousemove',this.sourceContainerMouseMoveHandler);
			}else
				this.sourceContainer.stopObserving('click',this.sourceContainerOpenHandler);
		}
		if(this.iFrameShim)
			this.iFrameShim.destroy();
		Event.stopObserving(window,'resize',this.outOfBoundsPositionHandler);
		Control.Window.windows = Control.Window.windows.without(this);
		this.notify('afterDestroy');
	},
	//private
	applyResizable: function(){
		if(this.options.resizable){
			if(typeof(Resizable) == "undefined")
				throw "Control.Window requires resizable.js to be loaded.";
			var resizable_handle = null;
			if(this.options.resizable === true){
				resizable_handle = new Element('div',{
					className: 'resizable_handle'
				});
				this.container.insert(resizable_handle);
			}else
				resizable_handle = $(this.options.resziable);
			this.resizable = new Resizable(this.container,{
				handle: resizable_handle,
				minHeight: this.options.minHeight,
				minWidth: this.options.minWidth,
				maxHeight: this.options.constrainToViewport ? function(element){
					//viewport height - top - total border height
					return (document.viewport.getDimensions().height - parseInt(element.style.top || 0)) - (element.getHeight() - parseInt(element.style.height || 0));
				} : this.options.maxHeight,
				maxWidth: this.options.constrainToViewport ? function(element){
					//viewport width - left - total border width
					return (document.viewport.getDimensions().width - parseInt(element.style.left || 0)) - (element.getWidth() - parseInt(element.style.width || 0));
				} : this.options.maxWidth
			});
			this.resizable.handle.observe('mousedown',this.bringToFrontHandler);
			Resizables.addObserver(new Control.Window.LayoutUpdateObserver(this,function(){
				if(this.iFrameShim)
					this.updateIFrameShimZIndex();
				this.notify('onResize');
			}.bind(this)));
		}
	},
	applyDraggable: function(){
		if(this.options.draggable){
			if(typeof(Draggables) == "undefined")
				throw "Control.Window requires dragdrop.js to be loaded.";
			var draggable_handle = null;
			if(this.options.draggable === true){
				draggable_handle = new Element('div',{
					className: 'draggable_handle'
				});
				this.container.insert(draggable_handle);
			}else
				draggable_handle = $(this.options.draggable);
			this.draggable = new Draggable(this.container,{
				handle: draggable_handle,
				constrainToViewport: this.options.constrainToViewport,
				zindex: this.container.getStyle('z-index'),
				starteffect: function(){
					if(Prototype.Browser.IE){
						this.old_onselectstart = document.onselectstart;
						document.onselectstart = function(){
							return false;
						};
					}
				}.bind(this),
				endeffect: function(){
					document.onselectstart = this.old_onselectstart;
				}.bind(this)
			});
			this.draggable.handle.observe('mousedown',this.bringToFrontHandler);
			Draggables.addObserver(new Control.Window.LayoutUpdateObserver(this,function(){
				if(this.iFrameShim)
					this.updateIFrameShimZIndex();
				this.notify('onDrag');
			}.bind(this)));
		}
	},
	createDefaultContainer: function(container){
		if(!this.container){
			//no container passed or found, create it
			this.container = new Element('div',{
				id: 'control_window_' + this.numberInSequence
			});
			$(document.body).insert(this.container);
			if(typeof(container) == "string" && $(container) == null && !container.match(/^#(.+)$/) && !container.match(Control.Window.uriRegex))
				this.container.update(container);
		}
	},
	finishOpen: function(event){
		this.bringToFront();
		if(this.options.fade){
			if(typeof(Effect) == "undefined")
				throw "Control.Window requires effects.js to be loaded."
			if(this.effects.fade)
				this.effects.fade.cancel();
			this.effects.appear = new Effect.Appear(this.container,{
				queue: {
					position: 'end',
					scope: 'Control.Window.' + this.numberInSequence
				},
				from: 0,
				to: 1,
				duration: this.options.fadeDuration / 2,
				afterFinish: function(){
					if(this.iFrameShim)
						this.updateIFrameShimZIndex();
					this.isOpen = true;
					this.container.setStyle({display: 'block'});
					this.notify('afterOpen');
				}.bind(this)
			});
		}else
			this.container.show();
		this.position(event);
		if(!(this.options.draggable || this.options.resizable) && this.options.position == 'center')
			Event.observe(window,'resize',this.positionHandler,false);
		if(!this.options.draggable && this.options.position == 'center')
			Event.observe(window,'scroll',this.positionHandler,false);
		if(!this.options.fade){
			this.isOpen = true;
			this.notify('afterOpen');
		}
		return true;
	},
	showIndicator: function(){
		this.showIndicatorTimeout = window.setTimeout(function(){
			if(this.options.fade){
				this.indicatorEffects.appear = new Effect.Appear(this.indicator,{
					queue: {
						position: 'front',
						scope: 'Control.Window.indicator.' + this.numberInSequence
					},
					from: 0,
					to: 1,
					duration: this.options.fadeDuration / 2
				});
			}else
				this.indicator.show();
		}.bind(this),Control.Window.indicatorTimeout);
	},
	hideIndicator: function(){
		if(this.showIndicatorTimeout)
			window.clearTimeout(this.showIndicatorTimeout);
		this.indicator.hide();
	},
	getRemoteContentInsertionTarget: function(){
		return typeof(this.options.insertRemoteContentAt) == "string" ? this.container.down(this.options.insertRemoteContentAt) : $(this.options.insertRemoteContentAt);
	},
	updateIFrameShimZIndex: function(){
		if(this.iFrameShim)
			this.iFrameShim.positionUnder(this.container);
	}
});
//class methods
Object.extend(Control.Window,{
	windows: [],
	baseZIndex: 9999,
	indicatorTimeout: 250,
	iframeTemplate: new Template('<iframe src="#{href}" width="100%" height="100%" frameborder="0"></iframe>'),
	uriRegex: /^(\/|\#|https?\:\/\/|[\w]+\/)/,
	bringToFront: function(w){
		Control.Window.windows = Control.Window.windows.without(w);
		Control.Window.windows.push(w);
		Control.Window.windows.each(function(w,i){
			var z_index = Control.Window.baseZIndex + i;
			w.container.setStyle({
				zIndex: z_index
			});
			if(w.isOpen){
				if(w.iFrameShim)
				w.updateIFrameShimZIndex();
			}
			if(w.options.draggable)
				w.draggable.options.zindex = z_index;
		});
	},
	open: function(container,options){
		var w = new Control.Window(container,options);
		w.open();
		return w;
	},
	//protected
	initialZIndexForWindow: function(w){
		return Control.Window.baseZIndex + (Control.Window.windows.length - 1);
	}
});
Object.Event.extend(Control.Window);

//this is the observer for both Resizables and Draggables
Control.Window.LayoutUpdateObserver = Class.create({
	initialize: function(w,observer){
		this.w = w;
		this.element = $(w.container);
		this.observer = observer;
	},
	onStart: Prototype.emptyFunction,
	onEnd: function(event_name,instance){
		if(instance.element == this.element && this.iFrameShim)
			this.w.updateIFrameShimZIndex();
	},
	onResize: function(event_name,instance){
		if(instance.element == this.element)
			this.observer(this.element);
	},
	onDrag: function(event_name,instance){
		if(instance.element == this.element)
			this.observer(this.element);
	}
});

//overlay for Control.Modal
Control.Overlay = {
	id: 'control_overlay',
	loaded: false,
	container: false,
	lastOpacity: 0,
	styles: {
		position: 'fixed',
		top: 0,
		left: 0,
		width: '100%',
		height: '100%',
		zIndex: 9998
	},
	ieStyles: {
		position: 'absolute',
		top: 0,
		left: 0,
		zIndex: 9998
	},
	effects: {
		fade: false,
		appear: false
	},
	load: function(){
		if(Control.Overlay.loaded)
			return false;
		Control.Overlay.loaded = true;
		Control.Overlay.container = new Element('div',{
			id: Control.Overlay.id
		});
		$(document.body).insert(Control.Overlay.container);
		if(Prototype.Browser.IE){
			Control.Overlay.container.setStyle(Control.Overlay.ieStyles);
			Event.observe(window,'scroll',Control.Overlay.positionOverlay);
			Event.observe(window,'resize',Control.Overlay.positionOverlay);
			Control.Overlay.observe('beforeShow',Control.Overlay.positionOverlay);
		}else
			Control.Overlay.container.setStyle(Control.Overlay.styles);
		Control.Overlay.iFrameShim = new IframeShim();
		Control.Overlay.iFrameShim.hide();
		Event.observe(window,'resize',Control.Overlay.positionIFrameShim);
		Control.Overlay.container.hide();
		return true;
	},
	unload: function(){
		if(!Control.Overlay.loaded)
			return false;
		Event.stopObserving(window,'resize',Control.Overlay.positionOverlay);
		Control.Overlay.stopObserving('beforeShow',Control.Overlay.positionOverlay);
		Event.stopObserving(window,'resize',Control.Overlay.positionIFrameShim);
		Control.Overlay.iFrameShim.destroy();
		Control.Overlay.container.remove();
		Control.Overlay.loaded = false;
		return true;
	},
	show: function(opacity,fade){
		if(Control.Overlay.notify('beforeShow') === false)
			return false;
		Control.Overlay.lastOpacity = opacity;
		Control.Overlay.positionIFrameShim();
		Control.Overlay.iFrameShim.show();
		if(fade){
			if(typeof(Effect) == "undefined")
				throw "Control.Window requires effects.js to be loaded."
			if(Control.Overlay.effects.fade)
				Control.Overlay.effects.fade.cancel();
			Control.Overlay.effects.appear = new Effect.Appear(Control.Overlay.container,{
				queue: {
					position: 'end',
					scope: 'Control.Overlay'
				},
				afterFinish: function(){
					Control.Overlay.notify('afterShow');
				},
				from: 0,
				to: Control.Overlay.lastOpacity,
				duration: (fade === true ? 0.75 : fade) / 2
			});
		}else{
			Control.Overlay.container.setStyle({
				opacity: opacity || 1
			});
			Control.Overlay.container.show();
			Control.Overlay.notify('afterShow');
		}
		return true;
	},
	hide: function(fade){
		if(Control.Overlay.notify('beforeHide') === false)
			return false;
		if(Control.Overlay.effects.appear)
			Control.Overlay.effects.appear.cancel();
		Control.Overlay.iFrameShim.hide();
		if(fade){
			Control.Overlay.effects.fade = new Effect.Fade(Control.Overlay.container,{
				queue: {
					position: 'front',
					scope: 'Control.Overlay'
				},
				afterFinish: function(){
					Control.Overlay.notify('afterHide');
				},
				from: Control.Overlay.lastOpacity,
				to: 0,
				duration: (fade === true ? 0.75 : fade) / 2
			});
		}else{
			Control.Overlay.container.hide();
			Control.Overlay.notify('afterHide');
		}
		return true;
	},
	positionIFrameShim: function(){
		if(Control.Overlay.container.visible())
			Control.Overlay.iFrameShim.positionUnder(Control.Overlay.container);
	},
	//IE only
	positionOverlay: function(){
		Control.Overlay.container.setStyle({
			width: document.body.clientWidth + 'px',
			height: document.body.clientHeight + 'px'
		});
	}
};
Object.Event.extend(Control.Overlay);

Control.ToolTip = Class.create(Control.Window,{
	initialize: function($super,container,tooltip,options){
		$super(tooltip,Object.extend(Object.extend(Object.clone(Control.ToolTip.defaultOptions),options || {}),{
			position: 'mouse',
			hover: container
		}));
	}
});
Object.extend(Control.ToolTip,{
	defaultOptions: {
		offsetLeft: 10
	}
});

Control.Modal = Class.create(Control.Window,{
	initialize: function($super,container,options){
		Control.Modal.InstanceMethods.beforeInitialize.bind(this)();
		$super(container,Object.extend(Object.clone(Control.Modal.defaultOptions),options || {}));
	}
});
Object.extend(Control.Modal,{
	defaultOptions: {
		overlayOpacity: 0.5,
		closeOnClick: 'overlay'
	},
	current: false,
	open: function(container,options){
		var modal = new Control.Modal(container,options);
		modal.open();
		return modal;
	},
	close: function(){
		if(Control.Modal.current)
			Control.Modal.current.close();
	},
	InstanceMethods: {
		beforeInitialize: function(){
			Control.Overlay.load();
			this.overlayFinishedOpening = false;
			this.observe('beforeOpen',Control.Modal.Observers.beforeOpen.bind(this));
			this.observe('afterOpen',Control.Modal.Observers.afterOpen.bind(this));
			this.observe('afterClose',Control.Modal.Observers.afterClose.bind(this));
		}
	},
	Observers: {
		beforeOpen: function(){
			if(!this.overlayFinishedOpening){
				Control.Overlay.observeOnce('afterShow',function(){
					this.overlayFinishedOpening = true;
					this.open();
				}.bind(this));
				Control.Overlay.show(this.options.overlayOpacity,this.options.fade ? this.options.fadeDuration : false);
				throw $break;
			}else
			Control.Window.windows.without(this).invoke('close');
		},
		afterOpen: function(){
			Control.Modal.current = this;
		},
		afterClose: function(){
			Control.Overlay.hide(this.options.fade ? this.options.fadeDuration : false);
			Control.Modal.current = false;
			this.overlayFinishedOpening = false;
		}
	}
});

Control.LightBox = Class.create(Control.Window,{
	initialize: function($super,container,options){
		this.allImagesLoaded = false;
		if(options.modal){
			var options = Object.extend(Object.clone(Control.LightBox.defaultOptions),options || {});
			options = Object.extend(Object.clone(Control.Modal.defaultOptions),options);
			options = Control.Modal.InstanceMethods.beforeInitialize.bind(this)(options);
			$super(container,options);
		}else
			$super(container,Object.extend(Object.clone(Control.LightBox.defaultOptions),options || {}));
		this.hasRemoteContent = this.href && !this.options.iframe;
		if(this.hasRemoteContent)
			this.observe('onRemoteContentLoaded',Control.LightBox.Observers.onRemoteContentLoaded.bind(this));
		else
			this.applyImageObservers();
		this.observe('beforeOpen',Control.LightBox.Observers.beforeOpen.bind(this));
	},
	applyImageObservers:function(){
		var images = this.getImages();
		this.numberImagesToLoad = images.length;
		this.numberofImagesLoaded = 0;
		images.each(function(image){
			image.observe('load',function(image){
				++this.numberofImagesLoaded;
				if(this.numberImagesToLoad == this.numberofImagesLoaded){
					this.allImagesLoaded = true;
					this.onAllImagesLoaded();
				}
			}.bind(this,image));
			image.hide();
		}.bind(this));
	},
	onAllImagesLoaded: function(){
		this.getImages().each(function(image){
			this.showImage(image);
		}.bind(this));
		if(this.hasRemoteContent){
			if(this.options.indicator)
				this.hideIndicator();
			this.finishOpen();
		}else
			this.open();
	},
	getImages: function(){
		return this.container.select(Control.LightBox.imageSelector);
	},
	showImage: function(image){
		image.show();
	}
});
Object.extend(Control.LightBox,{
	imageSelector: 'img',
	defaultOptions: {},
	Observers: {
		beforeOpen: function(){
			if(!this.hasRemoteContent && !this.allImagesLoaded)
				throw $break;
		},
		onRemoteContentLoaded: function(){
			this.applyImageObservers();
			if(!this.allImagesLoaded)
				throw $break;
		}
	}
});