/**
 * Challenge Games wrapper library for jqModal
 * CG.Modal is Copyright (c) 2009 Challenge Online Games inc.
 *
 * jqModal is Copyright (c) 2007,2008 Brice Burgess <bhb@iceburg.net>
 * 
 * @require jQuery 1.3 (1.2 without the benefit of live event listeners)
 * @require /vendors/js/libs/jquery.jqmodal-dev.js <- has modified variable names to be readable, hash.win, hash.config, hash.overlay
 * @require CG.Event
 * @require CG.Util
 *
 * @example
 * <a class="cg-modal" href="/modal/content" data-cg-modal="{modal: true}">Open Modal Dialog</a>
 *
 * If using jQuery 1.2, fire CG.events.updateModalClickListener when updating content
 */

require(['jQuery', 'jQuery.fn.jqm', 'jQuery.fn.getData', 'CG', 'CG.Event'], 'CG.Modal');

(function($) {
	
	CG.options = CG.options ? CG.options : {};
	CG.options.Modal = CG.options.Modal ? CG.options.Modal : {};
	/**
	 * Variables declared in thie scope are not accessible from outside the API.
	 */
	
		/**
		 * Default options
		 */
	var defaultOptions = $.extend({
			top: 0,						// the position of the modal from the top of the viewport
			left: 0,					// the position of the modal from the left of the viewport
			width: 0,					// the width of the modal
			height: 0,					// the height of the modal
			srcType: 'ajax',			// [iframe|ajax|dom]
			overlay: 50,				// opacity (%) of overlay, or false for no overlay
			modal: false,				// [true, false] - If false, clicking on the overlay will close the modal
			layout: false,				// Dom element to use as the wrapper
			target: 'new',				// [new|current] - Whether to replace the contents of a current modal or create a new one
			closeClass: 'cg-modal-close',
			triggerClass: 'cg-modal',
			containerClass: 'cg-modal-container',
			linkFilter: function(url) { return url; },
			queue: false				// whether or not to queue modals opening
		}, CG.options.Modal),
		
		/**
		 *
		 */
		modals = [],
		
		modalsQueue = defaultOptions.queue ? new CG.Queue() : null,

		/**
		 * Creates a modal upon clicking of an anchor with class triggerClass
		 */
		onModalClick = function() {
			var	$trigger = $(this),
				options   = $trigger.getData('cg-modal'),
				src, instance;
			options = (typeof options !== 'undefined') ? options : {};

			// For 'ajax' and 'iframe' srcTypes, we use the triggering link's href as src
			if (typeof options.srcType === 'undefined' || options.srcType !== 'dom') {
				src = $trigger.attr('href');
			// For 'dom' srcType, a field src needs to be set to a DOM element or an element id string.
			} else {
				if (typeof options.src === 'undefined' || !(typeof options.src === 'string' || typeof options.src === 'object')) {
					throw {name: 'sourceElementNotSpecifiedException', message: 'An element id must be specified as a String "src" in the options field when using "dom" srcType.'};
				}
				src = options.src;
			}

			new CG.Modal(src, options);
			return false;
		},
		
		/**
		 * Triggered by jqModal on modal open trigger
		 * Shows the modal element
		 * 
		 * @fire CG.Modal.events.open 
		 */
		onModalShow = function(hash) {
			var modal = CG.Modal.getModal(hash.win[0]._jqm);
			if (hash.config.srcType == 'iframe') {
				// Append iframe element with the configured source
				iframehtml = '<iframe width="100%" height="100%" src="' + modal._src + '" frameborder="0"></iframe>';
				hash.win.prepend(iframehtml);
				// Fire load event when content is loaded
				hash.win.find('iframe').load(function() {CG.Modal.events.load.fire();});
				// Wrap iframe in layout if layout was specified
				// element to be wrapped needs to be in the DOM before wrapping
				hash.win.show();
				if (hash.config.layout !== false) {
					$('iframe', hash.win).wrap(hash.config.layout);
				}
			} else if (hash.config.srcType == 'dom') {
				// Append content
				hash.win.append($(modal._src).show().addClass('cg-modal-source'));
				// Wrap layout around content, if specified
				if (hash.config.layout !== false) {
					hash.win.wrapInner(hash.config.layout);
					hash.config.layout.style.visibility = 'visible';
				}
				// Manually add closeClass, since jqModal only adds existing closeClasses in $.fn.jqm()
				hash.win.jqmAddClose($('.'+hash.config.closeClass, hash.win));
				// Show modal
				hash.win.show();
				// Fire load event for DOM source
				CG.Modal.events.load.fire(modal);
			} else {
				hash.win.show();
			}
			CG.Modal.events.open.fire(modal);
		},
		
		/**
		 * Triggered by jqModal on AJAX modal load complete
		 * 
		 * @fire CG.Modal.events.load 
		 */
		onModalLoad = function(hash) {
			var modal = CG.Modal.getModal(hash.win[0]._jqm);
			CG.Modal.events.load.fire(modal);
		},
		
		/**
		 * Triggered by jqModal on modal close trigger
		 * Hides the modal element and the overlay
		 * Destroys the CG.Modal instance
		 * 
		 * @fire CG.Modal.events.close 
		 */
		onModalClose = function(hash) {
			var modal = CG.Modal.getModal(hash.win[0]._jqm);
			
			hash.win.hide();
			
			if (hash.overlay) {
				hash.overlay.remove();
			}
			
			modal._destroy();
			
			if (CG.options.Modal.queue) {
				( modalsQueue.next() || { "show": function(){} } ).show();
			}
			
			CG.Modal.events.close.fire(modal);
		},

		/**
		 * Initial processing
		 */
		init = function() {
			// @todo There should be a check at construction whether className was changed
			if (typeof jQuery.fn.live === 'function') {
				// Add a live listener
				$('.' + defaultOptions.triggerClass).live('click', onModalClick);
			} else {
				// Define a function to be run on document ready and when a updateModalClickListener event is fired
				var handleModalClick = function() {
					$('.' + defaultOptions.triggerClass).bind('click', onModalClick);
				};
				CG.events.updateModalClickListener = new CG.Event();
				CG.events.updateModalClickListener.subscribe(handleModalClick);
				$(document).ready(handleModalClick);
			}
			
		};
		
		if(CG.options.Modal.queue) require(['CG.Queue'], 'CG.Modal');
		
	/**
	 * CG.Modal constructor
	 * 
	 * @param	string	src		href or DOM element id
	 * @param	object	options	[optional]
	 */
	CG.Modal = function(src, options) {

		/**
		 * 
		 */
		this.events = {
			open: new CG.Event(),
			load: new CG.Event(),
			error: new CG.Event(),
			close: new CG.Event()
		};
				
		/**
		 * @private
		 */
		this._options = $.extend({}, defaultOptions, options || {});

		/**
		 * @private
		 */
		this._src = src;
		
		/**
		 * Set in _init()
		 * @private
		 */
		this._id = '';
		
		/**
		 * Set in _init()
		 * @private
		 */
		this._element = null;

		/**
		 * Instantiates a jqModal using the supplied options
		 */
		this._init();
		
		// Return the instance
		return this;
	};
	
	/**
	 * CG.Modal prototype
	 */
	CG.Modal.prototype = {

		/**
		 * Closes this modal dialog
		 */
		close: function() {
			$(this._element).jqmHide();
			// actual work done in onModalClose above
		},
		
		/**
		 * Shows this modal dialog
		 */
		show: function() {
			$(this._element).jqmShow();
			// actual work done in onModalShow above
		},
		
		/**
		 * Returns the DOM element containing the dialog
		 */
		getContainerElement: function() {
			return this._element;
		},

		/**
		 * @private
		 */
		_init: function() {
			var	o            = this._options,                             // Shortcut for options
				visibleModal = CG.Modal._getVisibleModal(),
				containerEl  = (o.target === 'current' && visibleModal) ? // If target is 'current', use visible modal, if any.
					visibleModal._element :
					document.createElement('div'),
				$jqm, dims;                                               // Reference to the DOM element created by jqModal

			// Apply the link filter to non-dom sources
			if (typeof o.srcType === 'undefined' || o.srcType !== 'dom') {
				// this._src = CG.options.Modal.linkFilter(this._src);
				this._src = o.linkFilter(this._src);
			}
			
			// Force new target if no modal is visible
			if (!visibleModal) {
				o.target = 'new';
			}

			// Apply layout
			// supported types: CSS selector string, jQuery object, DOM element
			if (o.layout) {
				o.layout = (typeof o.layout === 'string') ? $(o.layout).get(0) : (
					       (typeof o.layout === 'object' && o.layout.jquery) ? o.layout.get(0) : o.layout);
				// for iframe and DOM srcTypes, layout is wrapped around content in onModalShow (jqModal only supports target element for AJAX srcTypes)
				if (o.srcType === 'ajax') {
					$(containerEl).append(o.layout);
					o.layout.style.visibility = 'visible';
				}
			}
			
			// Apply dimensions
			for (var key in dims = ({'width': o.width, 'height': o.height, 'top': o.top, 'left': o.left})) {
				if (dims[key]) {
					containerEl.style[key] = dims[key] + 'px';
					if (key === 'width') {
						containerEl.style.marginLeft = (-1*dims[key]/2) + 'px';
					} else if (key === 'top' || key === 'left') {
						containerEl.style.position = 'absolute';
						if (key === 'left') {
							containerEl.style.marginLeft = '0px';
						}
					}
				};
			}
			
			// If the target is 'new', append container element to body
			if (o.target === 'new') {
				containerEl.className = this._options.containerClass;
				containerEl.style.zIndex = 3000 + modals.length;
				document.body.appendChild(containerEl);
			}
			// ELSE If the target is 'current', clear the contents
			else {
				// containerEl.innerHTML = '';
				// AJAX-load new content, appending a random number string to the URL to prevent clients from caching
				// @todo this only works with 'ajax' sourceType
				$(containerEl).load(this._src+'?'+Math.floor(Math.random()*1E10+1));
			}
			
			// Show the dialog (if replacing contents of current modal, jqModal will just update options).
			// assign dialog DOM element to $jqm
			$jqm = $(containerEl).jqm({
				trigger    : '.no_jqm_trigger',
				ajax       : (o.srcType === 'ajax') ? this._src : false,
				srcType    : o.srcType,
				onShow     : onModalShow,
				onLoad     : onModalLoad,
				onHide     : onModalClose,
				closeClass : o.closeClass,
				overlay    : o.overlay,
				modal      : o.modal,
				layout     : o.layout,
				target     : (o.layout) ? o.layout : containerEl // only used for srcType === 'ajax'
				/*overlay : ($('.jqmOverlay:visible').length === 0) ? 50 : 0*/
			});
			
			// For new modals
			if (o.target === 'new') {
				// Store jqModal dialog serial number
				this._id = $jqm.get(0)._jqm;
				// Store reference to dialog element
				this._element = containerEl;
				// Store reference to instance if it isn't already stored
				if ($.inArray(this, modals) < 0) {
					modals.push(this);
				};	
			}
			
			if(!CG.options.Modal.queue || !CG.Modal._getVisibleModal())
			{
				// Show modal
				$jqm.jqmShow();
			}
			else
			{
				modalsQueue.insert(this, 0);
			}
		},
		
		/**
		 * Removes the dialog element from the DOM
		 * Removes reference to this CG.Modal object from modals array
		 * @private
		 */
		_destroy: function() {
			var	that = this,
				success = false;
			// Move DOM-source element from dialog to body
			if (this._options.srcType === 'dom' && $('.cg-modal-source', this._element).length >= 0) {
				document.body.appendChild( $('.cg-modal-source', this._element).hide().get(0) );
			}
			// Remove DOM element
			if ($.inArray(this._element, document.body.childNodes) >= 0) {
				document.body.removeChild(this._element);
			};
			// Remove reference
			$(modals).each(function(i) {
				if (this === that) {
					modals.splice(i, 1);
					success = true;
					return false; // break
				}
			});
			if (!success) {
				throw {name: 'instanceNotFoundError', message: 'CG.Modal._destroy: The instance could not be located.'};
			}
			else if (CG.options.Modal.queue) {
				// Remove queue item
				modalsQueue.remove(this);
			}
		}
	};
		
	/**
	 * CG.Modal static properties
	 */
	$.extend(CG.Modal, {

		/**
		 * 
		 */
		events: {
			open: new CG.Event(),
			load: new CG.Event(),
			error: new CG.Event(),
			close: new CG.Event()
		},

		/**
		 * Closes all modals
		 */
		closeAll: function() {
			if(CG.options.Modal.queue) modalsQueue.empty();
				
			$(modals).each(function () {
				this.close();
			});
		},
		
		/**
		 * Close the first visible modal
		 */
		closeVisible: function() {
			( CG.Modal._getVisibleModal() || { "close": function() { } } ).close();
		},

		/**
		 * Gets a CG.Modal instance with a specified ID.
		 * 
		 * @param	int			id [optional] Id of instance to retrieve. If specified, has to be >=0. 
		 * @return	CG.Modal	By default returns the first available instance.
		 */
		getModal: function(id) {
			var m = false;
			if (typeof id === 'undefined') {
				id = -1;
			}
			if (modals.length) {
				$.each(modals, function() {
					if (id < 0 || this._id === id) {
						m = this;
						return false; // break
					}
				});
			}
			return m;
		},
		
		/**
		 * Gets the ccount of existing dialogs
		 */
		modalsCount: function() {
			return modals.length;
		},

		/**
		 * Gets the first found visible (active) dialog
		 * @return CG.Modal object or false, if no dialog is visible
		 * @private
		 */
		_getVisibleModal: function() {
			var	m = false,
			    i;
			// Find visible dialog
			if (modals.length) {
				// Loop through modals backwards
				for (i=modals.length-1; i>=0; i-=1) {
					if ($(modals[i]._element).is(':visible')) {
						m = modals[i];
						break;
					}
				}
			}
			return m;
		}
	});

	init();
	
})(jQuery);