var allModals = new function() {
	var mList = [],
	openMap = [];
	this.add = function( modalObj ) {
		modalId = mList.length;
		mList.push(modalObj);
		return modalId;
	};
	
	this.remove = function( modalId ) {
		mList[modalId] = null;
	};
	
	this.openSet = function() {
		var workingSet = [];
		$.each(mList, function( i, obj ) {
			if (!!obj && obj.isOpen()) {
				workingSet.push(obj);
			}
		});
		return workingSet;
	};
	
	this.wipe = function() {
		$.each(mList, function( i, obj ) {
			if (!!obj && obj.isOpen() && !obj.isMulti()) {
				obj.close();
			}
		});
	};
	
	this.hideAll = function(useCB) {
		$.each(mList, function( i, obj ) {
			if (!!obj && obj.isOpen() && !obj.isMulti()) {
				obj.hide(useCB);
			}
		});
	};
	
	this.showAll = function(useCB) {
		$.each(mList, function( i, obj ) {
			if (!!obj && obj.isOpen() && !obj.isMulti()) {
				obj.show(useCB);
			}
		});
	};
};

function Modal( contructorOpts ) {
	function DUMMYopts() {}
	DUMMYopts.prototype = {
		clean: true,  // Modify DOM on open/close
		//multi: false,  // Allow this modal to exist with other modals  /* NOT IMPLEMENTED */
		title: "",  // The modal's title
		html: "",  // Content to use in the modal
		useAjax: false,  // Uses an ajax request to get content
		url: "",  // URL for the ajax request
		ajaxData: {},  // data for the ajax request
		id: "dummyModal",
		className: "",
		height: 0,
		width: 0,
		animate: "opacity",
		overlay: true,
		closeSelector: "",
		ajaxCb: function() {},  // Callback to run after an ajax response
		buildCb: function() {}, // Callback to run after the modal builds
		openCb: function() {},  // Callback to run after the modal opens
		closeCb: function() {}, // Callback to run after the modal closes
		quickView: false
	};
	
	function AJAXopts() {}
	AJAXopts.prototype = {
		cache: true,
		dataType: "html",
		success: function( htmlStr ) {
			isAjaxDone = true;
			
			resolveDuplicateModal();
			setConfig({html: htmlStr});
			
			if (typeof opts.ajaxCb == "function") {
				opts.ajaxCb.call(modalInstance);
			}
			
			if (shouldBeOpen) {
				var oldClean = opts.clean;
				opts.clean = false;
				openModal();
				opts.clean = oldClean;
			}
		}
	};
	
	var opts = new DUMMYopts(),
	modalId = -1,
	modalInstance = this,
	modalDOM = $("#" + opts.id),
	modalDOMoverlay = $("#" + opts.id + "Overlay"),
	isOpen = false,
	isTransforming = false,
	isAjaxDone = false,
	shouldBeOpen = false,
	resizeDelayTimer = null,
	OPENanimationTime = 500,
	CLOSEanimationTime = 500,
	FASTanimationTime = 400,
	EASINGeq = "swing",
	IE6selectFixClass = "ie6selectFixModal",
	HIDEstyles = {
		display: "block",
		position: "absolute",
		overflow: "hidden",
		height: 0,
		width: 0
	},
	modalDOMoverlayStyles = {
		left: 0,
		top: 0,
		position: "absolute",
		zIndex: 9997
	},
	modalDOMstyles = {
		display: "block",
		overflow: "visible", // fixes a bug in jQuery where height/width animations leave a trailing "overflow: hidden" on elements.
		position: "absolute",
		height: "",
		width: "",
		zIndex: 9998
	},
	modalDOManimationStyles = {};
	
	function setConfig( configOpts ) {
		if (modalId === -1) {
			modalId = allModals.add(modalInstance);
		}
		if (!!configOpts) {
			var needsRebuild = false;
			$.each(opts, function( key ){
				if (configOpts[key] != null) {
					switch (key) {
						case "height":
						case "width":
							modalDOMstyles[key] = configOpts[key];
							opts[key] = configOpts[key];
							break;
						case "ajaxData":
						case "url":
							isAjaxDone = false;
							opts[key] = configOpts[key];
							break;
						case "id":
						case "html":
							needsRebuild = true;
							opts[key] = configOpts[key];
							break;
						default:
							opts[key] = configOpts[key];
					}
				}
			});
			if (!!configOpts.useAjax && !isAjaxDone) {
				var ajaxOpts = new AJAXopts()
				ajaxOpts.data = opts.ajaxData;
				ajaxOpts.url = opts.url;
				$.ajax(ajaxOpts);
			}
			if (needsRebuild) {
				buildDOM();
			}
		}
	}
	
	function buildDOM() {
		destroyDOM();
		if (isTransforming) {
			modalDOM.append(opts.html);
		} else {
			modalDOM = $("<div id='" + opts.id + "' class='modal" + ((!!opts.className) ? " " + opts.className : "") + "'></div>");
			modalDOMoverlay = $("<div id='" + opts.id + "Overlay' class='modalOverlay'></div>");
			modalDOMoverlay[0].modalRef = modalDOM[0].modalRef = modalInstance;  // Add a reference to the Modal obj from the DOM
			modalDOMoverlay.hide();
			modalDOMoverlay.prependTo(document.body);
			modalDOMoverlay.css(modalDOMoverlayStyles);
			modalDOMoverlay.hide();  // Safari 3.x Fix... (can't hide when not IN dom || applying css or adding to dom resets the hide)
			modalDOM.css(HIDEstyles);
			modalDOM.prependTo(document.body);
			modalDOM.css(modalDOMstyles);
			modalDOM.css(HIDEstyles);  // Safari 3.x Fix... (can't hide when not IN dom || applying css or adding to dom resets the hide)
			modalDOM.append(opts.html);  // Moved so that ajax inserted scripts run after objects exist in dom (after modalDOMwrap appends to body)
			
			if (typeof opts.buildCb == "function") {
				opts.buildCb.call(modalInstance);
			}
			
			addCloseListeners();
			$(window).unbind("resize.modalResize").bind("resize.modalResize", function() {
				clearTimeout(resizeDelayTimer);
				resizeDelayTimer = setTimeout(function() {
					centerModal(true);
				}, 300);
				stretchOverlay();
			});
			$(modalDOMoverlay).unbind("click.modalResize").bind("click.modalResize", function() {
				centerModal(true);
			});
		}
	}
	
	function destroyDOM() {
		if (!!modalDOM.length) {
			if (isTransforming) {
				modalDOM.empty();
			} else {
				modalDOM.empty();
				modalDOM.remove();
				modalDOMoverlay.remove();
				$(window).unbind("resize.modalResize");
			}
		}
	}
	
	function stretchOverlay() {
		if (opts.overlay) {
			modalDOMoverlay.stretchObj();
		}
	}
	
	function centerModal( useAnimation ) {
		if (isOpen) {
			var viewportSize = $.getViewSize(),
			frameHeight = 0,
			frameWidth = 0,
			frameTop = Math.floor(viewportSize.h / 2) + $(window).scrollTop(),
			frameLeft = Math.floor(viewportSize.w / 2);
			
			modalDOM.css(modalDOMstyles);
			frameHeight = modalDOM.outerHeight();
			frameWidth = modalDOM.outerWidth();
			
			frameTop -= Math.floor(frameHeight / 2);
			frameLeft -= Math.floor(frameWidth / 2);
			
			if (frameTop < 0) frameTop = 0;
			if (frameLeft < 0) frameLeft = 0;
			
			if (opts.height && (opts.height != parseInt(modalDOM.height()))) {
				$.extend(modalDOManimationStyles, { height: opts.height + "px" });
			}
			if (opts.width && (opts.width != parseInt(modalDOM.width()))) {
				$.extend(modalDOManimationStyles, { width: opts.width + "px" });
			}
			
			$.extend(modalDOManimationStyles, {
				top: frameTop + "px",
				left: frameLeft + "px"
			});
			
			$.extend(modalDOMstyles, modalDOManimationStyles);
			
			stretchOverlay();
			
			if (useAnimation) {
				modalDOM.stop();
				modalDOM.animate(modalDOManimationStyles, FASTanimationTime, EASINGeq, function() {
					modalDOM.css(modalDOMstyles);
					stretchOverlay();
				});
			} else {
				modalDOM.css(modalDOMstyles);
			}
		}
	}
	
	function addCloseListeners() {
		var closeEls = $("#" + opts.id + " .modalCloseLink");
		if (!!opts.closeSelector) {
			closeEls = closeEls.add(opts.closeSelector);
		}
		
		function closeHandler( evt ) {
			evt.preventDefault();
			closeEls.unbind("click.modalClose");
			closeModal();
		}
		
		if (!!closeEls.length) {
			closeEls.unbind("click.modalClose").bind("click.modalClose", closeHandler);
		}
	}
	
	function showModal() {
		modalDOM.stop();
		
		$.ie6selectFix({scope: modalDOM, className: IE6selectFixClass});
		
		if (opts.overlay) {
			modalDOMoverlay.show();
		}
		
		centerModal();
		
		if (!!opts.animate && (opts.animate != "none")) {
			switch (opts.animate) {
				case "opacity":
					modalDOM.css({opacity: 0});
					modalDOManimationStyles = $.extend(modalDOManimationStyles, {opacity: 1});
					break;
				case "corner":
					modalDOM.css(HIDEstyles);
					break;
			}
			modalDOM.animate(modalDOManimationStyles, OPENanimationTime, EASINGeq, function() {
				if ($.browser.msie && (opts.animate == "opacity")) {
					modalDOM.css({filter: ""});
				}
				
				if (typeof opts.openCb == "function") {
					opts.openCb.call(modalInstance);
				}
			});
		} else {
			modalDOM.css(modalDOManimationStyles);
			if (typeof opts.openCb == "function") {
				opts.openCb.call(modalInstance);
			}
		}
	}
	
	function hideModal() {
		modalDOM.stop();
		if (!!opts.animate) {
			if (opts.animate == "opacity") {
				modalDOManimationStyles = $.extend(modalDOManimationStyles, {opacity: 0});
			}
			modalDOM.animate(modalDOManimationStyles, CLOSEanimationTime, EASINGeq, function() {
				if (opts.overlay) {
					modalDOMoverlay.hide();
				}
				
				if (!allModals.openSet().length) {
					$.ie6selectFix({className: IE6selectFixClass});  // Should be fixed to only show next open modal if multi is considered
				}
				
				if (typeof opts.closeCb == "function") {
					opts.closeCb.call(modalInstance);
				}
			});
		} else {
			modalDOM.css(HIDEstyles);
			if (typeof opts.closeCb == "function") {
				opts.closeCb.call(modalInstance);
			}
		}
	}
	
	function openModal() {
		shouldBeOpen = true;
		
		if (!isOpen) {
			if (!!allModals.openSet().length) {
				allModals.wipe();
			}
			
			if (!opts.useAjax || isAjaxDone) {
				isOpen = true;
				if (opts.clean) {
					buildDOM();
				}
				showModal();
			}
		}
	}
	
	function closeModal() {
		shouldBeOpen = false;
		if (isOpen) {
			isOpen = false;
			hideModal();
			if (opts.clean) {
				modalDOM.queue(function() {
					destroyDOM();  // NOTE: event handlers will be lost... this should be worked out later
					modalDOM.dequeue();
				});
			}
		}
	}
	
	function findExistingModal() {
		modalDOM = $("#" + opts.id);
		modalDOMoverlay = $("#" + opts.id + "Overlay");
	}
	
	function resolveDuplicateModal() {
		findExistingModal();
		
		if (!!modalDOM.length) {
			if (!modalDOM[0].modalRef) {
				modalDOM[0].modalRef.kill();
			}
			findExistingModal();
		}
		
		if (!!modalDOMoverlay.length) {
			if (!modalDOMoverlay[0].modalRef) {
				modalDOMoverlay[0].modalRef.kill();
			}
			findExistingModal();
		}
	}
	
	modalInstance.isMulti = function() {
		return !!opts.multi;
	};
	
	modalInstance.isOpen = function() {
		return !!isOpen;
	};
	
	modalInstance.transform = function( transformOpts ) {
		modalInstance.retransform = (function( instanceOpts ) {
			return function() {
				modalInstance.transform(instanceOpts);
			};
		})(opts);
		
		isTransforming = true;
		modalDOM.addClass("modalTransforming");
		opts = new DUMMYopts();
		setConfig(transformOpts);
		centerModal(!!opts.animate);
		modalDOM.queue(function() {
			modalDOM.removeClass("modalTransforming");
			isTransforming = false;
			
			if (typeof opts.openCb == "function") {
				opts.openCb.call(modalInstance);
			}
			
			modalDOM.dequeue();
		});
	};
	
	modalInstance.retransform = function() {};
	
	modalInstance.open = function( htmlContent ) {
		if (typeof htmlContent == "string") {
			setConfig({html: htmlContent});
		}
		openModal();
	};
	
	modalInstance.close = function() {
		closeModal();
	};
	
	modalInstance.show = function(useCB) {
		showModal(useCB);
	};
	
	modalInstance.hide = function(useCB) {
		hideModal(useCB);
	};
	
	modalInstance.centerModal = centerModal;
	
	modalInstance.setConfig = setConfig;
	
	modalInstance.isQuickView = function() {
		return opts.quickView;
	};
	
	modalInstance.getModalDOM = function() {
		return modalDOM;
	};
	
	// REMOVE ME after dependencies are resolved
	modalInstance.getModalDOMwrap = function() {
		return modalDOM;
	};
	
	modalInstance.kill = function() {
		destroyDOM();
	};
	
	modalInstance.init = function( initOpts ) {
		resolveDuplicateModal();
		
		setConfig(initOpts);
	};
	modalInstance.init(contructorOpts);
}

$.template("modalShell",
	"<div class='modalHeader'>" +
		"<h2><span>{{= title}}</span></h2>" +
		"<a class='modalCloseLink'>Close</a>" +
		clearer +
	"</div>" +
	"<div class='modalContent'>" +
		"{{html body}}" +
		clearer +
	"</div>"
);

