/*
 * CLASS: X.Layer
 * 
 * A base layer class.
 * 
 * Layer needs the following configuration parameters:
 * 
 * content						[required]	(DOMElement|string) DOM element to wrap, - OR - HTML content of layer
 *
 * settings.id					[optional]	(string) Element id
 * settings.fixed				[optional]	(boolean) If the layer is position:fixed or not. Default = false
 * settings.x					[optional]	(number) X position of modal. Default = null (centered horizontally)
 * settings.y					[optional]	(number) Y position of modal. Default = null (centered vertically)
 * settings.resizeToFit			[optional]	(boolean) If the layer does not fit in the window should we resize it to fit? Default = false
 * settings.classNames			[optional]	(string) Element class names.
 * settings.wrapperClasses		[optional]	(array) An array of classes to be applied to the wrapper. Default = []
 *
 * EVENTS
 * settings.beforeCreateHandler	[optional]	(function) Function to be called before layer is created
 * settings.createHandler		[optional]	(function) Function to be called after layer is created
 * settings.beforeShowHandler	[optional]	(function) Function to be called before layer is shown
 * settings.showHandler			[optional]	(function) Function to be called after layer is shown
 * settings.beforeHideHandler	[optional]	(function) Function to be called before layer is hidden
 * settings.hideHandler			[optional]	(function) Function to be called after layer is hidden
 */
