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,
		shadow: true,
		shadowPad: 5,
		closeSelector: '',
		ajaxCb: function() {},  // Callback to run after an ajax response
		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),
	modalDOMwrap = $('#' + opts.id + 'Outer'),
	modalDOMoverlay = $('#' + opts.id + 'Overlay'),
	shadowMatrix = [],           // shadowMatrix => 0 1 2 
	isOpen = false,              //                 7   3
	isTransforming = false,      //                 6 5 4
	isAjaxDone = false,
	shouldBeOpen = false,
	OPENanimationTime = 500,
	CLOSEanimationTime = 500,
	FASTanimationTime = 400,
	EASINGeq = 'swing',
	IE6selectFixClass = 'ie6selectFixModal',
	HIDEstyles = {
		position: 'absolute',
		overflow: 'hidden',
		height: 0,
		width: 0
	},
	modalDOMoverlayStyles = {
		left: 0,
		top: 0,
		position: 'absolute',
		zIndex: 9997
	},
	modalDOMwrapStyles = {
		overflow: 'visible', // fixes a bug in jQuery where height/width animations leave a trailing "overflow: hidden" on elements.
		position: 'absolute',
		zIndex: 9998
	},
	modalDOMstyles = {
		overflow: 'visible', // fixes a bug in jQuery where height/width animations leave a trailing "overflow: hidden" on elements.
		position: 'absolute',
		zIndex: 9999
	},
	modalDOManimationStyles = {},
	modalDOMwrapAnimationStyles = {};
	
	function setConfig( configOpts ) {
		if (modalId === -1) {
			modalId = allModals.add(modalInstance);
		}
		if (!!configOpts) {
			var needsRebuild = false;
			$.each(opts, function( key ){
				if (typeof configOpts[key] != 'undefined') {
					switch (key) {
						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 (!!opts.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>');
			modalDOMwrap = $('<div id="' + opts.id + 'Outer" class="modalOuter"></div>');
			modalDOM.appendTo(modalDOMwrap);
			modalDOMoverlay = $('<div id="' + opts.id + 'Overlay" class="modalOverlay"></div>');
			modalDOMoverlay[0].modalRef = modalDOMwrap[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)
			modalDOMwrap.css(HIDEstyles);
			modalDOMwrap.prependTo(document.body);
			modalDOMwrap.css(modalDOMwrapStyles);
			modalDOM.css(modalDOMstyles);
			modalDOMwrap.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 (opts.shadow) {
				addShadow();
			}
			addCloseListeners();
			$(window).unbind('resize.modalResize').bind('resize.modalResize', function() {
				centerModal(true);
			});
			$(modalDOMoverlay).unbind('click.modalResize').bind('click.modalResize', function() {
				centerModal(true);
			});			
		}
	}
	
	function destroyDOM() {
		if (!!modalDOMwrap.length && !!modalDOM.length) {
			if (isTransforming) {
				modalDOM.empty();
			} else {
				removeShadow();
				modalDOM.empty();
				modalDOMwrap.remove();
				modalDOMoverlay.remove();
				$(window).unbind('resize.modalResize');
			}
		}
	}
	
	function centerModal( useAnimation ) {
		if (isOpen) {
			var viewportSize = $.getViewSize(),
			hasShadow = !!shadowMatrix.length,
			frameHeight = opts.height,
			frameWidth = opts.width,
			frameTop = Math.floor(viewportSize.h / 2) + $(window).scrollTop(),
			frameLeft = Math.floor(viewportSize.w / 2);
			
			if (hasShadow) {
				frameHeight += opts.shadowPad * 2;
				frameWidth += opts.shadowPad * 2;
				$.extend(modalDOManimationStyles, {
					top: opts.shadowPad + 'px',
					left: opts.shadowPad + 'px'
				});
			}
			
			frameTop -= Math.floor(frameHeight / 2);
			frameLeft -= Math.floor(frameWidth / 2);
			
			if (frameTop < 0) frameTop = 0;
			if (frameLeft < 0) frameLeft = 0;
			
			$.extend(modalDOManimationStyles, {
				height: opts.height + 'px',  // should test and only add animation for height/width if there is a delta from current.
				width: opts.width + 'px'
			});
			$.extend(modalDOMwrapAnimationStyles, {
				top: frameTop + 'px',
				left: frameLeft + 'px',
				height: frameHeight + 'px',
				width: frameWidth + 'px'
			});
			
			$.extend(modalDOMstyles, modalDOManimationStyles);
			$.extend(modalDOMwrapStyles, modalDOMwrapAnimationStyles);
			
			modalDOMoverlay.stretchObj();
			
			if (useAnimation) {
				modalDOM.stop();
				modalDOM.animate(modalDOManimationStyles, FASTanimationTime, EASINGeq, function() {
					modalDOM.css(modalDOMstyles);
				});
				modalDOMwrap.stop();
				modalDOMwrap.animate(modalDOMwrapAnimationStyles, FASTanimationTime, EASINGeq, function() {
					modalDOMwrap.css(modalDOMwrapStyles);
					
					try {
						setTimeout(function() {
							if (!!window['allTooltips']) {
								allTooltips.clean();
							}
						}, 0);
					} catch (err) {}
				});
				
				adjustShadows(useAnimation);
			} else {
				modalDOM.css(modalDOMstyles);
				modalDOMwrap.css(modalDOMwrapStyles);
				
				try {
					setTimeout(function() {
						if (!!window['allTooltips']) {
							allTooltips.clean();
						}
					}, 0);
				} catch (err) {}
				
				adjustShadows(useAnimation);
			}
		}
	}
	
	function styleShadow( shadowElement, shadowIndex ) {  // Theory says this should be done via CSS :(
		var shadowCSS = {
			position: 'absolute',
			overflow: 'hidden',
			zIndex: 9998
		};
		switch (shadowIndex) {
			case 0:
			case 1:
			case 2:
				shadowCSS.top = 0;
				shadowCSS.height = opts.shadowPad + 'px';
				break;
			case 4:
			case 5:
			case 6:
				shadowCSS.bottom = 0;
				shadowCSS.height = opts.shadowPad + 'px';
				break;
			case 3:
			case 7:
				shadowCSS.top = opts.shadowPad + 'px';
				shadowCSS.height = opts.height + 'px';
				
		}
		switch (shadowIndex) {
			case 0:
			case 6:
			case 7:
				shadowCSS.left = 0;
				shadowCSS.width = opts.shadowPad + 'px';
				break;
			case 2:
			case 3:
			case 4:
				shadowCSS.right = 0;
				shadowCSS.width = opts.shadowPad + 'px';
				break;
			case 1:
			case 5:
				shadowCSS.left = opts.shadowPad + 'px';
				shadowCSS.width = opts.width + 'px';
				
		}
		if ($.browser.msie/* && ($.browser.version < 7)*/) {  // Do for all versions of IE to keep the fades from turning pngs black
			var bgIm = shadowElement.css('backgroundImage').replace(/^url\(\"([^\"]*)\"\)$/, '$1');
			shadowElement.css({backgroundImage: 'none'});
			shadowCSS.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,sizingMethod=scale,src="' + bgIm + '")';
		}
		return shadowElement.css(shadowCSS);
	}
	
	function adjustShadows( useAnimation ) {
		if (!!shadowMatrix.length) {
			var vEdgeShadows = [$(shadowMatrix[3]), $(shadowMatrix[7])],
			hEdgeShadows = [$(shadowMatrix[1]), $(shadowMatrix[5])];
			$.each(vEdgeShadows, function( i, obj ) {
				if (useAnimation) {
					obj.stop();
					obj.animate({height: opts.height + 'px'}, FASTanimationTime, EASINGeq);
				} else {
					obj.css({height: opts.height + 'px'});
				}
			});
			$.each(hEdgeShadows, function( i, obj ) {
				if (useAnimation) {
					obj.stop();
					obj.animate({width: opts.width + 'px'}, FASTanimationTime, EASINGeq);
				} else {
					obj.css({width: opts.width + 'px'});
				}
			});
		}
	}
	
	function addShadow() {
		if (!shadowMatrix.length) {
			for (var i = 0; i < 8; i++) {
				shadowMatrix.push(styleShadow($('<div class="modalShadow' + i + '"></div>').appendTo(modalDOMwrap), i));
			}
		}
	}
	
	function removeShadow() {
		while(!!shadowMatrix.length) {
			shadowMatrix.pop().remove();
		}
	}
	
	function addCloseListeners() {
		if (!!opts.closeSelector) {
			var closeEls = $(opts.closeSelector);
			
			function closeHandler( evt ) {
				evt.preventDefault();
				closeEls.unbind('click.modalClose');
				setTimeout(function() {
					closeModal();
				}, 0);
			}
			
			if (!!closeEls.length) {
				closeEls.unbind('click.modalClose').bind('click.modalClose', closeHandler);
			}
		}
	}
	
	function showModal() {
		modalDOMwrap.stop();
		
		$.ie6selectFix({scope: modalDOM, className: IE6selectFixClass});
		
		if (opts.overlay) {
			modalDOMoverlay.show();
		}
		
		setTimeout(function() {
			centerModal();
			
			if (!!opts.animate && (opts.animate != 'none')) {
				switch (opts.animate) {
					case 'opacity':
						modalDOMwrap.css({opacity: 0});
						modalDOMwrapAnimationStyles = $.extend(modalDOMwrapAnimationStyles, {opacity: 1});
						break;
					case 'corner':
						modalDOMwrap.css(HIDEstyles);
						break;
				}
				modalDOMwrap.animate(modalDOMwrapAnimationStyles, OPENanimationTime, EASINGeq, function() {
					if ($.browser.msie && (opts.animate == 'opacity')) {
						modalDOMwrap.css({filter: ''});
					}
					
					if (typeof opts.openCb == 'function') {
						opts.openCb.call(modalInstance);
					}
				});
			} else {
				modalDOMwrap.css(modalDOMwrapAnimationStyles);
				if (typeof opts.openCb == 'function') {
					opts.openCb.call(modalInstance);
				}
			}
		}, 0);
	}
	
	function hideModal() {
		modalDOMwrap.stop();
		setTimeout(function() {
			if (!!opts.animate) {
				if (opts.animate == 'opacity') {
					modalDOMwrapAnimationStyles = $.extend(modalDOMwrapAnimationStyles, {opacity: 0});
				}
				modalDOMwrap.animate(modalDOMwrapAnimationStyles, 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 {
				modalDOMwrap.css(HIDEstyles);
				if (typeof opts.closeCb == 'function') {
					opts.closeCb.call(modalInstance);
				}
			}
		}, 0);
	}
	
	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) {
				modalDOMwrap.queue(function() {
					destroyDOM();  // NOTE: event handlers will be lost... this should be worked out later
					modalDOMwrap.dequeue();
				});
			}
		}
	}
	
	function findExistingModal() {
		modalDOM = $('#' + opts.id);
		modalDOMwrap = $('#' + opts.id + 'Outer');
		modalDOMoverlay = $('#' + opts.id + 'Overlay');
	}
	
	function resolveDuplicateModal() {
		findExistingModal();
		
		if (!!modalDOMwrap.length) {
			if (!modalDOMwrap[0].modalRef) {
				modalDOMwrap[0].modalRef.kill();
			}
			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;
		modalDOMwrap.addClass('modalTransforming');
		opts = new DUMMYopts();
		setConfig(transformOpts);
		centerModal(!!opts.animate);
		modalDOMwrap.queue(function() {
			modalDOMwrap.removeClass('modalTransforming');
			isTransforming = false;
			
			if (typeof opts.openCb == 'function') {
				opts.openCb.call(modalInstance);
			}
			
			modalDOMwrap.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.isQuickView = function() {
		return opts.quickView;
	};
	
	modalInstance.getModalDOM = function() {
		return modalDOM;
	};
	
	modalInstance.getModalDOMwrap = function() {
		return modalDOMwrap;
	};
	
	modalInstance.kill = function() {
		destroyDOM();
	};
	
	modalInstance.init = function( initOpts ) {
		resolveDuplicateModal();
		
		setConfig(initOpts);
	};
	modalInstance.init(contructorOpts);
}
