/*

Requirements

- Base Markup
  -- Generate necessary markup on-the-fly, or
  -- base it off user provided jQuery-selector or
  -- specify a layout-url

- Modal content source
  -- Already loaded html markup (hidden DIV)
  -- AJAX
  -- iFrame

- Triggers
  -- links of certain classes
  -- CG.Events

Usage

* A. Bare Bones
	// Clicking this link will show the first defined dialog
	<a href="/modal/content/url" class="cg-modal">Click me</a>

	// Alternatively
	<a href="/modal/content/url" target="cg-modal">Click me</a>

* B. Specify a Template
	// Clicking this link will call /modal/content/url?type=swanky.
	// Cake will look for a layout prefixed modal_swanky.ctp
	<a href="/modal/content/url" class="cg-modal" data-cg-modal="{type: 'swanky'}">Click me</a>

* C. More options
	var m = new CG.Modal({
		type: 'coolDialog',
		width: 500,
		height: 400,
		srcType: 'ajax'||'iframe'||'dom',
	});

	// This link will show the dialog with the specified options
	<a href="/modal/content/url" class="cg-modal" data-cg-modal="{name: 'coolDialog'}">Click me</a>

	// This link overrides the construction options
	<a href="/modal/content/url" class="cg-modal" data-cg-modal="{name: 'coolDialog', width: 300, height: 200}">Click me</a>

	Q: Would it be too opaque to deduce srcType from the anchor HREF?
	   A fully qualified URL (http://...) assumes iFrame, an /absolute/path or relative/path assumes AJAX,
	   and a string (possible prefixed with a #) assumes DOM element ID

*/

/*
 * jqModal - Minimalist Modaling with jQuery
 *   (http://dev.iceburg.net/jquery/jqModal/)
 *
 * Copyright (c) 2007,2008 Brice Burgess <bhb@iceburg.net>
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 * 
 * $Version: 03/01/2009 +r14
 */
