(function($) {
/**
 * Some common js
 */
    
    var Gorilla = window.Gorilla = {};

    /**
     * A Timer class
     */
    (function() {

        Gorilla.Timer = Timer;

        function Timer() {
            this.timer = null;
            var curFn;
            var curTimeout;

            this.set = function(fn, time) {
                this.clear();
                curFn = fn;
                curTimeout = time;
                this.timer = window.setTimeout(fn, time);
            };

            this.clear = function() {
                if(this.timer) {
                    clearTimeout(this.timer);
                }
                this.timer = null;
                curFn = null;
                curTimeout = null;
            };

            this.reset = function() {
                if (curFn == null || curTimeout == null) { return; }
                this.set(curFn, curTimeout);
            };

            this.executeAndClear = function() {
                if (curFn) {
                    curFn.call();
                    this.clear();
                }
            }
        }
    })();


    /**
     * Lightbox: call show() with your element, and it will open in a lightbox effect
     */
    (function() {

        Gorilla.Lightbox = {
            show:show,
            hide:hide,
            reposition:reposition,
            getElement:getElement
        }

        //var lb;
        var lbBg;
        var lbElem;
        var elemContainer;
        var speed;
        var elemSpeed;
        var opacity;
        var disableElementFade;

        /**
         * Shows the lightbox with the specified element.
         *
         * @param elem the element to show in the lightbox
         * @param opts options object
         *  Available options:
         *      speed - speed of open/close
         *      bgColor - color of the grey-out effect
         *      opacity - the opacity of the background grey-out effect
         *      closeOnBgClick - whether or not to close the lightbox when the bg is clicked
         *      disableElementFade - disable the fading of the element
         * @param callback a callback function when the lightbox is done animating
         */
        function show(elem, opts, callback) {

            hide(function() {
                opacity = 0.8;
                var closeOnBgClick = true;
                speed = 800;
                lbElem = $(elem);
                disableElementFade = false;
                
                _construct();

                if (!opts) {
                    opts = {};
                }
                
                if (opts.bgColor) {
                    lbBg.css({ backgroundColor:opts.bgColor });
                }
                if (opts.opacity) {
                    opacity = opts.opacity;
                }
                if (opts.speed) {
                    speed = opts.speed;
                    elemSpeed = speed/2;
                }
                if (opts.disableElementFade) {
                    disableElementFade = true;
                    elemSpeed = 0.0001;
                }
                
                if (opts.closeOnBgClick == undefined || opts.closeOnBgClick) {
                    elemContainer.children("div").click(function(event) {
                        event.stopPropagation();
                    });
                    lbBg.add(elemContainer).click(function(event) {
                        hide();
                        event.stopPropagation();
                    });
                }

                lbBg.fadeIn(speed, function() {
                    elemContainer.fadeIn(elemSpeed, function() {
                        elemContainer.css({
                            "opacity":""
                        });
                        reposition();
                        if ($.isFunction(callback)) {
                            callback.call(lbElem);
                        }
                    });
                });
                
            });
        }


        /**
         * Hides the currently showing lightbox.
         * @param callback a callback function to call when the animation is complete
         */
        function hide(callback) {
            if (lbBg == null) {
                if ($.isFunction(callback)) {
                    callback.call(ret);
                }
                return;
            }

            var ret = lbElem;
            if (disableElementFade) {
                elemContainer.css("display", "none");
            }
            
            elemContainer.fadeOut(elemSpeed/2, function() {
                elemContainer.css("display", "none");
                lbBg.fadeOut(speed/4, function() {
                    _destroy();
                    if ($.isFunction(callback)) {
                        callback.call(ret);
                    }
                });
            });

            return ret;

        }

        /**
         * Re-calculates the position of the lightbox. Call this function
         * if the size of the element has changed.
         */
        function reposition() {

            if (elemContainer == null) {
                return;
            }

            $(window).unbind("resize.lightbox");

            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);

            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.lightbox", function() {
                        reposition();
                    });
                }, 1);
            } else {
                $(window).bind("resize.lightbox", function() {
                    reposition();
                });
            }
        }

        function getElement() {
            var e = elemContainer.children(":first").contents();
            if (e.size() > 0) {
                return e.get(0);
            }
            return null;
        }

        /**
         * Internal function to construct the lightbox elements.
         */
        function _construct() {
            
            lbBg = $('<div></div>');
            $("body").append(lbBg);

            elemContainer = $('<div><div></div></div>');
            $("body").append(elemContainer);

            var elemContainerInner = elemContainer.children("div");
            elemContainerInner.append(lbElem);

            lbBg.css({
                display:"none",
                position:"absolute",
                top:"0",
                left:"0",
                backgroundColor:"#ffffff",
                zIndex:"98",
                opacity:opacity
            });

            elemContainer.css({
                display:"none",
                position:"absolute",
                left:"0",
                width:"100%",
                height:"auto",
                zIndex:"99"
            });

            elemContainerInner.css({
                "margin-left":"auto",
                "margin-right":"auto",
                "position":"relative"
            });

            reposition();
        }

        /**
         * Internal function to remove the lightbox elements from the dom
         */
        function _destroy() {
            $(window).unbind("resize.lightbox");
            lbBg.remove();
            elemContainer.remove();
            lbBg = null;
            lbElem = null;
            elemContainer = null;
        }
    })();


    /**
     * Draws out the google map at the specified layer with the given
     * address, city, state, zip.
     *
     * Requires the google maps api
     */
    (function() {

        Gorilla.GMap = {
            show:show,
            hide:hide,
            render:render
        }

        /**
         * Open up a google map in the lightbox.
         *
         * @param locationName the name of the location
         * @param address the address
         * @param city the city
         * @param state the state
         * @param postalCode the postalCode
         * @param mapOpts map options, params include:
         *      width - the width of the map div
         *      height - the height of the map div
         *      initialZoom - the inital zoom of the map
         *
         * @param lightboxOpts lightbox options, see the lightbox options defined above
         */
        function show(locationName, address, city, state, postalCode, mapOpts, lightboxOpts) {
            var map = $('<div id="gmap"></div>');
            map.css({
                width: mapOpts && mapOpts.width ? mapOpts.width : "600px",
                height: mapOpts && mapOpts.height ? mapOpts.height : "400px"
            });

            Gorilla.Lightbox.show(map, lightboxOpts, function() {
                render(map.get(0), locationName, address, city, state, postalCode, mapOpts);
            });
        }

        /**
         * Hides the google map lightbox
         */
        function hide() {
            Gorilla.Lightbox.hide();
        }

        /**
         * Renders the google map onto the specified dom element
         *
         * @param domElement the dom element
         * @param locationName the location name
         * @param address the address
         * @param city the city
         * @param state the state
         * @param postalCode the postal code
         * @param opts options
         *  Available options:
         *      initialZoom - the initial zoom of the map
         */
        function render(domElement, locationName, address, city, state, postalCode, opts) {
            if (GBrowserIsCompatible()) {
                var gmap = new GMap2(domElement);
                gmap.addControl(new GLargeMapControl());
                gmap.addControl(new GMapTypeControl());
                var geocoder = new GClientGeocoder();

                var location = "";
                location = address ? location + " " + address : location;
                location = city ? location + " " + city : location;
                location = state ? location + ", " + state : location;
                location = postalCode ? location + " " + postalCode : location;

                var zoom = opts && opts.initialZoom ? opts.initialZoom : 13;

                geocoder.getLatLng(
                    location,
                    function(point) {
                        if (!point) {
                            alert(location + " not found");
                        } else {
                            gmap.setCenter(point, zoom);
                            var marker = new GMarker(point);
                            gmap.addOverlay(marker);

                            var loc = location;
                            var markerHtml = "<strong>" + locationName + "</strong><br />" + address + "<br />" + city + ", " + state + " " + postalCode;
                            markerHtml = markerHtml + '<br /><a target="_blank" href="http://maps.google.com/maps?saddr=&daddr=' + encodeURI(location) +  '">Get Directions</a>';

                            marker.bindInfoWindowHtml(markerHtml);
                            marker.openInfoWindowHtml(markerHtml);
                        }
                    }
                );
            }

            $("body").unload(function() {
                GUnload();
            })
        }


    })();


    /**
     * Validation functions
     */
    (function() {

        Gorilla.AjaxValidator = AjaxValidator;

        var AJAX_CALL_DELAY = 0;

        function AjaxValidator(id) {
            if (id == null) {
                id = "";
            }
            var eventClass = "ajaxValidator" + id;
            var validationElements = [];

            this.attachValidator = function(formElements, ajaxUrl, onValidationComplete) {
                for (var i = 0; i < formElements.length; i++) {

                    (function() {

                        var element = formElements[i];
                        validationElements.push(element);
                        
                        var validationTimer = new Gorilla.Timer();
                        var prevVal = "";

                        var bindEvent = "";
                        if ($(element).is("select")) {
                            bindEvent = "change." + eventClass + " keyup." + eventClass;

                        } else if ($(element).is(":checkbox")) {
                            bindEvent = "click." + eventClass;

                        } else if ($(element).is(":radio")) {
                            bindEvent = "click." + eventClass;

                        } else {    // text input or password input
                            //bindEvent = "keyup." + eventClass;
                            bindEvent = "blur." + eventClass;
                            $(element).bind("keypress." + eventClass, function(e) {
                                prevVal = $(this).val();
                            });
                        }

                        if (bindEvent == "") {
                            return;
                        }

                        $(element).bind(bindEvent, function() {
                            var that = this;

                            validationTimer.clear();
                            validationTimer.set(function() {
                                var fieldName = $(that).attr("name");
                                var fieldValue = $(that).val();

                                var url = ajaxUrl;
                                if (url.indexOf("?") == -1) {
                                    url = url + "?";
                                } else {
                                    url = url + "&";
                                }
                                for (var j = 0; j < formElements.length; j++) {
                                    var formElemName = $(formElements[j]).attr("name");
                                    var formElemVal = $(formElements[j]).val();
                                    url = url + encodeURIComponent(formElemName) + "=" + encodeURIComponent(formElemVal);
                                    if (j + 1 < formElements.length) {
                                        url = url + "&";
                                    }
                                }

                                $.getJSON(url, function(data) {
                                    if (fieldValue == null || fieldValue == "") {
                                        if (prevVal != fieldValue) {
                                            onValidationComplete.call(that, [], data.fieldErrorMap);
                                        }
                                        return;
                                    }
                                    var errorList = data.fieldErrorMap[fieldName];
                                    if (typeof(errorList) == "undefined" || errorList == null) {
                                        onValidationComplete.call(that, [], data.fieldErrorMap);
                                        return;
                                    }
                                    onValidationComplete.call(that, errorList, data.fieldErrorMap);
                                });

                            }, AJAX_CALL_DELAY);
                        });

                    })();

                }
            }

            this.validate = function(element) {
                var elems = validationElements;
                if (element != null) {
                    elems = [element];
                }
                $(elems).trigger("keyup." + eventClass);
                $(elems).trigger("click." + eventClass);
                $(elems).trigger("change." + eventClass);
            }

            this.detachValidator = function(element) {
                var elems = validationElements;
                if (element != null) {
                    elems = [element];
                }
                $(elems).unbind("." + eventClass);
            }
            
        }

    })();

})(jQuery);
