function PastelPopupManager() {
	
	/**
	 * Holds all shown popups' data.
	 * Popup data is stored in this format: {popup: ...[, zIndex: ...][, mode: ...]}
	 * <ul>
	 *	<li>popup - The popup object. Can be of type <b>jQuery</b> or types that inherit <b>PastelBaseController</b></li>
	 *	<li>zIndex - <i>(Optional)</i> The default z-index to restore when hiding the popup.</li>
	 *	<li>mode - <i>(Optional)</i> Show the mode of the stored object. Currently its value can be 'MARK_ONLY'</li>
	 * </ul>
	 */
	var _popups = new Array();

	/**
	 * Show whether an popup is showing or not at a moment 
	 */
	var _isShowing = false;
	
	/**
	 * CSS selector of the close button object
	 */
	var _closeBtnSelector = "a.w_close";
	
	/**
	 * Set the close button selector
	 * @param {String} selector
	 */
	this.setCloseBtnSelector = function(selector) {
		_closeBtnSelector = selector;
	};
	
	/**
	 * CSS selector of the save button object
	 */
	var _submitBtnSelector = "a.id_save";
	
	/**
	 * Set the save button selector
	 * @param {String} selector
	 */
	this.setSaveBtnSelector = function(selector) {
		_submitBtnSelector = selector;
	};	
	
	/**
	 * CSS selector for the popup parent which z-index will be altered
	 */
	var _parentSelector = "ins.rel";
	
	/**
	 * Set the popup parent selector
	 * @param {String} selector
	 */	
	this.setParentSelector = function(selector) {
		_parentSelector = selector;
	};
	
	/**
	 * @param {Object} popup
	 * @return jQuery object that contains popup
	 * @type jQuery
	 */	
	function _getContainer(popup) {
		var $container = null;
		if (popup instanceof PastelBaseController) {
			if(popup.view != null) {
				$container = popup.view.container;
			}
		} else if (popup instanceof jQuery) {
			$container = popup;
		} else {
			throw("Unsupported type of popup given!");
		}		
		
		return $container;
	};
	
	/**
	 * Alter the z-index of the popup's parent object
	 * @param {Object} popup
	 * @return the default zIndex of the parent object
	 * @type string
	 */
 	function _alterZIndex(popup) {
		var $container = _getContainer(popup);
		
		if($container != null) {
			$parent = $container.parents(_parentSelector);
			if($parent != null && $parent.length > 0) {
				var defaultZIndex = $parent.css("z-index");
				$parent.css("z-index", "101");
				return defaultZIndex;
			}
		}
		return "auto";
 	};
 	
	/**
	 * Restore the z-index of the popup's parent object
	 * @param {Object} popup
	 * @param {string} zIndex - the default z-index to restore
	 */ 	
 	function _restoreZIndex(popup, zIndex) {
 		var $container = _getContainer(popup);
		if($container != null) {
			$parent = $container.parents(_parentSelector);
			if($parent != null && $parent.length > 0) {
				$parent.css("z-index", zIndex);
			}
		} 		
 	};	
	
 	/**
 	 * Hide the popup object
 	 * @param {Object} popupData
 	 */
	function _doHide(popupData) {
		var popup = popupData.popup;
		if (popup instanceof PastelBaseController) {
			popup.hideView();
		} else if (popup instanceof jQuery) {
			popup.css("display", "none");
		}		
		
		var onHide = popupData.onHide;
		if($.isFunction(onHide)) {
			onHide();
		}
	};
	
	/**
	 * Removes a popup object from the _popups array
	 * @param {Number} idx - the index of the object that will be removed
	 * @param {boolean} hideJQueryOnly - whether to remove only jQuery objects or all objects
	 */
	function _removePopup(idx, hideJQueryOnly) {
		var popupData = _popups[idx];
		var popup = popupData.popup;
		if(popup !== undefined && popup != null) {
			if (popup instanceof PastelBaseController) {
				if(hideJQueryOnly !== undefined && hideJQueryOnly == true) {
					return;
				}
			}
			
			_triggerCloseClick(popupData, hideJQueryOnly);
			if(!(popup instanceof PastelBaseController) || 
			   hideJQueryOnly === undefined || 
			   hideJQueryOnly == false) {
				_popups.splice(idx, 1);
			}
		}
	};
	
	/**
	 * Hide and remove the popup that is inserted last in the _snownPopup array
	 */
	function _removeLastShown() {
		var lastIdx = _popups.length - 1;
		while (lastIdx >= 0 && !_isVisible(lastIdx)) {
			_popups.splice(lastIdx, 1);
			lastIdx--;
		}
		if(lastIdx >= 0) {
			_removePopup(lastIdx);
		}
	};

	/**
	 * Hide and remove all popups
	 */	
	function _removeAll() {
		for(var i in _popups) {
			_removePopup(i);
		}
	};
	
	/** 
	 * @param {Number} x
	 * @param {Number} y
	 * @param {Number} idx
	 * @return If the point with coordinates <b>x</b> and <b>y</b> are inside the popup with index <b>idx</b> 
	 * @type boolean
	 */
	function _isInPopup(x, y, idx) {
		var popupData = _popups[idx];
		var popup = popupData.popup;
		if(popup === undefined || popup == null) {
			return false;
		}
		
		var $container = _getContainer(popup);
		if($container == null) {
			return false;
		}
		var minX = $container.offset().left;
		var maxX = minX + $container.width();
		var minY = $container.offset().top;
		var maxY = minY + $container.height();

		if(x >= minX && x <= maxX && y >= minY && y <= maxY) {
			return true;
		}
		return false;
	};
	
	/**
	 * @param {Number} idx
	 * @return If the popup with index <b>idx</b> is visible
	 * @type boolean
	 */
	function _isVisible(idx) {
		var popupData = _popups[idx];
		var popup = popupData.popup;
		
		if (popup instanceof PastelBaseController) {
			if (popup.view == null) {
				return false;
			} else {
				return popup.view.isVisible;
			}
		} else if (popup instanceof jQuery) {
			return popup.is(":visible");
		}
		
		return false;
	};
	
	/**
	 * Check if there is a visible popups in the <b>_popups</b> array and also remove all hidden popups 
	 * @return If there are visible popups in the <b>_popups</b> array
	 * @type boolean
	 */
	function _hasVisiblePopups() {
		if(_popups.length < 1) {
			return false;
		}
		
		var hasVisible = false;
		for (var i in _popups) {
			var popupData = _popups[i];
			var popup = popupData.popup;
			
			if (popup instanceof PastelBaseController) {
				if (popup.view == null) {
					hasVisible |= false;
					_popups.splice(i, 1);
				} else {
					var isVisible = popup.view.isVisible;
					hasVisible |= isVisible;
					if(!isVisible) {
						_popups.splice(i, 1);
					}
				}
			} else if (popup instanceof jQuery) {
				var isVisible = popup.is(":visible");
				hasVisible |= isVisible;
				if(!isVisible) {
					_popups.splice(i, 1);
				}				
			}				
		}
		
		return hasVisible;
	};
	
	/**
	 * The additional close click handler to call
	 */
	var _onCloseClick = function(event) {
		event.preventDefault();
		var popupData = event.data.popupData;
		_doHide(popupData);
		
		var zIndex = popupData.zIndex;
		if(zIndex !== undefined) {
			var popup = popupData.popup;
			_restoreZIndex(popup, zIndex);
		}	
	};

	/**
	 * Bind the additional close click handler
	 * @param {Object} popupData
	 */	
	function _bindCloseClick(popupData) {
		var popup = popupData.popup;
		var $container = _getContainer(popup);
		if($container != null) {
			var $closeBtn = $(_closeBtnSelector, $container);
			if($closeBtn.length > 1) {
				$closeBtn = $closeBtn.filter(":visible");
			}
			if($closeBtn.length > 0) {
				$closeBtn.one("click.popupManager", {popupData: popupData}, _onCloseClick);
			}
		}
 	};
 	
 	/**
 	 * Manually trigger all click handlers, that are binded to the popup close button
 	 * If there is no close button, manually invoke _doHide() function
 	 * @param {Object} popupData
 	 */
	function _triggerCloseClick(popupData) {
		var popup = popupData.popup;
		var $container = _getContainer(popup);
		if($container != null) {
			var $closeBtn = $(_closeBtnSelector, $container);
			if($closeBtn.length > 0) {
				$closeBtn.trigger("click");
			} else {
				_doHide(popupData);
				
				var zIndex = popupData.zIndex;
				if(zIndex !== undefined) {
					_restoreZIndex(popup, zIndex);
				}				
			}
		}
 	};
 	
 	/**
 	 * Add the <b>popup</b> to array and perform all initialisations
 	 * @param {Object} data
 	 */
 	function _addToShown(data) {
 		var popup = data.popup;
		var zIndex = _alterZIndex(popup);
		var popupData = {popup: popup, zIndex: zIndex, onHide: data.onHide};
		_popups.push(popupData);
		_bindCloseClick(popupData);
 	};
	 
 	/**
 	 * Init the PastelPopupManager object
 	 */
	function _init() {
		// register click for hide visible popup
		$(document).bind("click.popupManager", function(event) {
			if(_popups.length < 1) {
				return;
			}
			if(!_hasVisiblePopups()) {
				return;
			}

			var x = event.pageX;
			var y = event.pageY;
			for(var i = _popups.length - 1; i >= 0; i--) {
				if(!_isShowing && !_isInPopup(x, y, i)) {
					_removePopup(i, true);
				}
			}
			_isShowing = false;			
		});
		
		// register escape press handler
		$(document).bind("keydown.popupManager", function(event) {
			var keyCode = 0;
			if (event.which != "") {
				keyCode = event.which;
			} else if (event.charCode != "") {
				keyCode = event.charCode;
			} else if (event.keyCode != "") {
				keyCode = event.keyCode;
			}
			
			if(keyCode == 27) { // escape
				_removeLastShown();
			} else if(keyCode == 13) { // enter
				var lastIdx = _popups.length - 1;
				if(lastIdx >= 0) {
					var popup = _popups[lastIdx].popup;
					var $container = _getContainer(popup);
					if($("textarea", $container).length < 1) {
						var $submitBtn = $(_submitBtnSelector, $container);
						if($submitBtn.length > 0) {
							$submitBtn.click();
						}
					}
				}
			}
		});
	};
	_init();
	
	
	// ============================================================================================
	// PUBLIC =====================================================================================
	
	/**
	 * Show and register an object as popup
	 * @param {Object} data - The format is {popup: ...[, keepShown: ...]}
	 * @param {Function} callback - function to invoke when the popup is shown
	 */
	this.show = function(data, callback) {
		_isShowing = true;
		var popup = data.popup;
		if(popup === undefined) {
			throw("Popup not supplied!");
		}
		
 		// hide visible popup
		var keepShown = data.keepShown;
		if(keepShown === undefined || keepShown == false) {
			_removeAll();
		}
		
		// show supplied popup
		if (popup instanceof PastelBaseController) {
			popup.showView(function() {
				if($.isFunction(callback)) {
					callback();
				}
				_addToShown(data);
			});
		} else if (popup instanceof jQuery) {
			popup.css("display", "block");
			if($.isFunction(callback)) {
				callback();
			}
			_addToShown(data);
		} else {
			throw("Unsupported type of popup given!");
		}
	};
	
	/**
	 * Register an object as popup
	 * @param {Object} data - The format is {popup: ...[, keepShown: ...][, onHide: ...]}
	 */	
	this.markAsLastShown = function(data) {
		_isShowing = true;
		var popup = data.popup;
		if(popup === undefined) {
			throw("Popup not supplied!");
		}
		if (!(popup instanceof PastelBaseController) && !(popup instanceof jQuery)) {
			throw("Unsupported type of popup given!");
		}
		
 		// hide visible popup
		var keepShown = data.keepShown;
		if(keepShown === undefined || keepShown == false) {
			_removeAll();
		}		
		
		_popups.push({popup: popup, mode: "MARK_ONLY", onHide: data.onHide});
	};
	
	/**
	 * Hide all shown popups
	 */
	this.hideAll = function() {
		_removeAll();
	};
	
	/**
	 * Hide the last shown popup
	 */
	this.hideLastShown = function() {
		_removeLastShown();
	};
}