X.createClass('X.Layer',
	// Constructor
	function Layer(content, settings)
	{
		if (!content)
		{
			throw new Error('Missing required parameter: "content"');
		}
		
		if (!settings)
		{
			settings = {};
		}
		
		// Extend the settings with the defaults
		settings = jQuery.extend(true, {}, X.Layer.defaultSettings, settings);
		
		// Get the elmenent
		var $elm, id;
		if (typeof(content) === 'string')
		{
			// Determine the element's id and our selector
			id = (typeof(settings.id) === 'string' && settings.id.length > 0)? settings.id : '__layer_' + (++X.Layer.instanceCounter) + '__';
			this.selector = '#' + id;
			
			// Ensure the element has the layer class name
			if (!settings.classNames || settings.classNames.constructor !== Array)
			{
				settings.classNames = [];
			}
			settings.classNames.unshift('layer');
			
			// Create the element
			$elm = jQuery('<div id="' + id + '" class="' + settings.classNames.join(' ') + '">' + content + '</div>');
			
			registeredSettings = X.Layer._settings[this.selector] || {};
			
			this.isDynamic = true;
		}
		else
		{
			// Wrap the element
			$elm = jQuery(content);
			
			// Determine the element's id and our selector
			id = content.id;
			if (!id)
			{
				id = (typeof(settings.id) === 'string' && settings.id.length > 0)? settings.id : ('__layer_' + (++X.Layer.instanceCounter) + '__');
				$elm.attr('id', id);
			}
			this.selector = '#' + id;
			
			// Ensure the element has the layer class name
			$elm.addClass('layer');
			
			settings.classNames = content.className.split(' ');
			
			// Because we have an element, lets check to see if it has a position set.
			// If so, and there were no properties specified, the use the position as the setting
			if (isNaN(settings.x))
			{
				var x = $elm.css('left');
				if (!isNaN(x.replace(/\D/g, '')))
				{
					settings.x = x;
				}
			}
			if (isNaN(settings.y))
			{
				var y = $elm.css('top');
				if (!isNaN(y.replace(/\D/g, '')))
				{
					settings.y = y;
				}
			}
			
			this.isDynamic = false;
		}
		// Store a reference to the layer in our static instances associative array
		X.Layer.instances[this.selector] = this;
		
		// Set our id
		settings.id = id;
		
		this.settings = settings;
		
		// Our variables - leave the settings intact, as these will change
		this._visible = false;
		this._x = this.settings.x;
		this._y = this.settings.y;
		
		// Initialize the Layer class (only happens once)
		this.constructor.init();
		
		// Call the before create handler
		this.settings.beforeCreateHandler(this);
		
		// Add the "layer" css class to the wrapper's css classes array
		this.settings.wrapperClasses.unshift('layer-wrap');
		
		// Wrap the element, append it to the document and bgiframe the wrapper
		var $wrap = jQuery('<div class="' + this.settings.wrapperClasses.join(' ') + '"></div>').append($elm).bgiframe().appendTo(document.body);
		
		// Ensure the element is visible
		$elm.css({ display: 'block', top: '0', left: '0', visibility: 'visible' });
		
		// if the position should be fixed, and we are not in IE, set the position to be fixed
		if (this.settings.fixed && !X.Layer.msie6)
		{
			$wrap.css('position', 'fixed');
		}
		
		// show the layer
		this._create();
		
		return this;
	},
	// Prototype Members
	{
		_create: function()
		{			
			// Call back to the createHandler (if any)
			this.settings.createHandler(this);
			
			this._sizeWrap();
			
			this.show();
		},
		
		_sizeWrap: function($wrap)
		{			
			$wrap = $wrap || jQuery(this.selector).parent();
			
			if (X.Layer.msie6)
			{
				$wrap.find('.containerRow').css({zoom:0}).append('<div class="clearfix"></div>');
			}
			
			var w = 0;
			$wrap.find('> *').each(function(idx)
			{
				var $child = jQuery(this);
				var _w = $child.outerWidth();				
				//find the width of the widest child element
				if (w < _w) { w = _w; }
			});
			
			// Set the width of the wrapper to the outer width of the element
			$wrap.css({ width: w+'px' });
		},
		
		show: function(pos)
		{
			if (this._visible) { return; }
			
			// Call the beforeHide handler
			this.settings.beforeShowHandler(this);
			
			// If an override position was passed in, let's use it
			if (pos)
			{
				if (typeof(pos.x) === 'number' || pos.x === null) { this._x = pos.x; }
				if (typeof(pos.y) === 'number' || pos.y === null) { this._y = pos.y; }
			}
			
			// Call the "real" show method
			this._show();
			
			return this;
		},
		
		hide: function()
		{
			if (!this._visible) { return; }
			
			// Call the beforeHide handler
			this.settings.beforeHideHandler(this);
			
			// We hide the element right away
			this._position(-10000, -10000);
			
			// Call the "real" hide method
			this._hide();
			
			return this;
		},
		
		// The "real" show method
		_show: function()
		{
			this._visible = true;
			
			// Position the element into the visible area
			this._position(this._x, this._y);
			
			// Call the show handler
			this.settings.showHandler(this);
		},
		
		// The "hide" show method
		_hide: function()
		{
			this._visible = false;
			
			// Call the hide handler
			this.settings.hideHandler(this);
			
			if (this.isDynamic)
			{
				delete X.Layer.instances[this.selector];
				jQuery(this.selector).parent().remove();
			}
		},
		
		_position: function(x, y)
		{
			var $wrap = jQuery(this.selector).parent();
			var $win = jQuery(window);
			if (typeof(x) !== 'number')
			{
				x = Math.round(($win.width() - $wrap.outerWidth()) / 2);
				//if (x < 0) { x = 0; }
			}
			if (typeof(y) !== 'number')
			{
				y = Math.round(($win.height() - $wrap.outerHeight()) / 2);
				if (y < 0) { y = 0; }
			}
			
			// TODO - determine if resizing is  
			if (this.settings.resizeToFit)
			{
				//if (x < 0)
			}
			
			if (this.settings.fixed && X.Layer.msie6)
			{
				x += $win.scrollLeft();
				y += $win.scrollTop();
			}
			
			$wrap.css({ top: y+'px', left: x+'px' });
		},
		
		_onResize: function(evt)
		{
			if (!this._visible) { return; }
			
			if ((this.settings.fixed && X.Layer.msie6) || this._x === null || this._y === null)
			{
				this._position(this._x, this._y);
			}
		},
		
		_onScroll: function(evt)
		{
			if (!this._visible) { return; }
			
			if (this.settings.fixed && X.Layer.msie6)
			{
				this._position(this._x, this._y);
			}
		}
	},
	// Static Members
	{
		instanceCounter: 0,
		instances: {},
		_initialized: false,
		_settings: {},
		msie6: (jQuery.browser.msie && jQuery.browser.version.charAt(0) === '6'),
		
		init: function()
		{
			if (X.Layer._initialized) { return; }
			X.Layer._initialized = true;
			
			jQuery(window)
				.wresize(X.Layer._onResize)
				.scroll(X.Layer._onScroll);
		},
		
		get: function(selector)
		{
			return X.Layer.instances[selector] || null;//{ hide: function(){}, show: function(){} };
		},
		
		defaultSettings:
		{
			fixed: false,
			x: null,
			y: null,
			resizeToFit: false,
			classNames: [],
			wrapperClasses: [],
			beforeCreateHandler: function(){},
			createHandler: function(){},
			beforeShowHandler: function(){},
			showHandler: function(){},
			beforeHideHandler: function(){},
			hideHandler: function(){}
		},
		
		getSettings: function(selector)
		{
			return X.Layer._settings[selector] || X.Layer.defaultSettings;
		},
		
		registerSettings: function(selector, settings)
		{
			X.Layer._settings[selector] = jQuery.extend(true, {}, X.Layer.defaultSettings, settings);
		},
		
		_onResize: function(evt)
		{
			for (var selector in X.Layer.instances)
			{
				X.Layer.instances[selector]._onResize(evt);
			}
		},
		
		_onScroll: function(evt)
		{
			for (var selector in X.Layer.instances)
			{
				X.Layer.instances[selector]._onScroll(evt);
			}
		}
	}
);

