function getViewportDimensions()
{
	var viewportwidth;
	var viewportheight;
	// the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
	if (typeof window.innerWidth != 'undefined')
	{
		viewportwidth = window.innerWidth,
		viewportheight = window.innerHeight
	}
	// IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
	else if (typeof document.documentElement != 'undefined' && typeof document.documentElement.clientWidth != 'undefined' && document.documentElement.clientWidth != 0)
	{
		viewportwidth = document.documentElement.clientWidth,
		viewportheight = document.documentElement.clientHeight
	}
	// older versions of IE
	else
	{
		viewportwidth = document.getElementsByTagName('body')[0].clientWidth,
		viewportheight = document.getElementsByTagName('body')[0].clientHeight
	}
	return new Array(viewportwidth, viewportheight);
}

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.openList = function() {
        var oList = [];
        mList.each(function( obj ) {
            if (obj.isOpen()) {
                oList.push(obj);
            }
        });
        return oList;
    };*/
    
    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();
            }
        });
    };
};

if ($.browser.msie && ($.browser.version < 7)) {
    $(document).ready(function() {
        $(document.body).append('<style type="text/css">select.modalSelectHide{visibility: hidden !important;}</style>');
    });
}

function Modal( contructorOpts ) {
    function DUMMYopts() {
        this.clean = true;  // Modify DOM on open/close
        //this.multi = false;  // Allow this modal to exist with other modals  /* NOT IMPLEMENTED */
        //this.title = '';  // The modal's title
        this.html = '';  // Content to use in the modal
        //this.useAjax = false;  // Uses an ajax request to get content  /* NOT IMPLEMENTED */
        //this.url = '';  // URL for ajax request  /* NOT IMPLEMENTED */
        this.id = 'dummyModal';
        this.height = 0;
        this.width = 0;
        this.shadow = false;  // Until rendering issues are worked out, this may be an issue
        this.shadowPad = 16;
        this.openCb = function() {};  // Callback to run after the modal opens
        this.closeCb = function() {};  // Callback to run after the modal closes
    }
    
    var opts = new DUMMYopts(),
    modalId = -1,
    modalInstance = this,
    modalDOM = $('#' + opts.id),
    modalDOMwrap = $('#' + opts.id + 'Outer'),
    shadowMatrix = [],           // shadowMatrix => 0 1 2 
    isOpen = false,              //                 7   3
    isTransforming = false,      //                 6 5 4
    OPENanimationTime = 500,
    CLOSEanimationTime = 500,
    FASTanimationTime = 400,
    EASINGeq = 'swing',
    modalDOMwrapStyles = {
        position: 'absolute',
        zIndex: 9998
    },
    modalDOMstyles = {
        overflow: 'hidden',
        position: 'absolute',
        zIndex: 9999
    };
    
    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 'id':
                        case 'html':
                            needsRebuild = true;
                        default:
                            opts[key] = configOpts[key];
                    }
                }
            });
            if (needsRebuild) {
                buildDOM();
            }
        }
    }
    
    function buildDOM() {
        destroyDOM();
        if (isTransforming) {
            modalDOM.append(opts.html);
        } else {
            modalDOM = $('<div id="' + opts.id + '" class="modal"></div>');
            modalDOMwrap = $('<div id="' + opts.id + 'Outer" class="modalOuter"></div>');
            modalDOM.appendTo(modalDOMwrap);
            modalDOMwrap[0].modalRef = modalDOM[0].modalRef = modalInstance;  // Add a reference to the Modal obj from the DOM
            modalDOMwrap.hide();
            modalDOMwrap.css(modalDOMwrapStyles);
            modalDOM.css(modalDOMstyles);
            modalDOMwrap.appendTo('body');
            modalDOM.append(opts.html);
            modalDOMwrap.hide();  // Safari 3.x Fix... (can't hide when not IN dom || applying css or adding to dom resets the hide)
            if (opts.shadow) {
                addShadow();
            }
        }
    }
    
    function destroyDOM() {
        if (!!modalDOMwrap.length && !!modalDOM.length) {
            if (isTransforming) {
                modalDOM.empty();
            } else {
                removeShadow();
                modalDOMwrap.remove();
            }
        }
    }
    
    function centerModal( useAnimation ) {
        if (isOpen) {
            var viewport = getViewportDimensions(),
            hasShadow = !!shadowMatrix.length,
            frameHeight = opts.height,
            frameWidth = opts.width,
            frameTop = Math.floor(viewport[1] / 2) + $(window).scrollTop(),
            frameLeft = Math.floor(viewport[0] / 2);
            
            if (hasShadow) {
                frameHeight += opts.shadowPad * 2;
                frameWidth += opts.shadowPad * 2;
            }
            
            frameTop -= Math.floor(frameHeight / 2);
            frameLeft -= Math.floor(frameWidth / 2);
            
            if (frameTop < 0) frameTop = 0;
            if (frameLeft < 0) frameLeft = 0;
            
            var modalDOManimationStyles = {
                top: opts.shadowPad + 'px',
                left: opts.shadowPad + 'px',
                height: opts.height + 'px',
                width: opts.width + 'px'
            },
            modalDOMwrapAnimationStyles = {
                top: frameTop + 'px',
                left: frameLeft + 'px',
                height: frameHeight + 'px',
                width: frameWidth + 'px'
            };
            
            $.extend(modalDOMstyles, modalDOManimationStyles);
            $.extend(modalDOMwrapStyles, modalDOMwrapAnimationStyles);
            
            if (useAnimation) {
                modalDOM.stop();
                modalDOM.animate(modalDOManimationStyles, FASTanimationTime, EASINGeq, function() {
                    modalDOM.css(modalDOMstyles);
                });
                modalDOMwrap.stop();
                modalDOMwrap.animate(modalDOMwrapAnimationStyles, FASTanimationTime, EASINGeq, function() {
                    modalDOMwrap.css(modalDOMwrapStyles);
                });
                animateShadows();
            } else {
                modalDOM.css(modalDOMstyles);
                modalDOMwrap.css(modalDOMwrapStyles);
            }
        }
    }
    
    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 animateShadows() {
        if (!!shadowMatrix.length) {
            var vEdgeShadows = [$(shadowMatrix[3]), $(shadowMatrix[7])],
            hEdgeShadows = [$(shadowMatrix[1]), $(shadowMatrix[5])];
            $.each(vEdgeShadows, function( i, obj ) {
                obj.stop();
                obj.animate({height: opts.height + 'px'}, FASTanimationTime, EASINGeq);
            });
            $.each(hEdgeShadows, function( i, obj ) {
                obj.stop();
                obj.animate({width: opts.width + 'px'}, FASTanimationTime, EASINGeq);
            });
        }
    }
    
    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 showModal() {
        centerModal();
        modalDOMwrap.stop();
        modalDOMwrap.hide();  // Safari fix... animation skips otherwise ???
        modalDOMwrap.animate({opacity: 'show'}, OPENanimationTime, EASINGeq, opts.openCb);
    }
    
    function hideModal() {
        modalDOMwrap.stop();
        modalDOMwrap.animate({opacity: 'hide'}, CLOSEanimationTime, EASINGeq, opts.closeCb);
    }
    
    function openModal() {
        if (!isOpen) {
            if (!!allModals.openSet().length) {
                allModals.wipe();
            }
            isOpen = true;
            if (opts.clean) {
                buildDOM();
            }
            ie6modalSelectFix('hide', modalDOM);
            showModal();
        }
    }
    
    function closeModal() {
        if (isOpen) {
            isOpen = false;
            hideModal();
            if (opts.clean) {
                modalDOMwrap.queue(function() {
                    destroyDOM();  // NOTE: event handlers will be lost... this should be worked out later
                    if (!allModals.openSet().length) {
                        ie6modalSelectFix('show');  // Should be fixed to only show next open modal if multi is considered
                    }
                    modalDOMwrap.dequeue();
                });
            }
        }
    }
    
    var ie6modalSelectFix = (function() {
        if ($.browser.msie && ($.browser.version < 7)) {
            return function( toggle, scope ) {
                var bod = $(document.body);
                if (toggle != 'hide') {
                    bod.find('select').removeClass('modalSelectHide');
                } else {
                    if (!scope) {
                        scope = bod;
                    } else {
                        bod.find('select').addClass('modalSelectHide');
                    }
                    scope.find('select').removeClass('modalSelectHide');
                }
            };
        } else {
            return function(){};
        }
    })();
        
    modalInstance.isMulti = function() {
        return !!opts.multi;
    };
    
    modalInstance.isOpen = function() {
        return !!isOpen;
    };
    
    modalInstance.transform = function( transformOpts ) {
        modalInstance.retransform = (function() {
            var instanceOpts = opts;
            return function() {
                modalInstance.transform(instanceOpts);
            };
        })();
        
        isTransforming = true;
        modalDOMwrap.addClass('modalTransforming');
        opts = new DUMMYopts();
        setConfig(transformOpts);
        centerModal(true);
        modalDOMwrap.queue(function() {
            modalDOMwrap.removeClass('modalTransforming');
            isTransforming = false;
            transformOpts.openCb();
            modalDOMwrap.dequeue();
        });
    };
    
    modalInstance.retransform = function() {};
    
    modalInstance.open = function( htmlContent ) {
        if (typeof htmlContent == 'string') {
            setConfig({html: htmlContent});
        }
        openModal();
    };
    
    modalInstance.close = function() {
        closeModal();
    };
    
    modalInstance.getModalDOM = function() {
        return modalDOM;
    };
    
    modalInstance.getModalDOMwrap = function() {
        return modalDOMwrap;
    };
    
    modalInstance.init = function( initOpts ) {
        setConfig(initOpts);
        
        $(window).resize(function() {
            centerModal(true);
        });
    };
    modalInstance.init(contructorOpts);
}
