/**
 * A jQuery lightbox plugin.
 * This plugin will take an element and show it within a lightbox.
 *
 * This plugin should be used on one element at a time. For jquery objects with many elements,
 * this plugin will only lightbox the first element in the set.
 *
 * When showing the element in a lightbox, the element is taken out of its current place
 * in the DOM (if it is in fact currently in the DOM) and placed in a newly created lightbox
 * container element. The lightbox container element is then appended to the BODY element.
 *
 * When the lightbox is closed, the element is taken out of the lightbox container and re-placed
 * into the DOM where it originally was (if it was originally in the DOM). The lightbox container
 * elements are then destroyed.
 *
 * Because the re-positioning of the element, take into consideration any CSS selectors that
 * are applied to the element that might not get applied when the element is inserted into the DOM
 * at a different location.
 *
 * Usages:
 * $("[element_selector]").showLightbox();
 * $("[element_selector]").hideLightbox();
 * $("[element_selector]").repositionLightbox();
 *
 * @author Andy Don
 * 
 */
(function($) {

    // The namespace used for data storage and event binding
    var NAMESPACE = "gorilla_lightbox";

    var DEFAULTS = {
        opacity : 0.5,
        speed : 400,
        bgColor : "#000000",
        closeOnBgClick : true,
        disableElementFade : false
    };

    $.fn.extend({

        /**
         * Shows the lightbox.
         * @param opts an object containing various options for the style and behavior of the
         * lightbox.
         * 
         * Options include:
         *
         * opacity :  default 0.5 - The opacity the background should be shown at.
         * speed : default 400 - the speed in which the lightbox opens/closes.
         * bgColor : default "#000000" - the color of the lightbox background
         * closeOnBgClick : default true - whether or not the lightbox should close when the background is clicked.
         * disableElementFade : default false - disables the fading of the element that is lightboxed.
         *      This is sometimes neccessary because IE cannot fade transparencies correctly.
         *
         * @param callback a function to be called when the lightbox showing animation completes.
         */
        showLightbox : function(opts, callback) {
            if (this.size() == 0) {
                return this;
            }

            if ($.isFunction(opts)) {
                callback = opts;
                opts = {};
            }
            
            var elem = $(this.get(0));
            _init(elem, opts);
            var data = elem.data(NAMESPACE);
            
            this.hideLightbox(function() {

                _construct(elem);

                if (data.opts.closeOnBgClick == undefined || data.opts.closeOnBgClick) {
                    data.elemContainer.children("div").click(function(event) {
                        event.stopPropagation();
                    });
                    data.lbBg.add(data.elemContainer).click(function(event) {
                        elem.hideLightbox();
                        event.stopPropagation();
                    });
                }

                elem.css("display", "block");

                data.lbBg.fadeIn(data.opts.speed, function() {
                    data.elemContainer.fadeIn(data.opts.elemSpeed, function() {
                        data.elemContainer.css({
                            "opacity":""
                        });
                        elem.repositionLightbox();
                        if ($.isFunction(callback)) {
                            callback.call(elem);
                        }
                    });
                });

            });

            return this;
        },

        /**
         * Hides the lightbox
         * @param callback a callback function to be called when animations completes.
         */
        hideLightbox : function(callback) {
            if (this.size() == 0) {
                return this;
            }

            var elem = $(this.get(0));
            var data = elem.data(NAMESPACE);

            if (!data || !data.lbBg || data.lbBg == null) {
                if ($.isFunction(callback)) {
                    callback.call(elem);
                }
                return this;
            }

            if (data.opts.disableElementFade) {
                data.elemContainer.css("display", "none");
            }

            data.elemContainer.fadeOut(data.opts.elemSpeed, function() {
                data.elemContainer.css("display", "none");
                data.lbBg.fadeOut(data.opts.speed*0.5, function() {
                    _destroy(elem);
                    if ($.isFunction(callback)) {
                        callback.call(elem);
                    }
                });
            });

            return this;
        },

        /**
         * Re-calculates the positioning of all lightbox elements. Ensures that the element
         * is centered and that the lightbox background fills the entire viewport.
         */
        repositionLightbox : function() {

            if (this.size() == 0) {
                return this;
            }

            var elem = $(this.get(0));
            var data = elem.data(NAMESPACE);

            if (data == null || data.elemContainer == null) {
                return this;
            }

            $(window).unbind("resize." + NAMESPACE);

            var elemContainer = data.elemContainer;
            var elemContainerInner = elemContainer.children("div:first");
            var lbElem = elemContainerInner.children(":first");

            var lbElemState = $(lbElem).css("display");
            var elemContainerState = $(elemContainer).css("display");

            $(lbElem).add(elemContainer).css("display", "block");
            var elemWidth = $(lbElem).outerWidth();
            var elemHeight = $(lbElem).outerHeight();
            $(lbElem).css("display", lbElemState);
            $(elemContainer).css("display", elemContainerState);

            var docWidth = $(document).width();
            var docHeight = $(document).height();
            var winWidth = $(window).width();
            var winHeight = $(window).height();
            var scrollTop = $(window).scrollTop();
            var elemContainerTop = scrollTop + (winHeight / 2) - (elemHeight / 2);
            if (elemContainerTop < 0) {
                elemContainerTop = 0;
            }

            docHeight = Math.max(docHeight, elemHeight);
            
            data.lbBg.css({
                width: "100%",
                height: docHeight + "px"
            });

            elemContainer.css({
                top: elemContainerTop + "px"
            });

            elemContainerInner.css({
                "width": elemWidth + "px",
                "height": elemHeight + "px"
            });

            if ($.browser.msie) {
                setTimeout(function() {
                    $(window).bind("resize." + NAMESPACE, function() {
                        elem.repositionLightbox();
                    });
                }, 1);
            } else {
                $(window).bind("resize." + NAMESPACE, function() {
                    elem.repositionLightbox();
                });
            }
            return this;
        }
    });

    /**
     * Internal function to initialize the element and data object.
     * Sets the user options.
     */
    function _init(elem, opts) {
        // The data object. All variables should be stored here.
        var data = {};
        elem.data(NAMESPACE, data);

        data.prev = elem.prev();
        data.elemStyle = elem.attr("style");

        // Default options
        data.opts = DEFAULTS;

        // Set the user options
        $.extend(data.opts, opts);

        data.opts.elemSpeed = data.opts.speed / 2;
        if (data.opts.disableElementFade) {
            data.opts.elemSpeed = 0.0001;
        }
    }

    /**
     * Internal function to construct the lightbox elements.
     */
    function _construct(elem) {
        var data = elem.data(NAMESPACE);

        data.lbBg = $('<div class="lbBg"></div>');
        $("body").append(data.lbBg);

        data.elemContainer = $('<div class="lightboxVerticalPosition"><div class="lightboxHoizontalPosition"></div></div>');
        $("body").append(data.elemContainer);

        data.elemContainerInner = data.elemContainer.children("div");
        data.elemContainerInner.append(elem);

        data.lbBg.css({
            display:"none",
            position:"absolute",
            top:"0",
            left:"0",
            backgroundColor : data.opts.bgColor,
            zIndex:"10100",
            opacity:data.opts.opacity
        });

        data.elemContainer.css({
            display:"none",
            position:"absolute",
            left:"0",
            width:"100%",
            height:"auto",
            zIndex:"10200"
        });

        data.elemContainerInner.css({
            "margin-left":"auto",
            "margin-right":"auto",
            "position":"relative"
        });

        elem.repositionLightbox();
    }

    /**
     * Internal function to remove the lightbox elements from the dom and clean up data.
     */
    function _destroy(elem) {
        var data = elem.data(NAMESPACE);
        $(window).unbind("resize." + NAMESPACE);
        
        elem.css("display", "");
        if (data.elemStyle) {
            elem.attr("style", data.elemStyle);
        }
        if (data.prev && data.prev.size() > 0) {
            data.prev.after(elem);
        }

        data.lbBg.remove();
        data.elemContainer.remove();
        elem.removeData(NAMESPACE);
    }
})(jQuery);