/*
 * CLASS: X.Modal
 * 
 * A modal layer class.
 * 
 * Modal needs the following configuration parameters:
 * 
 * content						[required]	(DOMElement|string) DOM element to wrap, - OR - HTML content of layer
 *
 * settings.id					[optional]	(string) Element id
 * settings.fixed				[optional]	(boolean) If the layer is position:fixed or not. Default = false
 * settings.x					[optional]	(number) X position of modal. Default = null (centered horizontally)
 * settings.y					[optional]	(number) Y position of modal. Default = null (centered vertically)
 * settings.resizeToFit			[optional]	(boolean) If the layer does not fit in the window should we resize it to fit? Default = false
 * settings.classNames			[optional]	(string) Element class names.
 * settings.wrapperClasses		[optional]	(array) An array of classes to be applied to the wrapper. Default = []
 *
 * settings.header				[optional]	(object) 
 * 												html	[required]	(string) HTML text that will be prepended to the layer's wrapper element
 * 												actions	[options]	(array) Array of objects which represent element event actions
 * 													[0] { query: '', type: '', handler: '', closes: false }
 * settings.footer				[optional]	(object) 
 * 												html	[required]	(string) HTML text that will be appended to the layer's wrapper element
 * 												actions	[options]	(array) Array of objects which represent element event actions
 * 													[0] { query: '', type: '', handler: '', closes: false }
 *
 * EVENTS
 * settings.beforeCreateHandler	[optional]	(function) Function to be called before modal is created
 * settings.createHandler		[optional]	(function) Function to be called after modal is created
 * settings.beforeShowHandler	[optional]	(function) Function to be called before modal is shown
 * settings.showHandler			[optional]	(function) Function to be called after modal is shown
 * settings.beforeHideHandler	[optional]	(function) Function to be called before modal is hidden
 * settings.hideHandler			[optional]	(function) Function to be called after modal is hidden
 */