(function($) {
$.fn.jqm = function(options)
{
	var params = {
		overlay: 50,
		overlayClass: 'jqmOverlay',
		closeClass: 'jqmClose',
		trigger: '.jqModal',
		ajax: false,
		ajaxText: '',
		target: false,
		modal: false,
		toTop: false,
		onShow: false,
		onHide: false,
		onLoad: false
	};
	// For each element (aka "modal") $.jqm() has been called on;
	//  IF the _jqm expando exists, return
	//  ELSE increment serials and add _jqm expando to element
	// Passed-in options are always extended into the called on elements.
	return this.each(function()
	{
		if (this._jqm)
		{
			return H[this._jqm].config = $.extend({}, H[this._jqm].config, options);
		}
		serial++;
		this._jqm = serial;
	
		// ... Add this element's serial to the jqModal Hash Object 
		//  Hash is globally accessible via jQuery.jqm.hash. It consists of;
		//   confoig: {obj} config/options
		//   active: {bool} active state (true: active/visible, false: inactive/hidden)
		//   win: {JQ DOM Element} The modal element (window/dialog/notice/etc. container)
		//   serial: {int} The serial number of this modal (same as "H[s].w[0]._jqm")
		//   trigger: {DOM Element} The triggering element
		// *AND* ...
		H[serial] = {
			config: $.extend(params, $.jqm.params, options),
			active: false,
			win: $(this).addClass('jqmID' + serial),
			serial: serial
		};

		// ... Attach events to trigger showing of this modal
		if (params.trigger)
		{
			$(this).jqmAddTrigger(params.trigger);
		}
	});
};

/**
 * Triggers and Closes are typically added on the fly via CSS class selections when $.jqm() 
 * is called on element(s). They can also be manually added. e.g.
 *
 * $(e).jqmAddTrigger(triggers) will add a "trigger" to open (show) dialog(s) attached to e, and
 * $(e).jqmAddClose(closes) will add a "close" to close (hide) the dialog(s) attached to e.
 */
$.fn.jqmAddClose=function(closes)
{
	return _addHideShowTriggers(this, closes, 'jqmHide');
};

/**
 * Adds behavior to triggering elements via the hide-show (HS) function.
 * Triggers and Closes are typically added on the fly via CSS class selections when $.jqm() is called on element(s).
 * They can also be manually added. e.g.
 *   $(e).jqmAddTrigger(triggers) will add a "trigger" to open (show) dialog(s) attached to e, and
 *   $(e).jqmAddClose(closes) will add a "close" to close (hide) the dialog(s) attached to e.
 */
$.fn.jqmAddTrigger=function(triggers)
{
	return _addHideShowTriggers(this, triggers, 'jqmShow');
};

/**
 * Show a modal
 */
$.fn.jqmShow=function(t)
{
	return this.each(function()
	{
		t = t || window.event;
		$.jqm.open(this._jqm, t);
	});
};

/**
 * Hide a modal
 */
$.fn.jqmHide=function(t)
{
	return this.each(function()
	{
		t = t || window.event;
		$.jqm.close(this._jqm, t)
	});
};


$.jqm = {
	hash: {},

	/**
	 * Executed by $.jqmShow to show a modal
	 */
	open: function(serial, trigger)
	{
		// set local shortcuts
		//  hash:obj this Modal's "hash"
		//  config:obj (hash.config) config/options
		//  cc:str closing class ('.'+h.c.closeClass)
		//  z:int z-Index of Modal. If the Modal Dialog (hash.win) has the z-index style set it will use this value before defaulting to the one passed in the config (hash.config.zIndex)
		//  overlay: The overlay object
		var hash = H[serial],
			config = hash.config,
			cc = '.'+config.closeClass,
			z = (parseInt(hash.win.css('z-index'))),
			z = (z>0) ? z : 3000,
			overlay = $('<div></div>').css(
				{
					height:		'100%',
					width:		'100%',
					position:	'fixed',
					left:		0,
					top:		0,
					'z-index':	z-1,
					opacity:	config.overlay/100
				});

		// If this dialog is active, do nothing
		if (hash.active)
		{
			return false;
		}
		// mark this modal as active (hash.active === true)
		hash.active = true;
		// set the triggering object (hash.trigger) and the modal's z-Index.
		hash.trigger = trigger;
		hash.win.css('z-index', z);

		// IF the modal argument was passed as true (this is a true modal window);
		//    Bind the Keep Focus Function if no other Modals are open (!A[0]),
		//    Add this modal to the opened modals stack (A) for nested modal support,
		if (config.modal)
		{
			if (!A[0])
			{
				_keepFocus('bind');
			}
			A.push(serial);
		}

		// ELSE IF an overlay was requested (translucency set greater than 0);
		//    Attach a Close event to overlay to hide modal when overlay is clicked.
		else if (config.overlay > 0)
		{
			hash.win.jqmAddClose(overlay);
		}
		else
		{
			overlay = false;
		}
		// Add the Overlay to BODY if not disabled.
		hash.overlay = (overlay) ? overlay.addClass(config.overlayClass).prependTo('body') : false;

		// IF Internet Explorer 6
		// Set the Overlay to 100% height/width, and fix-position it via JS workaround
		try {
			if (ie6)
			{
				$('html,body').css({height:'100%',width:'100%'});
				if (overlay)
				{
					overlay = overlay.css({position:'absolute'})[0];
					for (var y in {Top:1,Left:1})
					{
						overlay.style.setExpression(y.toLowerCase(), "(_=(document.documentElement.scroll"+y+" || document.body.scroll"+y+"))+'px'");
					}
				}
			}
		}
		catch(ex) {
			console.log(ex);
		}

		// IF the modal's content is to be loaded via ajax;
		//  determine the target element to recieve content (targ:jq),
		//  determine the URL to load content from (url:str)
		if (config.ajax)
		{
			var targ = config.target || hash.win,
				url = config.ajax,
				targ = (typeof targ == 'string') ? $(targ, hash.win) : $(targ),
				// Should the URL be pulled from a trigger attribute?
				url = (url.substr(0,1) == '@') ? $(trigger).attr(url.substring(1)) : url;

			// Insert "loading..." message (ajaxText) in target
			// Load the Content (and once loaded);
			// Fire the onLoad callback (if exists),
			// Attach closing events to elements inside the modal that match the closingClass,
			// and Execute the jqModal default Open Callback
			targ.html(config.ajaxText).load(url, function()
			{
				// Call custom onLoad handler, if defined.
				// Passes the hash. The "this" scope is a reference to the ajax target as a DOM element.
				if (config.onLoad)
				{
					config.onLoad.call(this, hash);
				}
				if (cc)
				{
					hash.win.jqmAddClose($(cc, hash.win));
				}
				_open(hash);
			});
		}
		// ELSE the modal content is NOT to be loaded via ajax;
		//  Attach closing events to elements inside the modal that match the closingClass
		else if (cc)
		{
			hash.win.jqmAddClose($(cc, hash.win));
		}
		// IF toTop was passed and an overlay exists;
		//  Remember the DOM posistion of the modal by inserting a tagged (matching serial) <SPAN> before the modal
		//  Move the Modal from its current position to a first child of the body tag (after the overlay)
		if (config.toTop && hash.overlay)
		{
			hash.win.before('<span id="jqmP'+hash.win[0]._jqm+'"></span>').insertAfter(hash.overlay);
		}
		
		// Execute user defined onShow callback, or else show (make visible) the modal.
		// Execute the jqModal default Open Callback.
		// Return false to prevent trigger click from being followed.
		(config.onShow) ? config.onShow(hash) : hash.win.show();
		
		_open(hash);
		
		return false;
	},

	/**
	 * Executed by $.jqmHide to hide a modal
	 */
	close: function(s)
	{
		var hash = H[s];
		// If dialog is inactive, do nothing
		if (!hash.active)
		{
			return false;
		}
		// mark this modal as inactive
		hash.active = false;
		
		// remove from modal stack.
		if (A[0])
		{
			A.pop();
			// If no modals in modal stack, unbind the Keep Focus Function
			if (!A[0])
			{
				_keepFocus('unbind');
			}
		}
		// IF toTop was passed and an overlay exists;
		//  Move modal back to its previous ("remembered") position.		
		if (hash.config.toTop && hash.overlay)
		{
			$('#jqmP' + hash.win[0]._jqm).after(hash.win).remove();
		}

		// Execute user defined onHide callback, or else hide (make invisible) the modal and remove the overlay.
		if (hash.config.onHide)
		{
			hash.config.onHide(hash);
		}	
		else
		{
			hash.win.hide();
			if (hash.overlay)
			{
				hash.overlay.remove();
			}
		}
		
		return false;
	},
	params: {}
};

// set jqModal scope shortcuts;
//  serial: {INT} serials placeholder
//  H: {HASH} shortcut to jqModal Hash Object
//  A: {ARRAY} Array of active/visible modals
//  ie6: {bool} True if client browser is Internet Explorer 6
//  iFrame: {JQ, DOM Element} iframe placeholder used to prevent active-x bleedthrough in IE6
//    NOTE: It is important to include the iframe styling (iframe.jqm) in your CSS!
//     *AND* ...
var serial = 0,
	// Collection of all dialogs' hashes
	H = $.jqm.hash,
	// Modal array - holds the current focused modal. if empty, no modal dialogs visible.
	A = [],
	ie6 = $.browser.msie && ($.browser.version == "6.0"),
	// iFrame for ie6
	iFrame = $('<iframe src="javascript:false;document.write(\'\');" class="jqm"></iframe>').css({opacity:0}),

	// -----------------
	// Private Functions
	// -----------------
	
	/**
	 * The jqModal default Open Callback;
	 * IF ie6; Add the iframe to the overlay (if overlay exists) OR to the modal (if an iframe doesn't already exist from a previous opening)
	 * Execute the Modal Focus Function
	 */
	_open = function(hash)
	{
		if (ie6)
		{
			if (hash.overlay)
			{
				hash.overlay.html('<p style="width:100%;height:100%"/>').prepend(iFrame);
			}
			else if (!$('iframe.jqm', hash.w)[0])
			{
				hash.w.prepend(i);
			}
		}
		_modalFocus(hash);
	},
	
	/**
	 * hide-show function; assigns click events to trigger elements that 
	 * hide, show, or hide AND show modals.
	 * 
	 * Expandos (jqmShow and/or jqmHide) are added to all trigger elements. 
	 * These Expandos hold an array of modal serials {INT} to show or hide.
	 * 
	 * @param win: {DOM Element} The modal element (window/dialog/notice/etc. container)
	 * @param triggers: {DOM Element||jQ Selector String} The triggering element
	 * @param behavior: {String} Type (jqmHide||jqmShow)
	 * 
	 * NOTE: if jqmHide is passed, bindings will be scoped to window.
	 */
	_addHideShowTriggers = function(win, triggers, behavior)
	{
		return win.each(function()
		{
			var serial = this._jqm;

			// @todo add CG.Event trigger capability
			// A call to jqmShow() currently requires passing in a valid trigger scope.
			$(triggers).each(function()
			{
				if (!this[behavior])
				{
					this[behavior] = [];
					$(this).click(function()
					{
						for (var i in {jqmShow:1,jqmHide:1})
							for (var s in this[i])
								if (H[this[i][s]])
									H[this[i][s]].win[i](this);
						return false;
					});
				}
				this[behavior].push(serial);
			});
		});
	},
	
	/**
	 * Keep Focus Function;
	 * Binds or Unbinds (b) the Focus Examination Function to keypresses and clicks
	 * binds || unbinds the modal "focus function"
	 * @param b:str 'bind'||'unbind'
	 */
	_keepFocus = function(b)
	{
		$()[b]("keypress",_examineFocus)[b]("keydown",_examineFocus)[b]("mousedown",_examineFocus);
	},

	/**
	 * Focus Examination Function; - returns focus to a modal dialog (when bound)
	 * Fetch the current modal's Hash as h (supports nested modals)
	 * Determine if the click/press falls within the modal. If not (r===true);
	 *   call the Modal Focus Function and prevent click/press follow-through (return false [!true])
	 *   ELSE if so (r===false); follow event (return true [!false])	
	 */
	_examineFocus = function(e)
	{
		var hash = H[A[A.length-1]],
			win = (!$(e.target).parents('.jqmID'+hash.serial)[0]);
		if (win)
		{
			_modalFocus(hash);
		}
		return !win;
	},
	
	/**
	 * Modal Focus Function
	 * Attempt to focus the first visible input within the modal
	 */
	_modalFocus = function(hash)
	{
		try
		{
			$(':input:visible',hash.w)[0].focus();
		}catch(_){}
	};
})(jQuery);