/**
 * LBi Dialogs module
 *
 * @module    dialog
 * @version   3.53.101229
 * @requires  LBi, jQuery
 * @author    LBi Lost Boys
 */
(function($) {

	var LBi = window.LBi;

	var	ACTION_CONFIRM = "confirm";
	var	ACTION_CANCEL = "cancel";
	var	ACTION_CLOSE = "close";

	/**
	 * The LBi.Dialogs class manages the flow of dialogs in a given page. A default 
	 * Dialog class is provided, which may be subclassed for customized behavior. Different
	 * managers may be instanciated, but in general you'll only need one, along with one
	 * or more registered Dialog classes. Settings are shared with dialogs and overlay.
	 * See LBi.Dialogs.register for adding different dialog classes.
	 * 
	 * @class LBi.Dialogs
	 * @constructor
	 * @param {Object} settings Optional settings, see LBi.Dialogs.Defaults
	 * @return {LBi.Dialogs} Dialogs manager
	 */

	var Dialogs = function(settings) {
		this.settings = $.extend({}, Dialogs.Defaults, settings);
		this.classes = {};
		this.stack = [];
		
		this.regRelation = new RegExp("(^|\\s)" + this.settings.prefix);
		this.regAction = new RegExp(this.settings.prefix + "([^ ]+)");

		this.relations = new LBi.LinkRelations();
		this.relations.subscribe(this.regRelation, this.handleClick.bind(this));

		LBi.subscribe('click', this.tryClose.bind(this));
	};

	Dialogs.prototype = {

		/**
		 * Main click handler
		 * @private
		 */
		handleClick: function(e) {
			e.preventDefault();

			var link = $(e.target).closest('a')[0];
			var type = this.regAction.exec(link.rel)[1];
			var dialog = this.findDialog(link);

			switch (type) {
				case ACTION_CLOSE:
					this.close(dialog);
				break;
				case ACTION_CANCEL:
					if(dialog.confirm(false)) {
						this.close(dialog);
					}
				break;
				case ACTION_CONFIRM:
					if(dialog.confirm(true)) {
						this.close(dialog);
					}
				break;
				default:
					var self = this;
					setTimeout(function() {
						self.open(type, link);
					}, 100);
				break;
			}
		},
		
		/**
		 * Registers a dialog class under the given type. The type parameter may then be used as a
		 * suffix for link rel attributes, and the manager will create a dialog instance from the 
		 * given class for it instead of from the default class. "confirm", "cancel" and "close" 
		 * are reserved types, and may not be used.
		 *
		 * @param {String} type Dialog type, without the prefix setting.
		 * @param {LBi.Dialog} dialog Subclasses of LBi.Dialog.
		 * @return {LBi.Dialog} dialog Returns the dialog for easy chaining.
		 */
		register: function(type, Class) {
			this.classes[type] = Class;
			return Class;
		},
		
		/**
		 * Returns the dialog class associated with the given type.
		 * 
		 * @param {String} type A previously registered dialog type.
		 * @return {Class} Dialog The class associated with the given type, defaults to LBi.Dialog if none is found.
		 */
		getClass: function(type) {
			return this.classes[type] || this.settings.dialogClass || Dialog;
		},

		/**
		 * Generic helper. Returns the dialog (if any) that contains the given element. 
		 * @private
		 */
		findDialog: function(link) {
			var l = this.stack.length;
			for(var i=0; i<l; i++) {
				var dialog = this.stack[i];
				var node = dialog.node;

				if(node.contains(link)) {
					return dialog;
				}
			}
			return null;
		},

		/**
		 * Opens a dialog of the given type, positioning itself on the given origin. This method is 
		 * called automatically for  dialog related links, but may also be called manually.
		 *
		 * @param {String} type Type of a registered dialog.
		 * @param {Element} origin The element that requested the dialog.
		 */
		open:function(type, link) {
			var Dialog = this.getClass(type);
			var dialog = new Dialog(type, this.settings);

			dialog.init();
			dialog.open(link);

			this.stack.push(dialog);
		},
		
		/**
		 * Closes the given dialog. Called automatically, but may be called manually for 
		 * specific purposes.
		 * 
		 * @param {Dialog} dialog The dialog that needs to be closed
		 */
		close:function(dialog) {
			dialog.close();
			
			var l = this.stack.length;
			for(var i=0; i<l; i++) {
				var item = this.stack[i];
				if(item === dialog) {
					this.stack.splice(i, 1);
				}
			}
		},
		
		/**
		 * Generic close handler, checks the current stack of dialogs and closes any if required.
		 * @private
		 */
		tryClose: function(e) {
			var dialog = this.findDialog(e.target);
			var l = this.stack.length -1;
			for(var i=l; i>=0; i--) {
				var item = this.stack[i];
				if(item === dialog) {
					return;
				}
				
				if(item !== dialog && !item.isModal()) {
					this.close(item);
					return;
				}
			}
		}
	};

	LBi.namespace('Dialogs', Dialogs);


	/**
	 * The Dialog class manages single dialog instances on a given page. Instances are fully 
	 * managed by a LBi.Dialogs manager instance, and should never be created manually. See the 
	 * LBi.Dialogs.register method for adding new Dialog classes to the manager. When subclassing
	 * the base Dialog, note that node references don't yet exist when the constructor is run.
	 *
	 * Instead of creating dialogs via their template, they may be also statically added to a page.
	 * The static dialog must define an ID attribute with the prefix setting and type parameter 
	 * combined. 
	 * 
	 * @class LBi.Dialog
	 * @constructor
	 * @param {String} type The dialog type, excluding the prefix.
	 * @param {Object} settings Shared dialog settings
	 * @return {LBi.Dialog} Dialog instance
	 */
	var Dialog = function(type, settings) {
		this.settings = $.extend({}, Dialogs.Defaults, settings);
		this.type = type;
	};

	Dialog.prototype = {
		/**
		 * Initializes the dialog and optionally the overlay. This method is automatically called 
		 * immediately after the constructor, and creates all node references. Take great care when
		 * overwriting this method, or make sure that subclasses call the parent class' implementation.
		 * 
		 */
		init: function() {
			var settings = this.settings;
			
			if(settings.overlay || settings.modal) {
				var Overlay = settings.overlayClass;
				this.overlay = new Overlay(settings);
				this.overlay.init(this);
			}
			
			var id = settings.prefix + this.type;
			var node = document.getElementById(id);
			if(!node) {
				this.templated = true;
				node = $(settings.dialogTemplate)[0];
			}

			if (!this.templated && !settings.nomove) {
                $('body').append(node);
            };

			this.node = node;
			this.$node = $(node);
		},

		/**
		 * Opens the dialog on the given origin, called automatically.
		 * @private
		 */
		open: function(origin) {
			this.origin = origin;

			this.activate(true);
			this.$node.css(Dialogs.INVISIBLE);
			this.redraw();
			this.$node.css(Dialogs.HIDDEN);
			this.toggle(true);
		},
		
		/**
		 * Closes the dialog, called automatically.
		 * @private
		 */
		close: function() {
			this.activate(false);
			this.toggle(false);
		},

		/**
		 * Activate is called on a dialog immediately before its visibility changes. 
		 * Subclasses may use this method as a hook to initiate or end functionality.
		 *
		 * @method activate
		 * @param {boolean} toggle True when the dialog is opened, false otherwise.
		 */
		activate: function(toggle) {
		},
		
		/**
		 * Confirm is called when either the confirm or cancel button of an active dialog is clicked. 
		 * The confirm button should be a link with rel="[prefix]confirm", cancel with rel="[prefix]cancel".
		 * Subclasses may use this method as a hook to initiate (or cancel) functionality. 
		 *
		 * @method confirm
		 * @param {boolean} toggle True when the dialog is opened, false otherwise.
		 * @return {boolean} Indicates whether the controller may close the dialog.
		 */
		confirm: function(toggle) {
			return true;
		},
		
		/**
		 * Toggles the dialog's visibility
		 * @private
		 */
		toggle:function(toggle) {
			
			if(this.overlay) {
				this.overlay.toggle(toggle);
			}

			var settings = this.settings;
			var animation = settings.animation;
			animation.run(this.node, toggle, { 
				duration: settings.duration ,
				complete: toggle? 
					this.complete.bind(this) : 
					this.remove.bind(this)
			});
			
			if(toggle === true){
				// matthijs: hack for IE - will not play flash video otherwise
				var plh = $(".flash", this.node);
				if(plh){
					setTimeout(function(){
						plh.css("visibility", "visible");
					}, 100);
				}
			}
			
		},

		/**
		 * Complete is called automatically after the animation of the dialog has completed running, 
		 * and the dialog is fully visible. Use this method to initiate functionality that requires 
		 * nodes to have physical dimensions, and which cannot rely on the nodeInserted event. By 
		 * default, this method is empty.
		 * 
		 * @method complete
		 */
		complete: function() {
		},

		/**
		 * Writes the given html to the dialog. Depending on the dialog template, subclasses
		 * should overwrite this method to have it target the correct node. Redraw should be
		 * called after a write changes the dialog's dimensions.
		 * 
		 * @param {String} html Content
		 */
		write: function(html) {
			LBi.DOM.write(this.node, html);
			this.redraw();
		},

		/**
		 * Called before a dialog is made visible. Returns the dialog's left and top properties based
		 * on the link that requested it, and the orientation setting. Subclasses may overwrite this
		 * method for alternate positioning.
		 *
		 * @return {Object} position Position object with left and top integers.
		 */
		getPosition: function() {
			var orientation = this.settings.orientation;
			var target = Dialogs.parseNode(this.origin);
			var dialog = Dialogs.parseNode(this.node);

			var position = {};
			orientation.replace(/left|top|right|bottom/g, function(type) {
				switch (type) {
					case 'left':
						position.left = target.left - (dialog.width - target.width);
					break;
					case 'top':
						position.top = target.top - dialog.height;
					break;
					case 'right':
						position.left = target.left;
					break;
					case 'bottom':
						position.top = target.top + target.height;
					break;
				}
			});

			return position;
		},

		/**
		 * Returns the dialog's root node, optionally the jQuery reference.
		 * 
		 * @param {boolean} jQuery Whether or not to return the jQuery reference.
		 * @return {Node} node The dialog's root node.
		 */
		getNode: function(jQuery) {
			return jQuery? this.$node : this.node;
		},

		/**
		 * Check whether the dialog is modal
		 * 
		 * @return {boolean} modal
		 */
		isModal: function() {
			return this.settings.modal;
		},

		/**
		 * Sets the dialog's modal setting. If true, the dialog will also have an overlay, even
		 * if the overlay is set to false. Modal dialogs do not close automatically; an action
		 * by the user is required. See the confirm method for details.
		 * 
		 * @param {boolean} modal Modal if true
		 */
		setModal: function(toggle) {
			this.settings.modal = toggle;
		},

		/**
		 * Sets the dialog's overlay setting. May be set to true for non modal dialogs, but the 
		 * dialog will still close on a click outside of the dialog. This method should only be 
		 * called in the dialog class' constructor.
		 * 
		 * @param {boolean} overlay Displays an overlay if true.
		 */
		setOverlay: function(toggle) {
			this.settings.overlay = toggle;
		},

		/**
		 * Sets the dialog's template from which it will be created when not found in the html.
		 * This method should only be called in the dialog class' constructor.
		 * 
		 * @param {String} template The dialog's new template
		 */
		setTemplate: function(template) {
			this.settings.dialogTemplate = template;
		},

		/**
		 * Sets the dialog's orientation.
		 *
		 * @param {String} Orientation
		 */
		setOrientation: function(orientation) {
			this.settings.orientation = orientation;
		},

		/**
		 * Redraws the dialog via a call to getPosition. This may be required after an ajax response
		 * has changed the dimensions of the dialog.
		 */
		redraw: function() {
			this.$node.css(
				this.getPosition()
			);
		},
		
		/**
		 * Removes the dialog and its associated nodes (if templated). Called automatically.
		 * @private
		 */
		remove: function() {
			if(this.templated) {
				this.$node.remove();
			}

			delete this.settings;
			delete this.$node;
			delete this.node;
		}
	};

	LBi.namespace('Dialog', Dialog);

	
	/**
	 * The centered dialog class extends the default dialog, and centers itself when opened. Note
	 * that the CenteredDialog is not bound to a type by default, and must be registered to the 
	 * controlling manager before it will be used.
	 * 
	 * @class LBi.CenteredDialog
	 * @constructor
	 * @extends LBi.Dialog
	 * @param {String} type The dialog type, excluding the prefix.
	 * @param {Object} settings Shared dialog settings
	 * @return {LBi.CenteredDialog} dialog
	 */
	CenteredDialog = LBi.Class.extend(
		Dialog, null, {
		
		/**
		 * The centered dialog overrides the getPosition method to center the dialog on screen,
		 * rather than positioning it on the link that opened it.
		 * 
		 * @method getPosition
		 */
		getPosition: function() {
			var win = $(window);
			var doc = $(document);
			var dialog = Dialogs.parseNode(this.node);
			return {
				left: doc.scrollLeft() + (win.width() - dialog.width)/2,
				top: doc.scrollTop() + (win.height() - dialog.height)/2
			};
		}
	});

	LBi.namespace('CenteredDialog', CenteredDialog);

	
	/**
	 * The overlay class functions as either just a nice effect, or as a blocker to page 
	 * functionality underneath modal dialogs. Instances are managed automatically, but subclasses
	 * may be created and used for alternate functionality.
	 *
	 * @class LBi.Overlay
	 * @constructor
	 * @param {Object} settings Shared dialog settings
	 * @return {LBi.Overlay} overlay
	 */
	function Overlay(settings) {
		this.settings = $.extend({}, Dialogs.Defaults, settings);
	}

	Overlay.prototype = {

		/**
		 * Initializes the overlay for the given dialog.
		 * 
		 * @param {Dialog} dialog The dialog for which this overlay is created.
		 */
		init:function(dialog) {
			this.$node = $(this.settings.overlayTemplate);
			this.dialog = dialog;
			this.node = this.$node[0];
		},

		/**
		 * Toggles the overlay's visibility.
		 * 
		 * @param {boolean} toggle Visibility.
		 */
		toggle:function(toggle) {
			if(toggle) {
				var $node = this.dialog.getNode(true);
				$node.before(this.$node);
			}

			var settings = this.settings;
			var animation = settings.animation;
			
			animation.run(this.node, toggle, {
				duration: settings.duration,
				complete: toggle? null : this.remove.bind(this)
			});
		},

		/**
		 * Removes the overlay, called automatically
		 * @private
		 */
		remove: function() {
			this.$node.remove();

			delete this.$node;
			delete this.node;
			delete this.dialog;
			delete this.settings;
		}
	};

	LBi.namespace('Overlay', Overlay);
	

	/**
	 * Static properties
	 * 
	 */

	Dialogs.INVISIBLE = { visibility: 'hidden', display: 'block' };
	Dialogs.HIDDEN = { display: 'none', visibility: 'visible' };
	
	Dialogs.parseNode = function(node) {
		var w = node.offsetWidth;
		var h = node.offsetHeight;
		var offset = $(node).offset();

		return $.extend(offset, {
			width: w,
			height: h
		});
	};


	/**
	 * Default settings for the Dialogs controller. 
	 * 
	 * @static
	 * @class LBi.Dialogs.Defaults
	 */
	Dialogs.Defaults = {

		/**
		 * Global dialog prefix, used in rel attributes and ID checks for pre existing dialogs.
		 * @property prefix
		 * @type String
		 * @default 'dialog-'
		 */
		prefix: 'dialog-',

		/**
		 * Defines whether dialogs are modal or not.
		 * @property modal
		 * @type boolean
		 * @default false
		 */
		modal: false,

		/**
		 * Defines whether dialogs have an overlay or not.
		 * @property overlay
		 * @type boolean
		 * @default true
		 */
		overlay: true,

		/**
		 * Orientation, any combo of left or right and top or bottom.
		 * @property orientation
		 * @type String
		 * @default 'right bottom'
		 */
		orientation: 'right bottom',
		
		/**
		 * Template of which dialogs are created if they're not already in the html.
		 * @property template
		 * @type String
		 * @default '&lt;div class="dialog">&lt;/div>'
		 */
		dialogTemplate: '<div class="dialog"></div>',
		
		/**
		 * Default dialog class used for dialogs that are not registered to a specific class.
		 * @property dialogClass
		 * @type Class
		 * @default LBi.Dialog
		 */
		dialogClass: Dialog,
		
		/**
		 * Template of which the modal overlay is created.
		 * @property overlay
		 * @type String
		 * @default '&lt;div id="overlay">&lt;/div>'
		 */
		overlayTemplate: '<div class="overlay"></div>',

		/**
		 * Defines the class of which the overlay is created, for subclassing purposes.
		 * @property overlayClass
		 * @type Class
		 * @default LBi.Overlay
		 */
		overlayClass: Overlay,

		/**
		 * The animation used for toggling the visibility of dialogs and the overlay
		 * @property animation
		 * @type LBi.Animation
		 * @default LBi.Animation.FADE
		 */
		animation: LBi.Animation.FADE,

		/**
		 * The duration of the animation (if any)
		 * @property duration
		 * @type number
		 * @default 200
		 */
		duration: 200,

        /*
         * Prevent the dialog el from being moved if it's already in the body
         * @property nomove
         * @type boolean
         * @default false
         */
        nomove: false
	};	

})(jQuery);