X.createClass('X.Modal',
	// Constructor
	function Modal(content, settings)
	{
		if (!settings)
		{
			settings = {};
		}
		
		// Ensure the element has the layer class name
		if (!settings.classNames || settings.classNames.constructor !== Array)
		{
			settings.classNames = [];
		}
		settings.classNames.unshift('modal containerRow');
		
		// Ensure the element has the layer class name
		if (!settings.wrapperClasses || settings.wrapperClasses.constructor !== Array)
		{
			settings.wrapperClasses = [];
		}
		settings.wrapperClasses.unshift('modal-wrap containerRow');
		
		// Call the base class
		this.base(content, settings);
		
		return this;
	},
	// Prototype Members
	{
		_create: function()
		{
			// Extend the settings with the defaults
			this.settings = jQuery.extend(true, {}, X.Modal.defaultSettings, this.settings);
			//this.settings.header = jQuery.extend(true, {}, X.Modal.defaultSettings.header, this.settings.header);
			//this.settings.footer = jQuery.extend(true, {}, X.Modal.defaultSettings.footer, this.settings.footer);
			
			var $wrap = jQuery(this.selector).parent();
			var modal = this;
			// Add header html (if any)
			if (this.settings.header.html.length > 0)
			{
				$wrap.prepend('<div class="modal-header containerRow">' + this.settings.header.html + '</div>');
				for (var idx = 0, actions = this.settings.header.actions, len = actions.length; idx < len; idx++)
				{
					this._createActionDelegate('header', actions[idx]);
				}
			}
			// Add footer html (if any)
			if (this.settings.footer.html.length > 0)
			{
				$wrap.append('<div class="modal-footer containerRow">' + this.settings.footer.html + '</div>');
				for (var idx = 0, actions = this.settings.footer.actions, len = actions.length; idx < len; idx++)
				{
					this._createActionDelegate('footer', actions[idx]);
				}
			}
			
			this._sizeWrap($wrap);
			
			// Call back to the createHandler (if any)
			this.settings.createHandler(this);
			
			this.show();
		},
		
		_createActionDelegate: function(location, action)
		{
			var type = action.type || 'click';
			var $elm = jQuery(this.selector).siblings('.modal-' + location).find(action.query);
			if ($elm.length === 0) { return; }
			
			var elm = $elm.get(0);
			jQuery(elm).bind(type, this._actionHandler.delegate(this, elm, action.closes, action.handler));
		},
		
		_actionHandler: function(evt, elm, closes, handler)
		{
			evt.preventDefault();
			if (closes) { this.hide(); }
			if (typeof(handler) === 'function') { handler(evt, elm, this); }
		},
		
		show: function(pos)
		{
			if (this._visible) { return; }
			
			this.settings.beforeShowHandler(this);
			
			if (pos)
			{
				if (typeof(pos.x) === 'number' || pos.x === null) { this._x = pos.x; }
				if (typeof(pos.y) === 'number' || pos.y === null) { this._y = pos.y; }
			}
			
			X.Modal.modalCover(true, this);
			
			return this;
		},
		
		hide: function()
		{
			if (!this._visible) { return; }
			
			this.settings.beforeHideHandler(this);
			
			this._position(-10000, -10000);
			
			X.Modal.modalCover(false, this);
			
			return this;
		}
	},
	// Static Members
	{
		_initialized: false,
		defaultSettings:
		{
			header: { html: '', actions: [] }, //{ query: '', type: 'click', handler: function(evt){} }
			footer: { html: '', actions: [] }
		},
		
		init: function()
		{
			if (X.Modal._initialized) { return; }
			X.Modal._initialized = true;
			
			// Base class init
			X.Layer.init();
			
			// Set up the cover
			$cover = jQuery('<div id="layers-modal-cover"></div>').appendTo(document.body).bgiframe();
			
			var styles = { opacity: 0.7 };
			if (X.Layer.msie6)
			{
				styles.position = 'absolute';
				
				var $win = jQuery(window);
				jQuery('#layers-modal-cover').css({ top: $win.scrollTop()+'px', left: $win.scrollLeft()+'px' })
			}
			$cover.css(styles);
			
			jQuery(window)
				.wresize(X.Modal._onResize)
				.scroll(X.Modal._onScroll);
		},
		
		get: function(selector)
		{
			return X.Layer.get(selector);
		},
		
		getSettings: function(selector)
		{
			return X.Layer.getSettings(selector);
		},
		
		registerSettings: function(selector, settings)
		{
			X.Layer.registerSettings(selector, settings);
		},
		
		modalCover: function(show, modal)
		{
			if (show)
			{
				X.Modal.resizeCover();
				jQuery('#layers-modal-cover').fadeIn('fast', modal._show.delegate(modal));
			}
			else
			{
				jQuery('#layers-modal-cover').fadeOut('fast', modal._hide.delegate(modal));
			}
		},
		
		resizeCover: function()
		{
			if (!X.Modal._initialized || !X.Layer.msie6) { return; }
			
			var docElm = document.documentElement;
			jQuery('#layers-modal-cover').css({ width: docElm.clientWidth+'px', height: docElm.clientHeight+'px' });
		},
		
		
		_onResize: function(evt)
		{
			X.Modal.resizeCover();
		},
		
		_onScroll: function(evt)
		{
			if (X.Layer.msie6)
			{
				var $win = jQuery(window);
				jQuery('#layers-modal-cover').css({ top: $win.scrollTop()+'px', left: $win.scrollLeft()+'px' });
			}
		}
	},
	// Base Class
	X.Layer
);

/* ************************************
 * SINGLETON: ModalFactory
 */
X.createSingleton('X.ModalFactory',
	// Constructor
	function ModalFactory()
	{
		this.base();
		
		this.init();
		
		return this;
	},
	// Prototype Members
	{
		init: function()
		{
			// Loop through all of the elements in the page that have a modal-opener class and bind to the click event
			jQuery('.modal-opener').live('click', this._onClick);
		},
		
		create: function(content, settings)
		{
			return new X.Modal(content, settings);
		},
		
		_autoCreate: function(selector)
		{
			var $elm = jQuery(selector);
			if ($elm.length === 0) { return; }
			
			var settings = X.Modal.getSettings(selector);
			$elm.modal(settings);
		},
		
		_onClick: function(evt)
		{
			evt.preventDefault();
			
			var id = this.name;
			if (!id) { return; }
			
			var selector = '#' + id;
			var modal = X.Modal.get(selector);
			if (modal)
			{
				modal.show();
				return;
			}
			
			X.ModalFactory._autoCreate(selector);
		}
	},
	// Base Class
	X.EventDispatcher
);

// --------------------------
jQuery.fn.layer = function layer(settings)
{
	new X.Layer(this.get(0), settings);
	return this;
};

jQuery.fn.modal = function modal(settings)
{
	new X.Modal(this.get(0), settings);
	return this;
};