MAIN_TITLE = "Search Results";

FOR_SALE = 1006;
FOR_LEASE = 1007;

IS_IE = $.browser.msie;

IS_LOGGED_IN = false;
IS_NATIONAL = false;

AJAXMethodCaller.ALWAYS_NOCACHE = true;

var callTime;
var queryAndLoadTime;
var renderTime;

DEBUG = false;
function toggleDebug() {$('#debug-log').toggleClass('hidden')};
function debug(msg) {
    if (!DEBUG) return;
    try {
        var logDiv = $("#debug-log");

        var now = new Date();
        var time = now.getHours() + ":" + pad(now.getMinutes(),2) + ":" + pad(now.getSeconds(),2) + "." + pad(now.getMilliseconds(),3);

        logDiv.append('<div><strong>' + time + '</strong> ' + msg + '<div>');
        if (logDiv.children().length > 20) {
             $("#debug-log").find("div:first").remove();
        }
    } catch (e) {alert(e)}
}
function pad(val, size) {
    val = '' + val;
    while (val.length < size) {
        val = '0' + val;
    }
    return val;
}

var errorMsgTimeout;
function showUserError(msg) {
    if (errorMsgTimeout) {
        clearTimeout(errorMsgTimeout);
    }

    $('#error-message').html(msg);

    $('#error-message').show();
    errorMsgTimeout = setTimeout(function() { $('#error-message').slideUp() }, 10000);
}

function EntityManager(searchProvider) {
    if (EntityManager.instance) {
        return EntityManager.instance;
    }
    EntityManager.instance = this;
    var thisManager = this;

    this.searchProvider = searchProvider;
    this.searchCriteria = null;

    this.overrideEntityIDs = null;

    this.entitiesByName = new Array();
    this.entityIDs = new Array();

    this.searchResultsCount = 0;

    this.setSearchCriteria = function(searchCriteria) {
        this.searchCriteria = searchCriteria;
        this.entityIDs = new Array();
    }

    this.getResultsAndCallBack = function(methodName, methodParameters, callback, errorCallback) {
        var thisManager = this;
        var caller = null;

        callTime = new Date().getTime();
        var handler = function() {
            var req = caller.ajax.requestor;
            if (req.readyState == 4) {
                try {
                    // IE9 errors out on this call if the request was aborted.
                    req.status;

                    try {
                        if (req.status == 200) {
                            if (req.responseXML) {
                                var root = req.responseXML.documentElement;
                                var count = root.getAttribute("count")
                                if (count) {
                                    thisManager.searchResultsCount = count;
                                }
                                var mappables = null;
                                try {
                                    var entities = null;
                                    var entitiesNode = root.getElementsByTagName("entities");
                                    if (entitiesNode && entitiesNode.length) {
                                        entities = eval(entitiesNode[0].childNodes[0].nodeValue);
                                    }
                                    if (entities) {
                                        mappables = new Array();
                                        for (var i=0;i<entities.length;i++) {
                                            mappables.push(thisManager.addEntity(entities[i]));
                                        }
                                    }
                                } catch (err) {
                                    if (DEBUG) debug("Unable to parse entities: " + err);
                                }
                                queryAndLoadTime = new Date().getTime();
                                if (callback) {
                                    callback(req.responseXML, mappables);
                                }
                            }

                        } else if (req.status == 0) {
                            if (DEBUG) debug("AJAX response status 0");
                        } else {
                            if (DEBUG) debug("An AJAX response was not OK: " + req.statusText);
                            if (errorCallback) {
                                errorCallback();
                            }
                        }
                    } catch (e) {
                        if (DEBUG) debug("An AJAX error has occurred: " + e);
                        if (errorCallback) {
                            errorCallback();
                        }
                    }
                } catch (e) {
                    if (DEBUG) debug("IE9 AJAX abortion: " + e)
                }
            }
        }
        if (!this.searchProvider && methodName == "getEntity") {
            this.searchProvider = "com.catylist.ajax.RealEstateSearchProvider";
        }
        caller = new AJAXMethodCaller(this.searchProvider, methodName, handler);
        for (var i=0;i<methodParameters.length;i++) {
            caller.addParameter(methodParameters[i].type, methodParameters[i].value);
        }
        if (this.searchCriteria) {
            for (var key in this.searchCriteria) {
                caller.addRequestParameter(key, this.searchCriteria[key]);
            }
            delete this.searchCriteria["newSearch"];
        }
        return caller.call();
    }

    this.clearEntities = function() {
        this.entitiesByName = new Array();
        this.entityIDs = new Array();
    }

    this.addEntity = function(entity) {
        var name = entity.ID.toString();
        var mappable = this.entitiesByName[name];
        if (!mappable) {
            mappable = new Mappable(entity);
            this.entityIDs.push(mappable.ID);
            this.entitiesByName[name] = mappable;
        } else if (!mappable.isComplete()) {
            mappable.setData(entity);
        }
        return mappable;
    }

    this.getEntity = function(name) {
        return this.entitiesByName[name.toString()];
    }
    this.getEntities = function(ids) {
        var entities = new Array();
        for (var i = 0; i < ids.length; i++) {
            var entity = this.getEntity(ids[i]);
            if (entity) entities.push(entity);
        }
        return entities;
    }

    this.retrieveEntityAndCallBack = function(name,callback) {
        var entity = this.getEntity(name);
        if (!entity || !entity.isComplete()) {
            this.getResultsAndCallBack("getEntity",[new Parameter("java.lang.String",name)],callback);
        } else {
            if (callback) {
                callback();
            }
        }
    }

    this.retrieveEntitiesAndCallBack = function(bounds, callback) {
        var params = new Array();
        
        var boundsArray = null;
        if (bounds) {
            // expand the bouds a smidge to account for rounding errors
            boundsArray = [bounds.ne.lat(), bounds.ne.lng(), bounds.sw.lat(), bounds.sw.lng()];
            boundsArray[0] += .0000001;
            boundsArray[1] += .0000001;
            boundsArray[2] -= .0000001;
            boundsArray[3] -= .0000001;
        }
        params.push(new Parameter(AJAXMethodCaller.DOUBLE_ARRAY_TYPE, boundsArray));
        
        this.getResultsAndCallBack("getEntitiesInBounds", params, callback)
    }
}

Mappable.onOpenInfoWindow = function(mappable, infowindow) {
    if (!mappable || !mappable.isComplete()) {
        //if not complete then get the mappable, and have the callback open the info window
        var callback = function(xml) {
            mappable = new EntityManager().getEntity(mappable.ID);
            infowindow.setContent(mappable.getInfoHTML());

            //check the checkbox
            $("#bubble-title-{0} input:checkbox[value={0}]".format(mappable.ID)).each(function(){this.checked=new ActionManager().isSelected(mappable.ID)});

            new WindowState().save();
        }
        new EntityManager().retrieveEntityAndCallBack(mappable.ID,callback);
    } else {
        //check the checkbox
        $("#bubble-title-{0} input:checkbox[value={0}]".format(mappable.ID)).each(function(){this.checked=new ActionManager().isSelected(mappable.ID)});

        new WindowState().save();
    }
}
Mappable.onCloseInfoWindow = function(mappable) {
    new WindowState().save();
}
Mappable.onShowDetails = function(id) {
    new DetailsView().showDetails(id);
}
Mappable.isSelected = function(mappable) {
    return new ActionManager().isSelected(mappable.ID);
}

Cluster.onOpenInfoWindow = function(cluster, infowindow) {
    var onOpenComplete = function() {
        var entities = cluster.getEntities();
        for (var i = 0; i < entities.length; i++) {
            var actionMgr = new ActionManager();
            actionMgr.updateHighlight(entities[i].ID, actionMgr.isSelected(entities[i].ID));
        }
        new WindowState().save();
    }
    
    if (!cluster.isComplete()) {
        new EntityManager().retrieveEntitiesAndCallBack(cluster.getBounds(), 
                function(xml, entities) {
                    cluster.setEntities(entities);
                    infowindow.setContent(cluster.getInfoHTML());
                    
                    onOpenComplete();
                });
    } else {
        onOpenComplete();
    }
}
Cluster.onCloseInfoWindow = function(cluster) {
    new WindowState().save();
}


function TabsManager(tabsContainerID) {
    if (TabsManager.instance) {
        return TabsManager.instance;
    }
    TabsManager.instance = this;

    this.tabsContainerID = tabsContainerID;

    this.active = 1;
    this.lastActive = 1;

    this.tabs = new Array();
    this.tabs.push(new Object()); // the indices are 1-based

    this.onShow = function(tabIndex) {}
    this.onEnable = function(tabIndex) {}
    this.onDisable = function(tabIndex) {}

    this._doOnShow = function(tabIndex) {
        this.onShow(tabIndex);
    }
    this._doOnEnable = function(tabIndex) {
        this.onEnable(tabIndex);
    }
    this._doOnDisable = function(tabIndex) {
        this.onDisable(tabIndex);
    }

    this.triggerTab = function(tabIndex) {
        if (DEBUG) debug('triggering tab: ' + tabIndex);
        this.enableTab(tabIndex);

        var isSelected = false;
        var tab = this.tabs[tabIndex];
        if (tab) {
            isSelected = tab.tab.hasClass('active');
        }

        if (!isSelected) {
            for (var i = 1; i < this.tabs.length; i++) {
                this.tabs[i].tab.removeClass('active');
                this.tabs[i].container.addClass('tabs-hide');
            }

            if (tab) {
                tab.tab.addClass('active');
                tab.container.removeClass('tabs-hide');

                this.lastActive = this.active;
                this.active = tab.index;

                this._doOnShow(tab.index);
            }
        }
        return this;
    }
    this.disableTab = function(tabIndex) {
        var tab = this.tabs[tabIndex];
        if (tab) {
            if (!tab.tab.hasClass('tabs-disabled')) {
                tab.tab.addClass('tabs-disabled');
                this._doOnDisable(tab.index);
            }
        }
        return this;
    }
    this.enableTab = function(tabIndex) {
        var tab = this.tabs[tabIndex];
        if (tab) {
            if (tab.tab.hasClass('tabs-disabled')) {
                tab.tab.removeClass('tabs-disabled');
                this._doOnEnable(tab.index);
            }
        }
        return this;
    }

    this.getTabIndex = function(tabID) {
        var tab = this.tabs[tabID];
        return tab ? tab.index : null;
    }

    this.lastActiveTab = function() {
        return this.lastActive;
    }
    this.activeTab = function() {
        return this.active;
    }
    this.getActiveTabID = function() {
        var tab = this.tabs[this.active];
        return tab ? tab.id : null;
    }

    var me = this;
    $('#' + this.tabsContainerID).find('ul:first li a').each(function(i) {
        var containerID = this.getAttribute('href');
        if (containerID.indexOf('#') > -1) {
            containerID = containerID.substring(containerID.indexOf('#') + 1);
        }

        $(this).parent().parent().addClass('tabs');

        var tab = ({
            container: $('#' + containerID),
            tab: $(this).parent(),
            index: i + 1,
            id: containerID
        });

        tab.container.addClass('tabs-container');

        me.tabs.push(tab);
        me.tabs[containerID] = tab;

        $(this).click(function() {
            me.triggerTab(containerID);
            return false;
        });
    });

    this.triggerTab(1);
}

function getEntityTypeAndID(entityID) {
    var split = entityID.split('_');
    return ({"id": parseInt(split[1]), "type": parseInt(split[0]) });
}

function DetailsView() {
    if (DetailsView.instance) {
        return DetailsView.instance;
    }
    DetailsView.instance = this;

    this.currentEntityID = null;

    this.getCurrentEntityID = function() {
        if ($('#details-view-tab:visible').length > 0) {
            return this.currentEntityID;
        } else {
            return null;
        }
    }

    this.showDetails = function(entityID) {
        this._showDetails(entityID);

        // triggering tab will set a new details context
        new ActionManager().switchToTab('details-view');
    }

    // show details w/o changing tab, setting context, etc.
    this._showDetails = function(entityID) {
        if (!$('#details-tab').length) {
            window.location = "/jsp/common/overview.jsp?ID={0}".format(entityID);
        } else if (this.currentEntityID != entityID) {
            this.currentEntityID = entityID;

            $('#details-tab').html('<div class="tab-cover"></div><img src="/images/search/loading_graphic.gif" alt="Loading" class="beavis" />');

            var typeAndID = getEntityTypeAndID(entityID);

            var url = "/jsp/search/overview.jsp?ID={0}".format(entityID);

            var callback = function() {
                var actionMgr = new ActionManager();
                actionMgr.unthrobTab("details-view");
                actionMgr.updateHighlight(entityID, actionMgr.isSelected(entityID));
                //window.widget = new Widget('addToCatalog_frame');

                var defaultImageIndex = parseInt($('#default-image-index').val());
                if (!defaultImageIndex) {
                    defaultImageIndex = 0;
                }

                SlideManager.__instance = null;
                new SlideManager($("div.singleLightboxImage"),defaultImageIndex,"imagenumber","imagetotal");
                $("#imageholder a").colorbox({transition:"none", width:"60%", height:"600px", top: "30px", rel:"media"});

                var id = new DetailsView().getCurrentEntityID();
                $('#map-action-link').click(function() { new MapManager().showEntity(id); return false; });
                $('#email-action-link').click(function() { actionMgr.email(id); return false; });
                $('#catalog-action-link').unbind('click');
                $('#catalog-action-link').click(function() { actionMgr.addToCatalog(id); return false; });
                $('#brochure-action-link').click(function() {
                    // we don't want to print a suite-specific brochure, so we'll pull the listing ID from the link
                    var listingID = this.href.match(/ID=([0-9_]*)/)[1];
                    actionMgr.createReport(listingID); return false;
                });
                var entityCallback = function() {
                    var entity = new EntityManager().getEntity(entityID);
                    var lat = entity.latitude;
                    var lng = entity.longitude;
                    if (!(lat == -909090 && lng == -9090909) && !(lat == 0 && lng == 0)) {
                        var mapExtras = new MapExtras();
                        if (document.getElementById('msMap')) {
                            mapExtras.loadMSMap("msMap", lat, lng);
                        }
                        if (document.getElementById('gStreetView') && (!IS_IE || $.browser.version > 7)) {// streetview on ie7 screws up page formatting
                            mapExtras.loadGStreetView("gStreetView", "gStreetViewDiv", lat, lng);
                        }
                        if (document.getElementById('gMapDiv')) {
                            mapExtras.loadGMap("gMap", "gMapDiv", lat, lng);
                        }
                    }
                }
                new EntityManager().retrieveEntityAndCallBack(entityID, entityCallback);
            }
            new ActionManager().throbTab("details-view");
            new ScriptManager().loadDetailsScripts(function() {
                new AHAH().load($('#details-tab')[0],url, callback);
            });
        }
        popupOnOverviewIfNotLoggedIn();
    }
}

function EmailView() {
    if (EmailView.instance) {
        return EmailView.instance;
    }
    EmailView.instance = this;

    this.currentEntityIDs = null;

    this.getCurrentEntityIDs = function() {
        if ($('#email-view-tab:visible').length > 0) {
            return this.currentEntityIDs;
        } else {
            return null;
        }
    }

    this.show = function(ids) {
        this._show(ids);

        // triggering tab will set a new context
        new ActionManager().switchToTab('email-view');
    }
    this._show = function(ids) {
        ids = isArray(ids)?ids:[ids];

        this.currentEntityIDs = ids;

        var url = "/jsp/search/email_compose.jsp?newEmail=true&ID=" + ids.join("&ID=");
        this._load(url);

        popupIfNotLoggedIn();
    }
    this._load = function(url) {
        var actionMgr = new ActionManager();
        actionMgr.throbTab('email-view');

        var me = this;
        var callback = function() {
            me.initEmailForm();
            actionMgr.unthrobTab('email-view');
        }
        new AHAH().load($('#email-tab')[0],url, callback);
    }

    // used by find recipients popup
    this._render = function(html) {
        $('#email-tab')[0].innerHTML = html;
        this.initEmailForm();
    }

    this.initEmailForm = function() {
        var emailURL = '/jsp/email/email_compose.jsp?ID=' + this.currentEntityIDs.join('&ID=');
        $("#email-file-attach").html('<a href="' + emailURL + '">Attach Files from Your Computer</a>');

        $("#email-file-attach").find("a").click(function() {
            var newURL = emailURL;

            var rewriteFields = ['additionalRecipientString', 'subject', 'message'];
            for (var i = 0; i < rewriteFields.length; i++) {
                if (document.forms.emailForm[rewriteFields[i]].value) {
                    newURL += "&" + rewriteFields[i] + "=" + escape(document.forms.emailForm[rewriteFields[i]].value);
                }
            }
            this.href = newURL;
        });

        var form = new AJAXForm("emailForm");
        form.getAction = function () {
            return "/jsp/search/email_compose.jsp";
        }
        form.handleSuccess = function(req) {
            if (req.responseText.indexOf("ERRORMODE") > 0) {
                $('#email-tab').html(req.responseText);
                new EmailView().initEmailForm();
            } else {
                $('#email-tab').html("<div id='container'><div id='main'><h2>Your Email Has Been Sent.</h2></div></div>");
                var html = "<ul style=\"margin-top: 3px; padding-left: 0; list-style: none;\">";
                //html += "<li><a href=\"javascript:new ActionManager().emailSelected()\">Send another email</a></li>";
                html += "<li><a href=\"javascript:new ActionManager().switchToTab('list-view')\">Back to List View</a></li>";
                html += "<li><a href=\"javascript:new ActionManager().switchToTab('map-view')\">Back to Map View</a></li>";
                html += "</ul>";
                $('#email-tab #main').append(html);
                new EmailView().currentEntityIDs = null;
                new WindowState().save();
            }

            new ActionManager().unthrobTab("email-view");

        }
        $("#emailForm").submit(function(){
            if (recipientsWindow) {
                recipientsWindow.close();
            }

            new ActionManager().throbTab("email-view");
            form.submit();
            return false;
        });
    }
}

function ReportsView(dontRequireLogin) {
    if (ReportsView.instance) {
        return ReportsView.instance;
    }
    ReportsView.instance = this;

    this.currentEntityIDs = null;
    this.noLogin = dontRequireLogin||false;

    this.getCurrentEntityIDs = function() {
        if ($('#reports-view-tab:visible').length > 0) {
            return this.currentEntityIDs;
        } else {
            return null;
        }
    }

    this.show = function(ids) {
        this._show(ids);

        // triggering tab will set a new context
        new ActionManager().switchToTab('reports-view');
    }
    this._show = function(ids) {
        ids = isArray(ids)?ids:[ids];

        this.currentEntityIDs = ids;

        var url = "/jsp/search/report_choosetype.jsp?compareReportType=false&action=newReport&initialLoad=initialLoad&ID=" + ids.join("&ID=");
        var map = new MapManager().getMap();
        if (map) {
            var overlayControl = map.getOverlayControl();
            if (overlayControl) {
                var layers = overlayControl.getLayers();
                for (var name in layers) {
                    if (layers[name]) {
                        url += "&layer=" + name;
                    }
                }
                if (layers.DEMOGRAPHICS) {
                    var areaType = overlayControl.getDemographicAreaType();
                    if (areaType) {
                        url += "&demographicAreaType=" + areaType;
                        url += "&demographic=" + overlayControl.getDemographicType();
                    }
                }
            }
        }
        new AHAH().load($('#reports-tab')[0],url,null,true);

        if (ids.length > 1 && !this.noLogin) {
            popupIfNotLoggedIn();
        }
    }
}
function ExportView() {
    if (ExportView.instance) {
        return ExportView.instance;
    }
    ExportView.instance = this;

    this.currentEntityIDs = null;

    this.show = function(ids) {
        this._show(ids);

        // triggering tab will set a new context
        new ActionManager().switchToTab('export-view');
    }
    this._show = function(ids) {
        ids = isArray(ids)?ids:[ids];

        this.currentEntityIDs = ids;
        // TODO: HANDLE HETEROGENEOUS IDS
        var exportedEntityType = "LISTING";
        var searchType = new CriteriaManager().getSearchType();
        if (searchType == SearchType.COMPARABLES || searchType == SearchType.ARCHIVED_LISTINGS) {
            exportedEntityType = "COMPARABLE";
        }
        var url = "/jsp/search/export.jsp?exportType=1&exportedEntityType={0}&ID={1}".format(exportedEntityType,ids.join("&ID="));
        var callback = function() {
            //a little glue to get legacy check-all to work
            new CheckAllHandler("toggleFields","fieldNames");
            CheckAllHandler.handlers["toggleFields"].initialize();

            window["popUpExportWindow"] = function() {
                // Open the popup window
                window.open('','exportPopup','toolbar=no,location=no,directories=no,status=no,menubar=no,resizable=no,copyhistory=no,scrollbars=no,width=400,height=250');
                document.exportForm.target = 'exportPopup';
            }
        }
        new AHAH().load($('#export-tab')[0],url,callback,true);
        popupIfNotLoggedIn();
    }
}

function AddToCatalogView() {
    if (AddToCatalogView.instance) {
        return AddToCatalogView.instance;
    }
    AddToCatalogView.instance = this;

    this.currentEntityIDs = null;

    this.getCurrentEntityIDs = function() {
        if ($('#catalog-view-tab:visible').length > 0) {
            return this.currentEntityIDs;
        } else {
            return null;
        }
    }

    this.show = function(ids) {
        this._show(ids);

        // triggering tab will set a new context
        new ActionManager().switchToTab('catalog-view');
    }
    this._show = function(ids) {
        ids = isArray(ids)?ids:[ids];

        this.currentEntityIDs = ids;

        var url = "/jsp/search/console_addto_catalog.jsp?action=prepareForAdd&ID=" + ids.join("&ID=");
        var callback = function() {
            var form = new AJAXForm("catalogForm");
            form.handleSuccess = function(req) {
                $('#catalog-tab').html(req.responseText);
                $('#addToCatalogGoBackLink').html("Back to list view");
                $('#addToCatalogGoBackLink').attr('href','javascript:new ActionManager().viewAll()');

                new AddToCatalogView().currentEntityIDs = null;
                new WindowState().save();
            }
            document.forms.catalogForm.action = '/jsp/search/console_addto_catalog_confirm.jsp';
            $(document.forms.catalogForm.addToCatalogButton)[0].onclick = function(){form.submit()};
            initAddToCatalog();
        }

        $('#catalog-tab').html("");
        new ScriptManager().loadCatalogScripts(function() {
            new AHAH().load($('#catalog-tab')[0],url, callback);
        });

        popupIfNotLoggedIn();
    }
}

function ActionManager() {
    if (ActionManager.instance) {
        return ActionManager.instance;
    }
    ActionManager.instance = this;

    this.selectedEntityIDHash = new Array();

    this.viewSelected = function() {
        if (this._viewSelected()) {
            new ListView().show(1);
            var currentTabID = new TabsManager().getActiveTabID();
            if (currentTabID.indexOf("map") == -1) {
                this.switchToTab("list-view");
            }

            var map = new MapManager();
            map.showPolygon(false);
            map.setZoomFromResults(true);
            map.show();
        }
    }
    this._viewSelected = function() {
        if (this.hasSelectedEntities()) {
            new EntityManager().overrideEntityIDs = this.getSelectedEntityIDs();
            this.updateDisplay();
            $('#search-filters,#primary-sitelink-filters').addClass("disabled");
            $('#left').append('<div id="criteria-disabler" class="disabled-overlay"></div>');

            $('#list-view').find('.hollow').removeClass("hidden");
            $('#list-view').addClass("blue-gray");

            new WindowState().save();
            return true;
        } else {
            return false;
        }
    }

    this.viewAll = function() {
        var tabToShow = "list-view";
        var currentTabID = new TabsManager().getActiveTabID();
        if (currentTabID.indexOf("map") != -1) {
            tabToShow = "map-view";
        }
        var list = new ListView();
        new EntityManager().overrideEntityIDs = null;
        this.updateDisplay();
        $('#criteria-disabler').remove();
        $('#search-filters,#primary-sitelink-filters').removeClass("disabled");

        $('#list-view').find('.hollow').addClass("hidden");
        $('#list-view').removeClass("blue-gray");

        list.show(1);
        this.switchToTab(tabToShow)

        var map = new MapManager();
        map.showPolygon(true);
        map.setZoomFromResults(true);
        map.show();

        new WindowState().save();
    }

    this.compareSelected = function() {
        var url = "/jsp/reports/report_comparelistings.jsp?compareReportType=true&ID={0}".format(this.getSelectedEntityIDs().join("&ID="));
        window.open(url,'comparePopUp','toolbar=yes,location=yes,directories=no,status=yes,menubar=yes,resizable=yes,copyhistory=no,scrollbars=yes,width=800,height=600');
    }
    this.emailSelected = function() {
        this.email(this.getSelectedEntityIDs());
    }
    this.email = function(ids) {
        new EmailView().show(ids);
    }
    this.exportSelected = function() {
       new ExportView().show(this.getSelectedEntityIDs())
    }
    this.createReportWithSelected = function() {
        this.createReport(this.getSelectedEntityIDs());
    }
    this.createReport = function(ids) {
        new ReportsView().show(ids);
    }

    this.addSelectedToCatalog = function() {
        this.addToCatalog(this.getSelectedEntityIDs());
    }
    this.addToCatalog = function(ids) {
        new AddToCatalogView().show(ids);
    }

    this.switchToTab = function(id) {
        //first hide the ones to be hidden
        var tabs = new TabsManager();
        tabs.disableTab('email-view').disableTab('reports-view').disableTab('catalog-view');
        if (DEBUG) debug("Switching to tab: " + id);
        tabs.enableTab(id).triggerTab(id);
        new MapManager().setVisible((new TabsManager().getActiveTabID() == 'map-view'));
    }
    this.getActiveTab = function() {
        return new TabsManager().activeTab();
    }
    this.throbTab = function(id) {
        var tabs = $("#view-tabs");
        $("#{0}-tab > a".format(id)).prepend("<img src='/images/search/loading_circle.gif' class='tab-loading-graphic' />").parent().addClass("throbbing");
        tabs.find("li a span").hide();
    }
    this.unthrobTab = function(id) {
        var tabs = $("#view-tabs");
        $("#{0}-tab > a > img.tab-loading-graphic".format(id)).fadeOut("fast", function() { $(this).remove() });
        tabs.find("li a span").fadeIn();
        tabs.find(".throbbing").removeClass("throbbing");
    }

    this.isSelected = function(entityID) {
        return this.selectedEntityIDHash[entityID];
    }

    this.toggleSelected = function(entityID) {
        if (this.selectedEntityIDHash[entityID]) {
            this.deselectEntity(entityID);
        } else {
            this.selectEntity(entityID);
        }
    }

    this.getSelectedEntityIDs = function(overrideSearchType) {
        var searchType = overrideSearchType||new CriteriaManager().getSearchType();

        var ids = new Array();
        for (var i in this.selectedEntityIDHash) {
            if (this.selectedEntityIDHash[i] == searchType) {
                ids.push(i);
            }
        }
        return ids;
    }

    this.hasSelectedEntities = function() {
        var searchType = new CriteriaManager().getSearchType();

        var hasSelected = false;
        for (var i in this.selectedEntityIDHash) {
            if (this.selectedEntityIDHash[i] == searchType) {
                hasSelected = true;
                break;
            }
        }
        return hasSelected;
    }

    this.selectEntity = function(entityID, doNotUpdateDisplay, overrideSearchType) {
        if (!this.selectedEntityIDHash[entityID]) {
            this.selectedEntityIDHash[entityID] = overrideSearchType||new CriteriaManager().getSearchType();
            this.updateHighlight(entityID, true);

            if (! doNotUpdateDisplay) {
                this.updateDisplay();
                this.dazzle();

                new WindowState().save();
            }
        }
    }
    this.selectEntities = function(entityIDs, overrideSearchType) {
        for (var i = 0; i < entityIDs.length; i++) {
            this.selectEntity(entityIDs[i], true, overrideSearchType);
        }
        this.updateDisplay();
        this.dazzle();

        new WindowState().save();
    }

    this.deselectEntity = function(entityID, doNotUpdateDisplay) {
        this.updateHighlight(entityID, false);
        if (this.selectedEntityIDHash[entityID]) {
            delete this.selectedEntityIDHash[entityID];

            if (! doNotUpdateDisplay) {
                this.updateDisplay();
                this.dazzle();

                new WindowState().save();
            }
        }
    }

    this.deselectEntities = function(entityIDs) {
        for (var i = 0; i < entityIDs.length; i++) {
            this.deselectEntity(entityIDs[i],true);
        }
        this.updateDisplay();
        this.dazzle();

        new WindowState().save();
    }

    this.clearSelected = function() {
        var unhighlightConfirm = confirm("Unhighlight all items?");
        if (unhighlightConfirm) {
            if (this.hasSelectedEntities()) {
                if (new EntityManager().overrideEntityIDs) {
                    var currentTabID = new TabsManager().getActiveTabID();
                    var tabToShow = "list-view";
                    if (currentTabID.indexOf("map") != -1) {
                        var tabToShow = "map-view";
                    }
                    this.viewAll(tabToShow);
                } else {
                    var ids = new Array();
                    for (var id in this.selectedEntityIDHash) {
                        ids.push(id);
                    }
                    this.deselectEntities(ids);
                }
                this.selectedEntityIDHash = new Array();
                this.updateDisplay();
                this.dazzle();
                new WindowState().save();
            }
        }
    }

    this.highlightSelected = function() {
        var self = this;
        var selected = '|' + this.getSelectedEntityIDs().join('|') + '|';
        $("#list-results").find("input:checkbox").each(function() {
            this.checked = selected.indexOf('|' + this.value + '|') > -1;
            if (this.checked) {
                self.updateHighlight(this.value, true);
            }
        });
        this._updateCheckAll();
    }

    this.dazzle = function() {
        if (this.hasSelectedEntities()) {
            $('#highlight-message').addClass("hidden");
            $('#highlight-fade').fadeIn(500);
            $("#highlight-bar").find("ul,p").removeClass("hidden");
            $("#sitelink-header").find("ul").removeClass("hidden");
        } else {
            $("#highlight-bar").find("ul,p").addClass("hidden");
            $("#sitelink-header").find("ul").addClass("hidden");
            $('#highlight-fade').fadeOut(500);
            $('#highlight-message').removeClass("hidden");
        }
    }

    this.updateHighlights = function() {
        for (var id in this.selectedEntityIDHash) {
            this.updateHighlight(id,true);
        }
    }

    this.updateHighlight = function(entityID, highlighted) {
        var selector = '#list-result-{0},#bubble-title-{0},#cluster-detail-{0},#overview-title,#details-highlight'.format(entityID);
        var className = 'highlighted';
        if (highlighted) {
            $(selector).addClass(className);
        } else {
            $(selector).removeClass(className);
        }
        $(selector).find("input:checkbox[value={0}]".format(entityID)).each(function(){this.checked=highlighted});

        //change the map icon
        var map = new MapManager().getMap();
        var mappable = new EntityManager().getEntity(entityID);
        if (map && mappable) {
            mappable.setHighlighted(highlighted);
        }
    }

    this._updateCheckAll = function() {
        var countChecked = 0;
        var countTotal = 0;

        $("#list-results").find("input:checkbox").each(function() {
            countTotal++;
            if (this.checked) {
                countChecked++;
            }
        });

        $("#check-all")[0].checked = (countTotal && countChecked == countTotal);
    }

    this.updateDisplay = function() {
        var numberOfIDs = this.getSelectedEntityIDs().length;

        var desc = numberOfIDs + ' ' + (numberOfIDs != 1 ? 'items' : 'item');
        $('#num-selected').text(desc + ' selected');
        $('#num-selected-sitelink').text(numberOfIDs + ' selected');
        if (new EntityManager().overrideEntityIDs) {
            $('#view-all').removeClass("hidden");
            $('#view-highlighted').addClass("hidden");
        } else {
            $('#view-all').addClass("hidden");
            $('#view-highlighted').removeClass("hidden");
        }
        this._updateCheckAll();
    }
}

function Point(lat, lng) {
    this.latitude = lat;
    this.longitude = lng;
    this.lat = function() { return this.latitude; }
    this.lng = function() { return this.longitude; }
}

function MapManager(containerID, overlayOptions) {
    if (MapManager.instance && MapManager.instance.containerID) {
        return MapManager.instance;
    }
    MapManager.instance = this;
    var mapManager = this;
    this.containerID = containerID;
    this.map = null;

    this.redraw = {
            'needs':true,
            'centerLatLng':null,
            'zoom':null,
            'reset':function() {this.needs = false; this.centerLatLng = null; this.zoom = null;}
    };

    this._overlayOptions = overlayOptions;

    this.isVisible = false;
    this.lastSearchRequest = null;

    this._entityIDsToMap = new Array();
    this._clusters = new Array();

    this._mappableBounds = null;

    this._getZoomFromResults = true;

    this._hiddenPolygon = false;

    // tell the MapManager to re-zoom the map based on the next set of results
    this.setZoomFromResults = function(doit) {
        this._getZoomFromResults = doit;
    }

    this.closeInfoWindow = function() {
        if (this.map) {
            this.map.closeInfoWindow();
        }
    }
    this.openInfoWindow = function(id) {
        var iwid = id.split(':');
        if (iwid.length > 1) {
            switch (iwid[0]) {
            case 'M':
                var eid = iwid[1];
                
                var self = this;
                function open(entity) {
                    if (!self.map.hasMappableOverlay(entity)) {
                        self.map.addOverlaysForMappables(entity);
                    }
                    entity.openInfoWindow();
                }

                var entity = new EntityManager().getEntity(eid);
                if (!entity) {
                    new EntityManager().retrieveEntityAndCallBack(eid, function() {
                        open(new EntityManager().getEntity(eid));
                    });
                } else {
                    open(entity);
                }
                
                break;
                
            case 'C':
                var cid = iwid[1].split(',');
                var data = {
                    latitude: parseFloat(cid[0]), 
                    longitude: parseFloat(cid[1]), 
                    clusterSize: parseInt(cid[2])
                };
                data.bounds = {n: data.latitude, s: data.latitude, e: data.longitude, w: data.longitude };
                var cluster = new Cluster(data);
                if (!this.map.hasClusterOverlay(cluster)) {
                    this.map.addOverlaysForClusters(cluster);
                }
                cluster.openInfoWindow();
                
                break;
            }
        }
    }

    this.showPolygon = function(showit) {
        this._hiddenPolygon = !showit;
        if (this.map) {
            this.map.showPolygon(showit);
        }
    }

    this._redrawMappables = function() {
        if (this.map) {
            this.map.replaceOverlaysForMappables(new EntityManager().getEntities(this._entityIDsToMap));
            this.map.replaceOverlaysForClusters(this._clusters);
        }
    }

    this._postPanZoom = null;

    this.setVisible = function(vis) {
        if (vis && this.redraw.needs) {
            this._show(this.redraw.centerLatLng, this.redraw.zoom);
        }
        this.isVisible = vis;

        if (vis) {
            if (this.map && this._postPanZoom) {
                CMap.onPostPanZoom = this._postPanZoom;
                this._postPanZoom = null;
            }

            // if the map is hidden, and the user resizes the window, the map doesn't detect
            // the change, so we'll tell it to redraw when it becomes visible again.
            if (this.map) {
                this.map.resized();
            }
        } else {
            // suspend any events while the map is hidden
            if (this.map && !this._postPanZoom) {
                this._postPanZoom = CMap.onPostPanZoom;
                CMap.onPostPanZoom = function() {};
            }
        }
    }

    this.show = function(centerLatLng, zoom) {
        if (DEBUG) debug("MapManager.show(" + centerLatLng + "," + zoom + ")");

        this.closeInfoWindow();
        if (this.isVisible) {
            this._show(centerLatLng, zoom);
        } else {
            this.redraw.needs = true;
            if (centerLatLng) {
                this.redraw.centerLatLng = centerLatLng;
            }
            if (zoom) {
                this.redraw.zoom = zoom;
            }
        }
    }

    this.throb = function() {
        //create a new hidden div and put the loading graphic in it
        var toThrob = $("#map-view").find("#map-loading-message");
        if (toThrob.size() < 1) {
            $('#map-view').append('<div id="map-loading-message"><img src="/images/search/loading_circle.gif"/> Updating Map...</div>')
        } else {
            toThrob.show();
        }
    }
    this.unthrob = function() {
        new ActionManager().unthrobTab("map-view")

        //hide the div
        $("#map-view").find("#map-loading-message").hide();
    }

    this._mapCallback = function(xml, centerLatLng, zoom) {
        var exceptions;
        var errorMessage;
        if (xml) {
            exceptions = xml.documentElement.getElementsByTagName("exceptions");
        }
        if (exceptions && exceptions.length) {
            try {
                errorMessage = exceptions[0].childNodes[0].childNodes[0].nodeValue;
            } catch (e) {
            }
        }
        if (errorMessage && DEBUG) {
            debug("_mapCallback server error: " + errorMessage);
        }

        var openInfoWindowID = this.map.getOpenInfoWindowID();

        this._entityIDsToMap = new Array();

        var entitiesNode = xml.documentElement.getElementsByTagName("entities");
        if (entitiesNode && entitiesNode.length > 0) {
            var entities = eval(entitiesNode[0].childNodes[0].nodeValue);
            if (entities) {
                for (var i = 0; i < entities.length; i++) {
                    // we're not going to clear the infowindow marker, so don't add it
                    if (openInfoWindowID != ('M:' + entities[i].ID)) {
                        this._entityIDsToMap.push(entities[i].ID);
                    }
                }
            }
        }

        this._clusters = null;

        var clustersNode = xml.documentElement.getElementsByTagName("clusters");
        if (clustersNode && clustersNode.length > 0) {
            var clusterData = eval(clustersNode[0].childNodes[0].nodeValue);
            this._clusters = new Array();
            for (var i = 0; i < clusterData.length; i++) {
                this._clusters.push(new Cluster(clusterData[i]));
            }
        }

        this._mappableBounds = null;

        var boundsElements = xml.documentElement.getElementsByTagName("bounds");
        if (boundsElements && boundsElements.length) {
            var el = boundsElements[0];

            this._mappableBounds = {
                north: parseFloat(el.getAttribute("north")),
                south: parseFloat(el.getAttribute("south")),
                east: parseFloat(el.getAttribute("east")),
                west: parseFloat(el.getAttribute("west"))
            };

            // if we have a polygon, we always want to show all of it
            if (!this._hiddenPolygon) {
                var poly = this._getLocationPolygon();
                if (poly) {
                    for (var i = 0; i < poly.points.length; i++) {
                        if (poly.points[i].lat() > this._mappableBounds.north) {
                            this._mappableBounds.north = poly.points[i].lat();
                        }
                        if (poly.points[i].lat() < this._mappableBounds.south) {
                            this._mappableBounds.south = poly.points[i].lat();
                        }
                        if (poly.points[i].lng() < this._mappableBounds.west) {
                            this._mappableBounds.west = poly.points[i].lng();
                        }
                        if (poly.points[i].lng() > this._mappableBounds.east) {
                            this._mappableBounds.east = poly.points[i].lng();
                        }
                    }
                }
            }
        }

        if (xml.documentElement.getAttribute('filteredByViewport') == 'true') {
            CMap.onPostPanZoom = function() {
                new MapManager()._show();
                new WindowState().save();
            };
        } else {
            CMap.onPostPanZoom = function() {new WindowState().save()}
        }

        if (this._mappableBounds) {
            // will probably trigger a new onPostPanZoom event, but we want to swallow that one so we don't trigger a callback w/ the new bounds.
            var postPanZoom = CMap.onPostPanZoom;
            CMap.onPostPanZoom = function() {
                CMap.onPostPanZoom = postPanZoom;
                new WindowState().save();
            }
            this.map.zoomToFit(this._mappableBounds.north, this._mappableBounds.south, this._mappableBounds.east, this._mappableBounds.west);

            if (this._clusters) {
                // google zooms to fit the entire bounds, but is usually too conservative which looks bad when clustering, so we're gonna zoom in one more level
                this.map.setCenter(this.map.getCenter(), this.map.getZoom() + 1);
            }
        } else if (centerLatLng) {
            this.map.setCenter(centerLatLng, zoom);
        }

        this.map.replaceOverlaysForMappables(new EntityManager().getEntities(this._entityIDsToMap), true);
        this.map.replaceOverlaysForClusters(this._clusters, true);


        if (this.onNextMappables) {
            this.onNextMappables();
            this.onNextMappables = null;
        }

        setTimeout(this.unthrob, 500);
    }

    this._show = function(centerLatLng, zoom) {
        this.redraw.reset();

        this.throb();

        new ScriptManager().loadMapScripts(function() {
            var mapMgr = new MapManager();

            var callMap = function() {
                var parameters = new Array();

                var bounds = mapMgr.map.getBounds();
                parameters.push(new Parameter(AJAXMethodCaller.DOUBLE_ARRAY_TYPE,
                        bounds ? [bounds.ne.lat(), bounds.ne.lng(), bounds.sw.lat(), bounds.sw.lng()] : null));

                var mapW = $('#' + mapMgr.containerID).width();
                var mapH = $('#' + mapMgr.containerID).height();
                parameters.push(new Parameter(AJAXMethodCaller.INTEGER_ARRAY_TYPE, [mapW, mapH]));

                parameters.push(new Parameter("java.lang.Boolean", mapMgr._getZoomFromResults));

                var entityManager = new EntityManager();
                if (entityManager.overrideEntityIDs && entityManager.overrideEntityIDs.length > 0) {
                    parameters.push(new Parameter(AJAXMethodCaller.STRING_ARRAY_TYPE, entityManager.overrideEntityIDs));
                }
                if (mapMgr.lastSearchRequest) {
                    mapMgr.lastSearchRequest.abort();
                }

                mapMgr.lastSearchRequest = entityManager.getResultsAndCallBack("map",parameters,
                    function(xml){
                        new MapManager()._getZoomFromResults = false;
                        new MapManager()._mapCallback(xml, centerLatLng, zoom);
                        new ActionManager().updateHighlights();
                    },
                    function() {
                        new MapManager().unthrob();
                        showUserError("We're sorry, but there was an unexpected error.");
                    }
                );
            }

            if (!mapMgr.map) {
                CMap.onSearchByPolygon = function(polygon) {
                    new MapManager().addSearchByPolygonCriteria(polygon);
                }
                // This will get called immediately after the map is initialized
                CMap.onPostPanZoom = function() {
                    callMap();

                    CMap.onPostPanZoom = function() { new WindowState().save() }
                }
                CMap.onOverlayChange = function() {
                    new WindowState().save();
                }
                if (centerLatLng) {
                    mapMgr.map = new CMap(mapMgr.containerID, mapMgr._overlayOptions, centerLatLng.lat(), centerLatLng.lng(), zoom);
                } else {
                    mapMgr.map = new CMap(mapMgr.containerID, mapMgr._overlayOptions);
                }
                if (!mapMgr._hiddenPolygon) {
                    mapMgr.map.showPolygon(true);
                }

                // call the listener stack
                mapMgr._onMapCreate(mapMgr.map);
                // but only call them once
                mapMgr._onMapCreate = function() {};
                
            } else {
                if (centerLatLng) {
                    mapMgr.map.setCenter(centerLatLng, zoom);
                }
                callMap();
            }

            mapMgr._addLocationPolygonToMap();

            if (!mapMgr.isVisible) {
                mapMgr.map.reset()
            }
        });
    }

    this._onMapCreate = function(cmap) {}
    this.addMapCreationListener = function(listener) {
        if (this.map) {
            listener(this.map);
        } else {
            var listenerStack = this._onMapCreate;
            this._onMapCreate = function(cmap) {
                listenerStack(cmap);
                listener(cmap);
            }
        }
    }

    this._getLocationPolygon = function() {
        var poly = null;

        var locationCriteria = new CriteriaManager().getCurrentCriterionByName("Location");
        if (locationCriteria) {
            var items = locationCriteria.locationItems;
            if (items) {
                for (var i = 0; i < items.length; i++) {
                    if (items[i].locationType == 'polygon') {
                        poly = items[i].polygon;
                        break;
                    }
                }
            }
        }

        return poly;
    }

    this._addLocationPolygonToMap = function() {
        if (!this._hiddenPolygon) {
            var poly = this._getLocationPolygon();
            if (poly) {
                this.map.setUserShape(poly);
            }
        }
    }

    this.addSearchByPolygonCriteria = function(polygon) {
        //frist get the location criteria guy
        var mgr = new CriteriaManager();
        var locationCriterion = mgr.getCurrentCriterionByName("Location");

        //if there is no guy, then create him
        if (! locationCriterion) {
            mgr.addCurrentCriterionByName("Location");
            locationCriterion = mgr.getCurrentCriterionByName("Location");
        }

        //add the map data to the guy
        locationCriterion.addPolygonAsCriteria(polygon);

        new WindowState().save();
    }

    this.showEntity = function(id) {
        var entity = new EntityManager().getEntity(id);
        if (entity) {
            new ActionManager().switchToTab("map-view");

            var self = this;
            new MapManager().onNextMappables = function() {
                if (!self.map.hasMappableOverlay(entity)) {
                    self.map.addOverlaysForMappables(entity);
                }
                self.openInfoWindow(id);
            }
            new MapManager().show(new Point(entity.latitude, entity.longitude), 16);
        }
    }

    this.getMap = function() {
        return this.map;
    }

    // will run once, the next time we get mappables from the server
    this.onNextMappables = function() { }
}

function ListView(containerID) {
    if (ListView.instance) {
        return ListView.instance;
    }
    ListView.instance = this;

    this.sortColumn = "";
    this.sortReverse = false;

    this.expandAll = false;

    this.containerID = containerID;
    this.isVisible = false;

    this.lastSearchRequest = null;

    this.setExpandAllFromCookie = function() {
        this.expandAll = false;
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            cookies[i] = cookies[i].trim();
            if (cookies[i].indexOf('ListViewExpandAll=') == 0) {
                this.expandAll = cookies[i].substring('ListViewExpandAll='.length) == 'true';
            }
        }
    }
    this.setExpandAllFromCookie();

    this.setVisible = function(vis) {
        this.isVisible = vis;
    }

    this.setExpandAll = function(expanded) {
        this.expandAll = expanded;
        if (this.expandAll) {
            document.cookie='ListViewExpandAll=true; expires=1 Jan 2999; path=/';
        } else {
            document.cookie='ListViewExpandAll=false; path=/';
        }
    }

    this.toggleSummaryForAll = function() {
        var start = new Date().getTime();
        if (this.expandAll) {
            this.hideAllSummaries();
        } else {
            this.showAllSummaries();
        }
        if (DEBUG) debug('<strong>Toggle Summaries: </strong>' + (new Date().getTime() - start) + ' ms');
    }
    this.showAllSummaries = function(dontFetch) {
        this.setExpandAll(true);
        $("#expand-all").addClass("expanded");

        var listResults = $("#list-results");
        listResults.find("> div").addClass("expanded");
        listResults.find(".line-item").addClass("hidden");
        listResults.find(".summary").removeClass("hidden");

        $("#list-results-header").find(".sort-header").addClass("hidden");

        if (!dontFetch) {
            var entitiesToFetch = new Array();
            listResults.find(".summary-placeholder").each(function() {
                var id = $(this).attr("rel");
                $('#list-result-' + id).find(".line-item").removeClass("hidden");
                entitiesToFetch.push(id);
            });
            this.renderSummaries(entitiesToFetch);
        }
    }
    this.hideAllSummaries = function() {
        this.setExpandAll(false);
        $("#expand-all").removeClass("expanded");

        var listResults = $("#list-results");
        listResults.find("> div").removeClass("expanded");
        listResults.find(".line-item").removeClass("hidden");
        listResults.find(".summary").addClass("hidden");

        $("#list-results-header").find(".sort-header").removeClass("hidden");
    }

    this.toggleSummary = function(entityID) {
        var listResult = $("#list-result-" + entityID);
        listResult.toggleClass("expanded");
        listResult.find(".line-item").toggleClass("hidden");
        listResult.find(".summary").toggleClass("hidden");

        if (listResult.find(".summary-placeholder").length > 0) {
            this.renderSummaries([entityID]);
        }
    }

    this.styleSortCols = function(dontExpand) {
        $("#list-results-header").find(".sort").find("img").remove();
        $("#list-results-header").find(".sort-header").removeClass("sort");

        var sortCol = $("#header-" + this.sortColumn);
        sortCol.addClass("sort").append(this.sortReverse ? "<img src='/images/search/sort-up.png' alt='Sorted Up' />" : "<img src='/images/search/sort-down.png' alt='Sorted Down' />");

        if (sortCol.length == 0) {
            if (!dontExpand && !this.expandAll) {
                this.showAllSummaries(true);
            }
        }

        var self = this;
        $('#list-sort .list-sort-menu').each(function() {
            for (var i = 0; i < this.options.length; i++) {
                if (this.options[i].value == self.sortColumn) {
                    this.selectedIndex = i;
                    break;
                }
            }
        });
        $('#list-sort-direction')[0].selectedIndex = this.sortReverse ? 1 : 0;
    }

    this.sort = function(sortBy, sortReverse, dontDoSearch) {
        if (sortReverse == undefined) {
            if (this.sortColumn == sortBy) {
                this.sortReverse = !this.sortReverse;
            } else {
                this.sortReverse = false;
            }
        } else {
            this.sortReverse = sortReverse;
        }
        this.sortColumn = sortBy;

        this.styleSortCols();

        if (!dontDoSearch) {
            this.show(1);
        }
    }

    this.show = function(page) {
        $("#check-all")[0].checked = false;

        var parent = $("#list-results");
        parent.empty();
        parent.append('<div class="tab-cover"></div><img src="/images/search/loading_graphic.gif" alt="Loading" class="beavis" />');

        var mgr = new EntityManager();

        var parameters = new Array();
        if (mgr.overrideEntityIDs && mgr.overrideEntityIDs.length > 0) {
            parameters.push(new Parameter(AJAXMethodCaller.STRING_ARRAY_TYPE,mgr.overrideEntityIDs));
        }

        var perPage = new Paginator().itemsPerPage;
        var end = (page * perPage);
        var start = end - perPage;
        parameters.push(new Parameter("java.lang.Integer",start));
        parameters.push(new Parameter("java.lang.Integer",end));

        var sortString = "null";
        if (this.sortColumn) {
            sortString = this.sortColumn;
            if (this.sortReverse) {
                sortString += '|0';
            } else {
                sortString += '|1';
            }
        }
        parameters.push(new Parameter("java.lang.String",sortString));

        var self = this;
        var callback = function(xml) {
            self.lastSearchRequest = null;

            var parent = $("#list-results");
            parent.empty();

            //Remove loading graphic from blurred input
            $("#search-filters,#primary-sitelink-filters").find("li .input-loading").removeClass("input-loading");

            var listHTML = xml.documentElement.childNodes[1].childNodes[0].nodeValue;
            if (listHTML) {
                parent.html(listHTML);
            }
            self.styleResults();
            renderTime = new Date().getTime();

            var root = xml.documentElement;
            var count = root.getAttribute("count");
            $("#number-results").find("span").empty()
            $("#number-results").find("span").html(count)

            if (DEBUG) {
                var timeStr = '<strong>Query:</strong> ' + root.getAttribute("seconds") + ' s&nbsp;&nbsp;&nbsp;&nbsp;<strong>Query + Load:</strong> ' + (queryAndLoadTime - callTime)/1000 + ' s&nbsp;&nbsp;&nbsp;&nbsp;<strong>Render:</strong> ' + (renderTime - queryAndLoadTime)/1000 + ' s&nbsp;&nbsp;&nbsp;&nbsp;<strong>Total:</strong> ' + (new Date().getTime() - callTime)/1000 + ' s';
                debug("" + timeStr + "");
            }

            new Paginator().setCurrentPage(page);
            new Paginator().updateDisplay(eval(count));

            new ActionManager().highlightSelected();

            new WindowState().save();
            new WindowState().updateTitle();
        }

        if (this.lastSearchRequest) {
            this.lastSearchRequest.abort();
        }
        this.lastSearchRequest = new EntityManager().getResultsAndCallBack("search",parameters,callback,
                function() {
                    $("#search-filters,#primary-sitelink-filters").find("li .input-loading").removeClass("input-loading");
                    $("#list-results").empty();
                    $("#number-results span").html("0")
                    new Paginator().updateDisplay(0);
                    showUserError("We're sorry, but there was an unexpected error.");
                });
    }

    this.styleResults = function() {
        var evenOdd = "even";
        $("#list-results").find("> div").each(function(i) {
            if (evenOdd == "even") {
                evenOdd = "odd";
            } else {
                evenOdd = "even";
            }
            $(this).addClass(evenOdd);
        });
    }

    this.renderSummaries = function(entityIDs) {
        if (!entityIDs || entityIDs.length == 0) return;

        var mgr = new EntityManager();

        var parameters = new Array();
        parameters.push(new Parameter(AJAXMethodCaller.STRING_ARRAY_TYPE, entityIDs));

        var self = this;
        var callback = function(xml) {
            var start = new Date().getTime();
            var root = xml.documentElement;
            $(root).children().each(function() {
                var id = this.getAttribute('id');
                var summaryHTML = this.childNodes[0].nodeValue;
                var listResult = $("#list-result-" + id);
                listResult.find(".line-item").addClass('hidden');
                listResult.find(".summary-placeholder").replaceWith(summaryHTML);
            });
            if (DEBUG) debug('<strong>Render Summaries: </strong>' + (new Date().getTime() - start) + ' ms');
        }
        mgr.getResultsAndCallBack("populateSummaries", parameters, callback, function() {showUserError("We're sorry, but there was an unexpected error.");});
    }
}


function CriteriaManager() {
    if (CriteriaManager.instance) {
        return CriteriaManager.instance;
    }
    CriteriaManager.instance = this;

    this.allCriteria = new Array();
    this.allCriteriaIndex = new Array();
    this.currentCriteria = new Array();
    this.currentCriteriaIndex = new Array();

    this.isCriterionApplicable = function(name) {
        return this.allCriteriaIndex[name].isApplicable(this.getSelectedPropertyTypes());
    }

    //get a hash of all key/values of user entered search criteria.
    //keys are strings, values are arrays
    this.getCriteriaValues = function() {
        var values = new Array();
        for (var i=0;i<this.currentCriteria.length;i++) {
            var criterion = this.currentCriteria[i];
            var criterionValues = criterion.getCriterionValues();
            for (var key in criterionValues) {
                if (!values[key]) {
                    values[key] = new Array();
                }
                for (var j=0;j<criterionValues[key].length;j++) {
                    values[key].push(criterionValues[key][j]);
                }

            }

        }
        return values;
    }

    this.isPropertyTypeSelected = function(propTypeID) {
        var selected = false;
        var propCriterion = this.getCurrentCriterionByName("PropertyType");
        if (propCriterion) {
            selected = propCriterion.isSelected(propTypeID);
        }
        return selected;
    }

    this.getSelectedPropertyTypes = function() {
        var propertyTypeIDs = new Array();
        var propCriterion = this.getCurrentCriterionByName("PropertyType");
        if (propCriterion) {
            propertyTypeIDs = propCriterion.getPropertyTypes();
        }
        return propertyTypeIDs;
    }

    this.isSaleOnly = function() {
        var saleOnly = false;
        var saleLease = this.getCurrentCriterionByName("SaleLease");
        if (saleLease) {
            saleOnly = saleLease.isSaleOnly();
        }
        return saleOnly;
    }
    this.isLeaseOnly = function() {
        var leaseOnly = false;
        var saleLease = this.getCurrentCriterionByName("SaleLease");
        if (saleLease) {
            leaseOnly = saleLease.isLeaseOnly();
        }
        return leaseOnly;
    }
    this.getSearchType = function() {
        var active = SearchType.ACTIVE_LISTINGS;
        var searchType = this.getCurrentCriterionByName("SearchType");
        if (searchType) {
            active = searchType.getActiveSearchType();
        }
        return active;
    }

    this.handleSearchTypeChange = function() {
        var newSearchType = this.getSearchType();
        if (newSearchType == SearchType.ACTIVE_LISTINGS) {
            $('#rsslink').removeClass('hidden');
        } else {
            $('#rsslink').addClass('hidden');
        }

        if (newSearchType == SearchType.ACTIVE_LISTINGS) {
            $('#highlight-email').removeClass("hidden");
        } else {
            $('#highlight-email').addClass("hidden");
        }

        if (newSearchType == SearchType.PROPERTIES) {
            var listView = new ListView();
            if (listView.sortColumn == 'PRICE') {
                listView.sort('CITY', false, true);
            }

            $('#header-PRICE').addClass("stay-hidden");
            $('#header-PRICE-stand-in').removeClass("stay-hidden");
        } else {
            $('#header-PRICE').removeClass("stay-hidden");
            $('#header-PRICE-stand-in').addClass("stay-hidden");
        }

        if (newSearchType == SearchType.PROPERTIES) {
            $('#highlight-export').addClass("hidden");
        } else {
            $('#highlight-export').removeClass("hidden");
        }

        if (newSearchType == SearchType.COMPARABLES || newSearchType == SearchType.ARCHIVED_LISTINGS) {
            $('#list-sort-menu-listings').addClass('hidden');
            $('#list-sort-menu-offmarket').addClass('hidden');
            $('#list-sort-menu-comps').removeClass('hidden');

        } else if (newSearchType == SearchType.PROPERTIES) {
            $('#list-sort-menu-listings').addClass('hidden');
            $('#list-sort-menu-offmarket').removeClass('hidden');
            $('#list-sort-menu-comps').addClass('hidden');

        } else {
            $('#list-sort-menu-listings').removeClass('hidden');
            $('#list-sort-menu-offmarket').addClass('hidden');
            $('#list-sort-menu-comps').addClass('hidden');
        }

        var actionMan = new ActionManager();
        actionMan.updateDisplay();
        actionMan.dazzle();
    }

    this.addCriterion = function(criterion) {
        this.allCriteria.push(criterion);
        this.allCriteriaIndex[criterion.getName()] = criterion;
    }
    this.addCurrentCriterionByName = function(name, overrideParentID) {
        //we would need to clone these criteria if we want to add them more than once
        var criterion = this.getCriterionByName(name);
        if (criterion) {
            this.currentCriteria.push(criterion);
            this.currentCriteriaIndex[criterion.getName()] = criterion;
            new CriteriaView().add(criterion, overrideParentID);
        }
        return criterion;
    }
    this.removeCurrentCriterionByIndex = function(index) {
        var criterion = this.currentCriteria[index];
        if (criterion.clear) {
            criterion.clear();
        }
        this.currentCriteriaIndex[this.currentCriteria[index].name] = null;
        this.currentCriteria.splice(index,1);
        new CriteriaView().remove(criterion);

    }
    this.getCriterionByName = function(name) {
        return this.allCriteriaIndex[name];
    }
    this.getCurrentCriterionByName = function(name) {
        return this.currentCriteriaIndex[name];
    }
    this.currentContains = function(criterion) {
        var contains = false;
        for (var i in this.currentCriteria) {
            if (this.currentCriteria[i] == criterion) {
                contains = true;
                break;
            }
        }
        return contains;
    }

    this.getCriteriaBySearchParameters = function(params) {
        var criteria = new Array();

        for (var i = 0; i < this.allCriteria.length; i++) {
            if (this.allCriteria[i].isInCriteria(params)) {
                criteria.push(this.allCriteria[i]);
            }
        }

        return criteria;
    }
}

function CriteriaController(criteriaNames) {
    this.criteriaNames = criteriaNames;
    if (CriteriaController.instance) {
        return CriteriaController.instance;
    }
    CriteriaController.instance = this;

    this.lastSearchValues = null;
    this.lastSearchRequestedDate = null;

    this.initializeCriteria = function() {
        for (var i = 0; this.criteriaNames && i < this.criteriaNames.length; i++) {
            var name = this.criteriaNames[i];
            new CriteriaManager().addCriterion(eval("new " + name.toTitleCase() + "()"));
        }
    }

    this.searchCriteriaChanged = function(newCriterionValues) {
        var changed = false;
        if (this.lastSearchValues) {
            var keys = "";
            for (var key in newCriterionValues) {
                keys += key + ",";
            }
            for (var key in this.lastSearchValues) {
                if (keys.indexOf(key) < 0) {
                    keys += key + ",";
                }
            }
            keys = keys.split(",");
            keys.pop(); // remove the last empty element

            for (var i = 0; i < keys.length; i++) {
                var key = keys[i];

                if (!this.lastSearchValues[key]) {
                    changed = true;
                    break;
                }
                if (!newCriterionValues[key]) {
                    changed = true;
                    break;
                }

                var lastVals = this.lastSearchValues[key];
                var newVals = newCriterionValues[key];
                if (lastVals.length != newVals.length) {
                    changed = true;
                    break;
                }

                for (var j = 0; j < lastVals.length; j++) {
                    if (lastVals[j] != newVals[j]) {
                        changed = true;
                        break;
                    }
                }
            }

        } else {
            changed = true;
        }
        return changed;
    }

    this.forceNewSearch = false;
    this.search = function(initPageNum) {
        var didSearch = false;

        var cMgr = new CriteriaManager();
        var criteriaValues = cMgr.getCriteriaValues();
        if (this.searchCriteriaChanged(criteriaValues)) {
            $("#number-results").find("span").empty().prepend("<img src='/images/search/loading_circle.gif' alt='Loading...' />");

            this.lastSearchRequestedDate = new Date();

            this.lastSearchValues = criteriaValues;
            var map = new MapManager().getMap()

            if (this.forceNewSearch) {
                this.forceNewSearch = false;
                criteriaValues['newSearch'] = ['1'];
            }
            new EntityManager().setSearchCriteria(criteriaValues);

            var list = new ListView();
            new EntityManager().overrideEntityIDs = null;
            new ActionManager().updateDisplay();

            list.show(initPageNum||1);

            new MapManager().show();
            didSearch = true;

            // enable the search results map layer
            var map = new MapManager().getMap();
            if (map) {
                var overlay = map.getOverlayControl();
                if (overlay) {
                    overlay.selectLayer('LISTINGS', true);
                }
            }

            // update rss link
            var queryString = 'search=true';
            for (var key in this.lastSearchValues) {
                var vals = this.lastSearchValues[key];
                if (vals && vals.length > 0) {
                    queryString += '&' + mapFunction(function(val) { return key + "=" + escape(val); }, vals).join("&");
                }
            }
            $("#rsslink").each(function() {
                this.setAttribute('href', '/feed/listings/?' + queryString);
            });

            // update want link
            queryString = '';
            if (cMgr.isLeaseOnly()) {
                queryString = 'includeLeaseAndSale=includeLease';
            } else if (cMgr.isSaleOnly()) {
                queryString = 'includeLeaseAndSale=includeSale';
            }

            var typeCriterion = cMgr.getCurrentCriterionByName('PropertyType');
            if (typeCriterion) {
                var types = typeCriterion.getPropertyTypeAndSubtypes();
                if (types.length > 0) {
                    queryString += '&propertyType=' + types[0].id;
                    var subtypes = types[0].getSubtypes();
                    if (subtypes.length > 0) {
                        queryString += '&' + mapFunction(function(val) { return 'subtypes=' + escape(val); }, subtypes).join('&');
                    }
                }
            }

            var location = cMgr.getCurrentCriterionByName('Location');
            if (location) {
                var items = location.getLocationItems();
                for (var i = 0; i < items.length; i++) {
                    if (items[i].locationType == 'postalCode') {
                        queryString += '&postalCode=' + items[i]['postalCode'];
                        queryString += '&state=' + items[i]['state'];

                    } else if (items[i].locationType == 'msa') {
                        queryString += '&msas=' + escape(items[i]['msa']);
                        queryString += '&state=' + items[i]['state'];
                    } else {
                        if (items[i]['city']) {
                            queryString += '&city=' + escape(items[i]['city']);
                        }
                        if (items[i]['state']) {
                            queryString += '&state=' + items[i]['state'];
                        }
                    }
                }
            }

            $("#show-post-want").each(function() {
                this.setAttribute('href', '/jsp/resources/broadcast_want.jsp?' + queryString);
            });


        }
        return didSearch;
    }
    this.initializeCriteria();
}

function CriteriaView(criteriaParentID) {
    if (CriteriaView.instance) {
        return CriteriaView.instance;
    }
    CriteriaView.instance = this;

    this.criteriaParentID = criteriaParentID;

    this.redraw = true;

    function isEnabled(criterion) {
        var mgr = new CriteriaManager();
        var enabled = criterion.isApplicable(mgr.getSelectedPropertyTypes());
        if (enabled) {
            if ((mgr.isSaleOnly() && criterion.leaseOrSale == FOR_LEASE) ||
                (mgr.isLeaseOnly() && criterion.leaseOrSale == FOR_SALE)) {
                enabled = false;
            }
        }
        return enabled;
    }

    this.add = function(criterion, overrideParentID) {
        criterion.renderIn($("#" + (overrideParentID ? overrideParentID : this.criteriaParentID))[0]);

        if (this.redraw) {
            criterion.setEnabled(isEnabled(criterion));
            criterion.updateDisplay();

            this.updateAddMenu();
            new WindowState().save();
        }
    }

    this.remove = function(criterion) {
        criterion.renderOut();

        if (this.redraw) {
            this.updateAddMenu();
            new WindowState().save();
        }
    }

    this.updateAddMenu = function() {
        var start = new Date().getTime();

        var mgr = new CriteriaManager();

        criteria = mgr.allCriteria;

        var mgr = new CriteriaManager();
        var selectedPropertyTypes = mgr.getSelectedPropertyTypes();
        var saleOnly = mgr.isSaleOnly();
        var leaseOnly = mgr.isLeaseOnly();

        for (var i=0;i<criteria.length;i++) {
            var enabled = true;
            if (mgr.currentContains(criteria[i])) {
                enabled = false;
            } else if (!criteria[i].isApplicable(selectedPropertyTypes)) {
                enabled = false;
            } else if (saleOnly && criteria[i].leaseOrSale == FOR_LEASE) {
                enabled = false;
            } else if (leaseOnly && criteria[i].leaseOrSale == FOR_SALE) {
                enabled = false;
            }

            if (enabled) {
                $("#add-" + criteria[i].getName()).removeClass("disabled");
            } else {
                $("#add-" + criteria[i].getName()).addClass("disabled");
            }
        }

        //TODO: this is pretty brittle
        $("#select-filter").find("ul").each(function() {
            if ($(this).find("li:not(.disabled)").length > 0) {
                $(this).prev().removeClass("disabled");
            } else {
                $(this).prev().addClass("disabled");
            }
        });
        if ($("#select-filter").find("li:not(.disabled)").length > 0) {
            $("#add-filter").removeClass("disabled");
            $("#add-filter-disabler").addClass("hidden");
        } else {
            $("#add-filter").addClass("disabled");
            $("#add-filter-disabler").removeClass("hidden");
        }

        if (DEBUG) debug("Update criteria menu: " + (new Date().getTime() - start) + " ms");
    }

    //asynchronously update the criteria view
    this.updateDisplay = function() {
        //make sure controller is initialized
        new CriteriaController();

        var criteria = new CriteriaManager().currentCriteria;
        for (var i=0;i<criteria.length;i++) {
            criteria[i].setEnabled(isEnabled(criteria[i]));
            criteria[i].updateDisplay();
        }

        this.updateAddMenu();

        this.eventLoopID = null;
    }
}

function Criterion(name, config) {
    this.name = name;

    if (config == null) config = new Object();

    this.leaseOrSale = config.leaseOrSale;
    this.propertyTypes = config.propertyTypes;
    this.entityTypes = config.entityTypes;
    this.defaultFilter = config.defaultFilter||false;

    this.parentElement = null;
    this.enabled = true;

    this.getName = function() {
        return this.name;
    }

    this.isApplicable = function(selectedPropertyTypes) {
        return this._isApplicable(selectedPropertyTypes);
    }

    // a) when no property type is selected, only those criteria that apply to all or have "defaultFilter" set to true are applicable
    // b) if this criterion has no property types specified, it is applicable to all
    // c) if the criterion's property types intersect with the selected property types, it is applicable
    this._isApplicable = function(selectedPropertyTypes) {
        if (!this._isApplicableForSearchType()) {
            return false;
        } else if (!this.propertyTypes || this.propertyTypes.length == 0 || this.propertyTypes.length == 9) {
            return true;
        } else if (!selectedPropertyTypes || selectedPropertyTypes.length == 0) {
            return this.defaultFilter;
        } else {
            for (var i = 0; i < selectedPropertyTypes.length; i++) {
                for (var j = 0; j < this.propertyTypes.length; j++) {
                    if (selectedPropertyTypes[i] == this.propertyTypes[j]) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    this.isApplicableForCurrentSearchType = function() {
        return this._isApplicableForSearchType();
    }

    this._isApplicableForSearchType = function() {
        var applicable = true;
        if (this.entityTypes && this.entityTypes.length > 0) {
            applicable = false;
            var searchType = new CriteriaManager().getSearchType();
            for (var i = 0; i < this.entityTypes.length; i++) {
                if (this.entityTypes[i] == searchType) {
                    applicable = true;
                    break;
                }
            }
        }
        return applicable;
    }

    this.setEnabled = function(enabled) {
        if (this.enabled != enabled) {
            this.enabled = enabled;
            if (this.parentElement) {
                if (!this.enabled) {
                    $(this.parentElement).addClass('disabled');
                    $(this.parentElement).prepend('<div class="disabled-overlay"/>');
                } else {
                    $(this.parentElement).removeClass('disabled');
                    $(this.parentElement).find('div.disabled-overlay').remove();
                }
            }
        }
    }

    this.clear = function() {
        this._clear();
    }
    this._clear = function() {}

    this.getCriterionValues = function() {
        return this._getCriterionValues();
    }
    this._getCriterionValues = function() {
        var map = new Array();
        if (this.enabled && this.parentElement) {
            $(this.parentElement).find("input:text,input[type='hidden']").each(function(i) {
                if (this.value) {
                    map[this.name] = [this.value];
                }
            });
            $(this.parentElement).find("input:checkbox,input:radio").each(function(i) {
                if (this.checked) {
                    var vals = map[this.name];
                    if (!vals) {
                        vals = new Array();
                        map[this.name] = vals;
                    }
                    vals.push(this.value);
                }
            });
            $(this.parentElement).find("select").each(function(i) {
                if (this.value) {
                    map[this.name] = [this.value];
                }
            });
        }
        return map;
    }

    this.updateDisplay = function() {
        this._updateDisplay();
    }
    this._updateDisplay = function() {
        var self = this;

        var normalize = function(val) {
            var mdy = val.split("/");
            if (mdy.length >= 3 && mdy[2].length < 4) {
                while (mdy[2].length < 3) {
                    mdy[2] = '0' + mdy[2];
                }
                mdy[2] = '2' + mdy[2];
            }
            if (mdy.length > 0) {
                val = mdy.join("/");
            }
            return val;
        }

        var txts = $(this.parentElement).find("input:text");
        txts.unbind();
        txts.blur(function() {
            if ($(this).hasClass('date')) {
                this.value = normalize(this.value);
            }

            if (self.doSearch()) {
                $(this).addClass('input-loading');
            }
        });
        txts.keypress(function(e) {
            if (e.keyCode == 13) {
                if ($(this).hasClass('date')) {
                    this.value = normalize(this.value);
                }

                self.doSearch();
            }
        });


        $(this.parentElement).find("input:checkbox,input:radio").unbind().click(function() {
            self.doSearch();
        });
        $(this.parentElement).find("select").unbind().change(function() {
            self.doSearch();
        });
    }

    this.getParentElement = function() {
        return this.parentElement;
    }

    // check to see if this criterion is represented in the given criteria
    this.isInCriteria = function(criteria) {
        return this._isInCriteria(criteria);
    }
    this._isInCriteria = function(criteria) {
        var foundNames = false;

        var parent;
        if (this.parentElement) {
            parent = this.parentElement;
        } else {
            parent = $("#module-"+this.name.toUnTitleCase()).find('li');
        }

        parent.find("input,select").each(function(i) {
            if (criteria[this.name]) {
                foundNames = true;
            }
        });
        return foundNames;
    }

    this.setStateFromCriteriaValues = function(values) {
        this._setStateFromCriteriaValues(values);
    }
    this._setStateFromCriteriaValues = function(values) {
        if (this.parentElement && values) {
            for (var name in values) {
                $(this.parentElement).find("input:text,input[type='hidden']").each(function() {
                    if (this.name == name) {
                        this.value = values[name][0];
                    }
                });
                $(this.parentElement).find("input:checkbox,input:radio").each(function() {
                    if (this.name == name) {
                        for (var i = 0; i < values[name].length; i++) {
                            if (values[name][i] == this.value) {
                                this.checked = true;
                                break;
                            }
                        }
                    }
                });
                $(this.parentElement).find('select').each(function() {
                    if (this.name == name) {
                        this.value = values[name][0];
                    }
                });
            }
        }
    }

    this.closed = function() {}

    this.renderOut = function() {
        if (this.parentElement) {
            this.parentElement.remove();
            this.parentElement = null;
        }
    }

    this.renderIn = function(parentElement) {
        this._renderIn(parentElement);
    }
    this._renderIn = function(parentElement) {
        $(parentElement).append($("#module-"+this.getName().toUnTitleCase()).html());

        //get the last li
        this.parentElement = $(parentElement).children("li:last");

        var self = this;
        var xFunc = function () {
            var index = 0;
            var lis = $(parentElement).children("li");
            for (var i=0;i<lis.length;i++) {
                if (lis[i] == $(this).parents("li")[0]) {
                    index = i;
                    break;
                }
            }
            new CriteriaManager().removeCurrentCriterionByIndex(index);
            self.closed();

            new CriteriaController().search();
        }
        $(this.parentElement).find(".x").click(xFunc);

        //do something to load any data into the form widgets
        if (this.updateDisplay) {
            this.updateDisplay();
        }
    }

    this.doSearch = function() {
        return new CriteriaController().search();
    }
}
function MultiSelectCriterion(name, config) {
    Criterion.call(this, name, config);

    this.renderIn = function(parentElement) {
        this._renderIn(parentElement);

        var criteria = $(this.parentElement).find(".criteria");
        var options = $(this.parentElement).find("form.criteria-options");
        var openButton = $(this.parentElement).find("button.options-open");
        var applyButton = $(this.parentElement).find("button.options-apply");

        openButton.unbind().click(function() {
            criteria.addClass("hidden");
            openButton.addClass("hidden");
            applyButton.removeClass("hidden");
            options.slideDown()
        });

        var self = this;
        function apply() {
            self.applyOptionsForm();

            openButton.removeClass("hidden");
            applyButton.addClass("hidden");
            if (!MultiSelectCriterion.APPLY_ON_TYPE_SELECT) {
                options.slideUp("normal", function() {criteria.removeClass("hidden")});
            }
        }
        applyButton.unbind().click(function() {
            apply();
        });

        if (MultiSelectCriterion.APPLY_ON_TYPE_SELECT) {
            $(parentElement).find("form.criteria-options input:checkbox").click(function() { apply(); });
        }
    }

    this.updateDisplay = function() {
        var parentEl = this.getParentElement();
        if (parentEl) {
            if ($(parentEl).find(".criteria")) {
                var displayable;

                var selected = new Array();
                $(parentEl).find('form.criteria-options input:checked').each(function() {
                    selected.push(this.value);
                });
                if (selected.length == 0) {
                    displayable = "Any";
                } else {
                    displayable = selected.join(", ");
                }
                $(parentEl).find(".criteria").html(displayable);
            }
        }
    }

    this.setStateFromCriteriaValues = function(values) {
        this._setStateFromCriteriaValues(values);

        if (this.parentElement) {
            var criteria = $(this.parentElement).find(".criteria");
            var options = $(this.parentElement).find("form.criteria-options");
            var openButton = $(this.parentElement).find("button.options-open");
            var applyButton = $(this.parentElement).find("button.options-apply");

            if ($(this.parentElement).find('form.criteria-options input:checked').length > 0) {
                options.hide();
                applyButton.addClass("hidden");

                openButton.removeClass("hidden");
                criteria.removeClass("hidden");
            } else {
                options.show();
                applyButton.removeClass("hidden");

                openButton.addClass("hidden");
                criteria.addClass("hidden");
            }
        }
    }

    this.applyOptionsForm = function() {
        this.updateDisplay();
        this.doSearch();
    }
}
MultiSelectCriterion.APPLY_ON_TYPE_SELECT = false;

function Paginator(parentID) {
    if (Paginator.instance) {
        return Paginator.instance;
    }
    Paginator.instance = this;

    this.parentID = parentID;
    this.currentPage = 1;
    this.oldCurrentPage = 0;
    this.numberOfResults = 0;

    this.itemsPerPage = 50;

    this.setItemsPerPage = function(count) {
        $("#per-page-select").val(count);
        this.itemsPerPage = count;
    }

    this.setCurrentPage = function(page) {
        this.oldCurrentPage = this.currentPage;
        this.currentPage = page;
    }

    this.showPrevious = function() {
        if (this.currentPage > 1) {
            new ListView().show(this.currentPage - 1);
        }
    }
    this.showNext = function() {
        var numberOfPages = Math.ceil(this.numberOfResults / this.itemsPerPage);
        if (this.currentPage < numberOfPages) {
            new ListView().show(this.currentPage + 1);
        }
    }

    this.updateDisplay = function(numberOfResults) {
        this.numberOfResults = numberOfResults;
        $("#" + this.parentID).find(".page-num").remove();
        $("#pagination-drop").empty();

        var numberOfPages = Math.ceil(this.numberOfResults / this.itemsPerPage);
        if (this.currentPage == 1 || this.numberOfResults == 0) {
            $("#prev-page").addClass("disabled");
        } else {
            $("#prev-page").removeClass("disabled");
        }

        if (this.currentPage == numberOfPages || this.numberOfResults == 0) {
            $("#next-page").addClass("disabled");
        } else {
            $("#next-page").removeClass("disabled");
        }

        if (this.numberOfResults > 0) {
            var i = 0;
            var template = '<li class="page-num" id="pagination{0}"><a href="javascript:new ListView().show({1})">{2}</a></li>';
            for (i=0;(i<numberOfPages && i<Paginator.MAX_PAGE_NUMS_TO_SEE);i++) {
                var pageNum = i+1;
                $("#pagination-drop-start").before(template.format(pageNum,pageNum,pageNum));
            }

            for (i=i;i<numberOfPages;i++) {
                var pageNum = i+1;
                $("#pagination-drop").append(template.format(pageNum,pageNum,pageNum));
            }
            $("#pagination" + this.oldCurrentPage).find("> a").removeClass("current");
            $("#pagination" + this.currentPage).find("> a").addClass("current");
        }

        if (numberOfPages > Paginator.MAX_PAGE_NUMS_TO_SEE) {
            $("#pagination-drop-start").removeClass("hidden");
        } else {
            $("#pagination-drop-start").addClass("hidden");
        }

        var end = (this.currentPage * this.itemsPerPage);
        var start = end - this.itemsPerPage + 1;
        if (end > this.numberOfResults) {
            end = this.numberOfResults;
        }
        if (start > end) start = end;
        $("#results-current").html(start + '-' + end);
        $("#results-total").html(this.numberOfResults);
    }
}
Paginator.MAX_PAGE_NUMS_TO_SEE = 5;

function WindowState() {
    if (WindowState.instance) {
        return WindowState.instance;
    }
    WindowState.instance = this;

    this.moveListener = null;
    this.zoomListener = null;

    this.dontSetContext = false;
    new WindowStateHistoryManager().onHistoryChange = function(stateHash) {
        var ws = WindowState.instance;

        var subtitle = '';
        var state = null;
        if (stateHash) {
            stateHash = stateHash.replace(/\b[a]?c=[^\|]*\|/g, ''); // don't restore search criteria
            stateHash = stateHash.replace(/\bh=[^\|]*\|/g, ''); // don't restore highlighted
            stateHash = stateHash.replace(/\bss=[^\|]*\|/g, ''); // don't restore saved search
            state = ws.deserialize(stateHash);

            subtitle = state.subtitle;
        }
        ws.updateTitle(subtitle);

        if (state) {
            if (DEBUG) debug('restoring: ' + stateHash);
            ws.restore(false, state);
        }
    }

    this.updateTitle = function(subtitle) {
        if (!subtitle) {
            subtitle = this.getSubtitle();
        }
        if (!subtitle || subtitle.length == 0) {
            var ss = new SavedSearchManager();
            if (ss.isSavedSearch()) {
                subtitle = ss.savedSearchName;
            }
        }
        if (subtitle && subtitle.length > 0) {
            document.title = MAIN_TITLE + " : " + subtitle;
        } else {
            document.title = MAIN_TITLE;
        }
    }

    this.getSubtitle = function() {
        var subtitle;

        switch (new TabsManager().getActiveTabID()) {
        case 'list-view':
            subtitle = 'Page ' + new Paginator().currentPage;
            break;

        case 'map-view':
            subtitle = 'Mapped';
            break;

        case 'details-view':
            var entity = new EntityManager().getEntity(new DetailsView().currentEntityID);
            if (entity) {
                subtitle = entity.title||"";
                subtitle = subtitle.replace(/&amp;/g, '&');
            } else {
                subtitle = 'Property Details';
            }
            break;

        case 'email-view':
            subtitle = 'Email';
            break;

        case 'reports-view':
            subtitle = 'Create Report';
            break;

        case 'catalog-view':
            subtitle = 'Add To Catalog';
            break;

        default:
            subtitle = '';
            break;
        }
        return subtitle;
    }


    this.addHistory = function() {
        if (this.dontSetContext) return;
        new WindowStateHistoryManager().addHistoryStackFrame();
    }

    /* if the client has a search-state cookie, let's use that to set up the
     * state of this search session.
     */
    this.redirectOnCookie = function() {
        var serializedState = getCookieValue("SearchPageState");
        //TODO: implement this if we're going to use a cookie to save page state
    }

    /* the server doesn't see any anchor ("#...") info on the URL, so we need
     * to hack this by reading the hash info via javascript and re-encoding this
     * data into a query string and redirecting to this page using the query
     * string. We only need to make a server call to handle criteria, other stately
     * stuff can be handed via the anchor mechanism
     */
    this.redirectOnHash = function() {
        if (window.location.hash.search(/\bc=/) > -1) {
            var queryString = "?override=true";

            if (window.location.href.indexOf('investment=1') > -1) {
                queryString += "&investment=1";
            }

            var state = this.deserialize(window.location.hash.substring(1));
            if (state.savedSearchID > 0) {
                queryString += "&savedSearchID=" + state.savedSearchID;
            }

            for (var key in state.criteria) {
                var vals = state.criteria[key];
                queryString += "&" + mapFunction(function(val) { return key + "=" + escape(val); }, vals).join("&");
            }

            // the non-criteria stuff is parsed via javascript anyway, so we can leave
            // that as hash information (we'll strip out the criteria element)
            var hash = window.location.hash.replace(/(\b)c=[^\|]*\|/g, '$1');
            if (hash.length > 1) {
                queryString += hash;
                if (window.location.href.indexOf('forceNewSearch=1') > -1) {
                    queryString += "force=1|";
                }
            }
            window.location.replace(queryString);
        }
    }

    /* Save the current state of the window for later recall.  This method is fairly
     * lightweight and transparent to the user so it can be called often.
     * See addHistory() to add user-noticeable state (i.e. state that can be restored via
     * the browser's forward/back buttons)
     */
    this.save = function() {
        if (this.dontSetContext) return;
        new WindowStateHistoryManager().saveState();
    }

    this.getState = function() {
        var state;
        if (window.location.hash.indexOf("#") == 0) {
            state = this.deserialize(window.location.hash.substring(1));
        } else {
            state = new Object();
        }
        return state;
    }
    this.restore = function(doSearch, state) {
        this.dontSetContext = true;

        if (!state) {
            if (DEBUG) {
                var stateHash = '';
                if (window.location.hash.indexOf("#") == 0) {
                    stateHash = window.location.hash.substring(1);
                }
                debug('restoring: ' + stateHash);
            }
            state = this.getState();
        }

        var viewSelected = false;
        var changePage = false;
        var page = 1;
        if (state) {
            if (state.sort) {
                var list = new ListView();
                if (list.sortColumn != state.sort.column) {
                    list.sortColumn = state.sort.column;
                    changePage = true;
                }
                if (list.sortReverse != state.sort.reverse) {
                    list.sortReverse = state.sort.reverse;
                    changePage = true;
                }
                list.styleSortCols(true);
            }

            // state.criteria -- this should be handled as query string params
            // state.activeCriteria -- this is better handled in results.jsp

            var activeTab =  new TabsManager().activeTab();
            if (state.activeTab > 0) {
                activeTab = state.activeTab;
            }

            var paginator = new Paginator();
            if (state.page) {
                if (state.page.current > 1) {
                    page = state.page.current;
                }
                if (state.page.itemsPerPage > 0 && state.page.itemsPerPage != paginator.itemsPerPage) {
                    changePage = true;
                    paginator.setItemsPerPage(state.page.itemsPerPage);
                }
            }
            if (page != paginator.currentPage) {
                changePage = true;
            }

            if (state.forceSearch) {
                new CriteriaController().forceNewSearch = true;
            }

            if (state.highlighted) {
                for (var searchType in state.highlighted) {
                    new ActionManager().selectEntities(state.highlighted[searchType], searchType);
                }
            }
            if (state.viewHighlighted) {
                new ActionManager()._viewSelected();
                doSearch = false;
                viewSelected = true;
            }

            if (doSearch) {
                new CriteriaController().search(page);
            } else if (changePage || viewSelected) {
                new ListView().show(page);
            }

            if (state.map) {
                var mm = new MapManager();
                mm.setZoomFromResults(false);
                if (state.overlay) {
                    mm.addMapCreationListener(function(cmap) {
                        var control = cmap.getOverlayControl();
                        if (control) {
                            control.deserialize(state.overlay);
                        }
                    });
                }
                mm.show(new Point(state.map.latitude, state.map.longitude), eval(state.map.zoom));
                if (state.openInfoWindowID) {
                    mm.onNextMappables = function() {
                        mm.openInfoWindow(state.openInfoWindowID);
                    }
                }
            }

            // state.savedSearchID -- handled via query string params

            var tabs = new TabsManager();
            if (state.detailsEntityID) {
                new DetailsView()._showDetails(state.detailsEntityID);
                tabs.enableTab('details-view');
            } else {
                tabs.disableTab('details-view');
                if (activeTab == tabs.getTabIndex('details-view')) activeTab = 1;
            }

            if (state.emailEntityIDs && state.emailEntityIDs.length > 0) {
                new EmailView()._show(state.emailEntityIDs);
                tabs.enableTab('email-view');
            } else {
                tabs.disableTab('email-view');
                if (activeTab == tabs.getTabIndex('email-view')) activeTab = 1;
            }

            if (state.reportEntityIDs && state.reportEntityIDs.length > 0) {
                new ReportsView()._show(state.reportEntityIDs);
                tabs.enableTab('reports-view');
            } else {
                tabs.disableTab('reports-view');
                if (activeTab == tabs.getTabIndex('reports-view')) activeTab = 1;
            }

            if (state.catalogEntityIDs && state.catalogEntityIDs.length > 0) {
                new AddToCatalogView()._show(state.catalogEntityIDs);
                tabs.enableTab('catalog-view');
            } else {
                tabs.disableTab('catalog-view');
                if (activeTab == tabs.getTabIndex('catalog-view')) activeTab = 1;
            }

            if (state.exportEntityIDs && state.exportEntityIDs.length > 0) {
                new ExportView()._show(state.exportEntityIDs);
                tabs.enableTab('export-view');
            } else {
                tabs.disableTab('export-view');
                if (activeTab == tabs.getTabIndex('export-view')) activeTab = 1;
            }

            if (state.historyID) {
                var wshm = new WindowStateHistoryManager();
                wshm.historyStackCounter = state.historyID;
            }

            var actionMan = new ActionManager();
            if (actionMan.getActiveTab() != activeTab) {
                tabs.enableTab(activeTab).triggerTab(activeTab);
                function zoomIt() {
                    if (DEBUG) debug("zoomIt");
                    try {
                        var north = 0
                        var south = 0
                        var east = 0
                        var west = 0;
                        var ids = new ActionManager().getSelectedEntityIDs();
                        for (var i=0;i<ids.length;i++) {
                            var id = ids[i];
                            var entity = new EntityManager().getEntity(id);
                            var lat = entity.latitude;
                            var lng = entity.longitude;
                            if (DEBUG) debug("zoomIt pt {0}".format(pt));
                            if ((!north || lat > north)) {
                                north = parseFloat(lat);
                            }
                            if ((!south || lat < south)) {
                                south = parseFloat(lat);
                            }
                            if ((!east || lng > east)) {
                                east = parseFloat(lng);
                            }
                            if ((!west || lng < west)) {
                                west = parseFloat(lng);
                            }
                        }
                        var mgr = new MapManager();
                        if (mgr.getMap()) {
                            mgr.getMap().zoomToFit(north,south,east,west);
                        } else {
                            window.setTimeout(zoomIt,1000);
                        }
                    } catch (e) {
                        window.setTimeout(zoomIt,1000);
                    }
                }
                if (state.highlighted && state.highlighted.length > 0 && !state.map && activeTab == 2) {
                    zoomIt();
                }
            }

            new CriteriaManager().handleSearchTypeChange();
        }

        if (DEBUG) debug("restoration complete");

        this.dontSetContext = false;
    }

    this.serialize = function() {
        var hash = "";
        // tab
        if (new TabsManager().activeTab() > 1) {
            hash += "t=" + new TabsManager().activeTab() + "|";
        }

        // page
        var pg = new Paginator();
        hash += "p=" + pg.currentPage + "," + pg.itemsPerPage + "|";

        // sort
        var list = new ListView();
        if (list.sortColumn) {
            hash += "s=" + list.sortColumn + "," + (list.sortReverse ? "0" : "1") + "|";
        }

        // map
        var map = new MapManager().getMap();
        if (map && map.getCenter()) {
            hash += "m=" + map.getZoom() + "," + map.getCenter().lat() + "," + map.getCenter().lng() + "|";

            // overlay state
            if (map.getOverlayControl()) {
                hash += "ov=" + map.getOverlayControl().serialize() + "|";
            }

            // info window
            if (map.getOpenInfoWindowID()) {
                hash += "iw=" + map.getOpenInfoWindowID() + "|";
            }
        }


        // polygon
        /* don't think we need to persist this
        if (map && map.getUserShape()) {
            var coords = new Array();
            for (var i=0;i<map.getUserShape().getVertexCount();i++) {
                var v = map.getUserShape().getVertex(i);
                coords.push(v.lat());
                coords.push(v.lng());
            }
            hash += "poly=" + coords.join(",") + "|";
        }
        */

        // search criteria
        var criteriaVals = new CriteriaController().lastSearchValues;
        if (criteriaVals) {
            hash += "c=";
            for (var name in criteriaVals) {
                var vals = mapFunction(function(val) { return hashEscape(val) }, criteriaVals[name]);
                hash += name + ":" + vals.join(",") + ";"
            }
            hash += "|";
        }
        // active criteria
        var criteria = new CriteriaManager().currentCriteria;
        if (criteria && criteria.length > 0) {
            var names = mapFunction(function(criterion) {
                var val = criterion.getName();
                if (criterion.serialize) {
                    val += ":" + hashEscape(criterion.serialize());
                }
                return val;
            }, criteria);
            hash += "ac=" + names.join(",") + "|";
        }


        // highlighted
        hash += "h=";
        for (var i = 0; i < SearchType.ALL_TYPES.length; i++) {
            var highlighted = new ActionManager().getSelectedEntityIDs(SearchType.ALL_TYPES[i]);
            if (highlighted.length > 0) {
                hash += SearchType.ALL_TYPES[i] + ":" + highlighted.join(",") + ";";
            }
        }
        hash += "|";

        // saved search
        var savedSearch = new SavedSearchManager();
        if (savedSearch.savedSearchID > 0) {
            hash += "ss=" + savedSearch.savedSearchID + "|";
        }

        // details tab
        var currentDetailsEntityID = new DetailsView().getCurrentEntityID();
        if (currentDetailsEntityID) {
            hash += "d=" + currentDetailsEntityID + "|"
        }

        var subtitle = this.getSubtitle();
        if (subtitle.length > 0) {
            hash += "tl=" + hashEscape(subtitle) + "|"
        }

        // email tab
        var emailIDs = new EmailView().getCurrentEntityIDs();
        if (emailIDs && emailIDs.length > 0) {
            hash += "e=" + emailIDs.join(",") + "|";
        }

        // reports tab
        var reportIDs = new ReportsView().getCurrentEntityIDs();
        if (reportIDs && reportIDs.length > 0) {
            hash += "r=" + reportIDs.join(",") + "|";
        }

        // add to catalog tab
        var catalogIDs = new AddToCatalogView().getCurrentEntityIDs();
        if (catalogIDs && catalogIDs.length > 0) {
            hash += "ct=" + catalogIDs.join(",") + "|";
        }

        // export tab
        var exportIDs = new ExportView().currentEntityIDs;
        if (exportIDs && exportIDs.length > 0) {
            hash += "ex=" + exportIDs.join(",") + "|";
        }

        // viewing highlighted
        if (new EntityManager().overrideEntityIDs) {
            hash += "hv=1|";
        }

        return hash;
    }

    this.deserialize = function(serialized) {
        var state = new Object();

        var items = serialized.split('|');
        for (var i = 0; i < items.length; i++) {
            var keyValue = items[i].split('=');
            var key = keyValue[0];
            var value = keyValue[1];
            switch (key) {
            case 't':
                state.activeTab = parseInt(value);
                break;

            case 'p':
                var pageItems = value.split(',');
                state.page = {'current':parseInt(pageItems[0]), 'itemsPerPage':parseInt(pageItems[1])};
                break;

            case 's':
                var sortItems = value.split(',');
                state.sort = {'column':sortItems[0], 'reverse':sortItems[1] == '0'}
                break;

            case 'm':
                var mapItems = value.split(',');
                state.map = {'zoom': mapItems[0], 'latitude':mapItems[1], 'longitude':mapItems[2]};
                break;

            case 'iw':
                state.openInfoWindowID = value;
                break;

            case 'ov':
                state.overlay = value;
                break;

            case 'poly':
                var coords = value.split(',');
                var polygon = new Array();
                for (var j=0;j<coords.length;j++) {
                    var lat = coords[j];
                    j++;
                    var lng = coords[j];
                    polygon.push(new Point(lat,lng));
                }
                state.polygon = polygon;
                break;

            case 'c':
                state.criteria = new Object();

                var criteria = value.split(';');
                for (var j = 0; j < criteria.length; j++) {
                    if (criteria[j].indexOf(':') < 0) continue;

                    var criteriaKeyVal = criteria[j].split(':');
                    var values = mapFunction(function(val) { return hashUnescape(val) }, criteriaKeyVal[1].split(','));
                    state.criteria[criteriaKeyVal[0]] = values;
                }
                break;

            case 'ac':
                state.activeCriteria = new Object();
                var criteria = value.split(',');
                for (var j = 0; j < criteria.length; j++) {
                    var nameState = criteria[j].split(':');
                    if (nameState.length == 1) {
                        nameState.push('');
                    }
                    state.activeCriteria[nameState[0]] = hashUnescape(nameState[1]);
                }

                break;

            case 'h':
                var idsByTypes = value.split(';');
                if (idsByTypes.length > 0) {
                    state.highlighted = new Object();
                    for (var j = 0; j < idsByTypes.length; j++) {
                        if (idsByTypes[j].indexOf(':') < 0) continue;

                        var typeIDs = idsByTypes[j].split(':');
                        state.highlighted[typeIDs[0]] = typeIDs[1].split(',');
                    }
                }
                break;

            case 'ss':
                state.savedSearchID = parseInt(value);
                break;

            case 'd':
                state.detailsEntityID = value;
                break;

            case 'tl':
                state.subtitle = hashUnescape(value);
                break;

            case 'e':
                state.emailEntityIDs = value.split(',');
                break;

            case 'r':
                state.reportEntityIDs = value.split(',');
                break;

            case 'ct':
                state.catalogEntityIDs = value.split(',');
                break;

            case 'ex':
                state.exportEntityIDs = value.split(',');
                break;

            case 'hv':
                state.viewHighlighted = (value == '1');
                break;

            case 'hid':
                state.historyID = parseInt(value);
                break;

            case 'force':
                state.forceSearch = true;
                break;

            default:
                break;
            }
        }

        return state;
    }

    // escape [=|:,;/] w/ "/HH"
    function hashEscape(val) {
        val = val||"";
        val = val.toString();
        val = val.replace(/\//g, '/2F');

        val = val.replace(/=/g, '/3D');
        val = val.replace(/\|/g, '/7C');
        val = val.replace(/:/g, '/3A');
        val = val.replace(/,/g, '/2C');
        val = val.replace(/;/g, '/3B');
        return val;
    }
    function hashUnescape(val) {
        val = val.replace(/\/3D/g, '=');
        val = val.replace(/\/7C/g, '|');
        val = val.replace(/\/3A/g, ':');
        val = val.replace(/\/2C/g, ',');
        val = val.replace(/\/3B/g, ';');

        val = val.replace(/\/2F/g, '/');
        return val;
    }
}
function getCookieValue(name) {
    var val = '';
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
        cookies[i] = cookies[i].trim();
        if (cookies[i].indexOf(name + '=') == 0) {
            val = cookies[i].substring((name + '=').length) == 'true';
        }
    }
    return val;
}

function WindowStateHistoryManager() {
    if (WindowStateHistoryManager.instance) {
        return WindowStateHistoryManager.instance;
    }
    WindowStateHistoryManager.instance = this;

    this.useIframeHistory = IS_IE;
    this.iframe = null;
    this.historyStackCounter = 0;

    this.ignoreHistoryEvent = false;

    this.init = function(iframeID) {
        this.iframe = $('#' + iframeID)[0];
        this._checkHistoryChange('' + this.historyStackCounter);
    }
    this._checkHistoryChange = function(lastVal) {
        var newVal;
        if (this.useIframeHistory) {
            var id = /\bid=(\d+)\b/.exec(this.iframe.contentWindow.location.search);
            newVal = ((id != null) && (id.length > 0)) ? id[1] : '0';
        } else {
            var id = /\bhid=(\d+)\b/.exec(window.location.hash);
            newVal = ((id != null) && (id.length > 0)) ? id[1] : '0';
        }

        if (newVal != lastVal && newVal.length > 0) {
            if (this.ignoreHistoryEvent) {
                this.ignoreHistoryEvent = false;
            } else {
                var serialized;
                if (this.useIframeHistory) {
                    var hash = this.iframe.contentWindow.location.hash;
                    if (hash.indexOf('#') > -1) {
                        serialized = hash.substring(hash.indexOf('#') + 1);
                    }
                } else {
                    var hash = window.location.hash;
                    if (hash.indexOf('#') > -1) {
                        serialized = hash.substring(hash.indexOf('#') + 1);
                    }
                }

                if (serialized) {
                    if (DEBUG) debug('history change: ' + serialized);
                    if (this.onHistoryChange) {
                        this.onHistoryChange(serialized);
                    }
                }
            }
        }

        setTimeout(function() {new WindowStateHistoryManager()._checkHistoryChange(newVal)}, 250);
    }
    // this guy is called from the iframe
    this._iframeLoaded = function(id) {
        //if (DEBUG) debug('iframe loaded: ' + id);
    }

    this._saveState = function(noIframe) {
        var now = new Date().getTime();

        var serialized = new WindowState().serialize();
        window.location.replace("#" + serialized + 'hid=' + this.historyStackCounter + '|');

        if (!noIframe && this.useIframeHistory && this.iframe) {
            var newLoc = this.iframe.contentWindow.location.href;
            if (newLoc.indexOf('#') > 0) {
                newLoc = newLoc.substring(0, newLoc.indexOf('#'));
            }
            newLoc += '#' + serialized;
            this.iframe.contentWindow.location.replace(newLoc);
        }

        if (DEBUG) debug('saving state (' + (new Date().getTime() - now) + 'ms): ' + serialized);

        return serialized;
    }

    this.saverID = null;
    this.saveState = function() {
        if (this.saverID) {
            clearTimeout(this.saverID);
        }
        this.saverID = setTimeout(function() {new WindowStateHistoryManager()._saveState()}, IS_IE ? 2000 : 500);
    }

    this._addHistoryStackFrame = function() {
        this.historyStackCounter++;

        if (this.useIframeHistory) {
            var serialized = this._saveState(true);
            new WindowState().updateTitle();

            if (DEBUG) debug('history checkpoint[' + this.historyStackCounter + ']');

            this.ignoreHistoryEvent = true;
            var newLocation = "results_context.jsp?subtitle=" + escape(new WindowState().getSubtitle()) + "&id=" + this.historyStackCounter + "#" + serialized;
            this.iframe.src = newLocation;

        } else {
            var now = new Date().getTime();

            var serialized =  new WindowState().serialize();

            this.ignoreHistoryEvent = true;
            window.location = '#' + serialized + 'hid=' + this.historyStackCounter + '|';

            new WindowState().updateTitle();

            if (DEBUG) debug('saving state (' + (new Date().getTime() - now) + 'ms): ' + serialized);
            if (DEBUG) debug('history checkpoint[' + this.historyStackCounter + ']');
        }
    }

    this.historyAdderID = null;
    this.addHistoryStackFrame = function() {
        if (this.saverID) {
            clearTimeout(this.saverID);
            this.saverID = null;
        }
        if (this.historyAdderID) {
            clearTimeout(this.historyAdderID);
        }
        this.historyAdderID = setTimeout(function() {new WindowStateHistoryManager()._addHistoryStackFrame()}, 100);
    }

    this.onHistoryChange = function(newState) {}
}

function SavedSearchManager(savedSearchID, savedSearchName, additionalEmail) {
    if (SavedSearchManager.instance) {
        return SavedSearchManager.instance;
    }
    SavedSearchManager.instance = this;

    this.savedSearchID = savedSearchID;
    this.savedSearchName = savedSearchName;
    this.additionalEmail= additionalEmail;

    this.isSavedSearch = function() {
        return this.savedSearchID > 0;
    }

    this.callSaveSearch = function(savedSearchID, savedSearchName, alertFrequencyDays, savedSearchAdditionalEmail) {
        var handler = function(responseXML) {
            var el = responseXML.documentElement;
            var savedID = parseInt(el.getAttribute("id"));
            var savedName = el.getAttribute("name");
            var savedAdditionalEmail = el.getAttribute("additional-email-address");
            var errorMsg = el.getAttribute("error-message");

            //TODO: error handling, revert saved search name if the call errors out?

            var me = new SavedSearchManager();
            if (savedID != me.savedSearchID) {
                me.savedSearchID = savedID;
                me.savedSearchName = savedName;
                me.savedSearchAdditionalEmail = savedAdditionalEmail;

                $('#load-searches, #to-save').removeClass('hidden');
                $("#load-searches").append('<a href="?savedSearchID={0}" title="Load this Saved Search">{1}</a>'.format(savedID, savedName));
                $('#load-searches').find('a:odd:last').addClass("odd");
                $('#load-searches').find('a:even:last').addClass("even");

                new WindowState().save();
            }
        }

        caller = new AJAXMethodCaller("com.catylist.ajax.SavedSearchProvider", "saveSearch");
        caller.setSuccessfulResponseXMLHandler(handler);

        caller.addParameter("java.lang.Integer", savedSearchID);
        caller.addParameter("java.lang.String", savedSearchName);
        caller.addParameter("java.lang.String", "UNIFIED_REAL_ESTATE");
        caller.addParameter("java.lang.String", savedSearchAdditionalEmail);

        var criteriaVals = new CriteriaController().lastSearchValues;
        if (criteriaVals) {
            for (var key in criteriaVals) {
                caller.addRequestParameter(key, criteriaVals[key]);
            }
            if (alertFrequencyDays) {
                caller.addRequestParameter("frequency", alertFrequencyDays);
            }
            if (savedSearchAdditionalEmail) {
                caller.addRequestParameter("frequency", alertFrequencyDays);
            }

            var map = new MapManager().getMap();
            if (map && map.getOverlayControl()) {
                caller.addRequestParameter("overlays", map.getOverlayControl().serialize());
            }

            caller.call();
        }
    }

    this.saveAs = function(savedSearchID, newName, freq, additionalEmail) {
        this.callSaveSearch(savedSearchID, newName, freq, additionalEmail);

        document.title = MAIN_TITLE + ' : ' + newName;
        $('#saved-search-title, #saved-search-to-save').html(newName);
    }
}

// note: scripts w/ document.write()'s (like google maps) in them are not working w/ firefox 2
function ScriptManager() {
    if (ScriptManager.instance) {
        return ScriptManager.instance;
    }
    ScriptManager.instance = this;

    function Script(url) {
        this.url = url;

        this.added = false;
        this.loaded = false;

        this._loadCallbacks = new Array();

        this.load = function(callback) {
            this._loadCallbacks.push(callback);

            if (!this.added) {
                this.added = true;

                var scriptNode = document.createElement('script');
                scriptNode.setAttribute('type', 'text/javascript');
                scriptNode.setAttribute('src', this.url);

                var me = this;
                // !IE:
                scriptNode.onload = function() {
                    me.loaded = true;
                    me._callLoadCallbacks();
                }
                // IE:
                scriptNode.onreadystatechange = function() {
                    // I'm seeing either "complete" or "loaded", but the specs say there should be both so we'll handle that.
                    if (!me.loaded &&
                            (this.readyState == 'complete' || this.readyState == 'loaded')) {
                        me.loaded = true;
                        me._callLoadCallbacks();
                    }
                }

                document.documentElement.appendChild(scriptNode);
            } else if (this.loaded) {
                this._callLoadCallbacks();
            }
        }
        this._callLoadCallbacks = function() {
            var callbacks = this._loadCallbacks;
            this._loadCallbacks = new Array();

            for (var i = 0; i < callbacks.length; i++) {
                callbacks[i]();
            }
        }

        this.toString = function() { return this.url };
    }

    this.detailsScripts = [
        new Script("/js/jquery.colorbox.js"),
        new Script("/js/slideshow.js"),
        new Script("/js/maps.js?v=R110")
    ];

    this.mapScripts = [
        new Script('/js/CPolygonControl.js'),
        new Script('/js/COverlayControl.js?v=R109'),
        new Script('/js/CZoomControl.js'),
        new Script('/js/ClusterOverlay.js?v=R109'),
        new Script('/js/CDistanceMeasureControl.js')
    ];

    this.catalogScripts = [
         new Script("/js/catalog.js")
    ];

    this._loadScripts = function(scripts, callback) {
        if (scripts && scripts.length > 0) {
            var calledback = false;
            var checkLoaded = function() {
                var allLoaded = true;
                for (var i = 0; i < scripts.length; i++) {
                    if (!scripts[i].loaded) {
                        allLoaded = false;
                        break;
                    }
                }
                if (allLoaded && !calledback) {
                    calledback = true;
                    if (DEBUG) debug('scripts loaded: ' + scripts.join(', '));
                    callback();
                }
            }

            for (var i = 0; i < scripts.length; i++) {
                scripts[i].load(checkLoaded);
            }
        } else {
            callback();
        }
    }

    this.loadMapScripts = function(callback) {
        this._loadScripts(this.mapScripts, callback);
    }

    this.loadDetailsScripts = function(callback) {
        this._loadScripts(this.detailsScripts.concat(this.mapScripts), callback);
    }

    this.loadCatalogScripts = function(callback) {
        this._loadScripts(this.catalogScripts, callback);
    }
}

function show_calendar(fieldName) {
    var field = $('#search-filters,#primary-sitelink-filters').find('input:text[name=' + fieldName + ']');

    var cal = new YAHOO.widget.Calendar('theCalendar', 'calendar-container', { close:true });

    var mdy = field.val().split("/");
    if (mdy.length == 3) {
        cal.select(mdy.join("/"));
        cal.cfg.setProperty("pagedate", mdy[0] + "/" + mdy[2]);
    }
    cal.selectEvent.subscribe(function(type,args,obj) {
        var dates = args[0];
        var date = dates[0];
        var year = date[0], month = date[1], day = date[2];

        field.val(month + "/" + day + "/" + year);

        cal.hide();

        new CriteriaController().search();
    }, cal, true);

    cal.hideEvent.subscribe(function() {
        $("body").removeClass("thick");
    });

    cal.render();

    $("#thickbox-mimic").unbind().click(function() {
        cal.hide();
    });
    $("body").addClass("thick");

    cal.show();
}


/*
 * functions defined in email_compose_include.jsp that are overridden for new
 * style searches
 */
function openRecipientPopup() {
    window.open('/jsp/email/email_recipients_list_popup.jsp','emailPopup','toolbar=no,location=no,directories=no,status=no,menubar=no,resizable=yes,copyhistory=no,scrollbars=yes,width=300,height=500');
}
function showEntityDetails(id, context) {
    var typeAndID;
    if (context.indexOf('listing') > -1) {
        typeAndID = LISTING + '_' + id;
    } else if (context.indexOf('property') > -1) {
        typeAndID = PROPERTY + '_' + id;
    }

    new DetailsView().showDetails(typeAndID);
}
function removeRecipients() {
    new EmailView()._load("/jsp/search/email_compose.jsp?action=removeRecipients");
}

/* functions defined in global.js that are overridden for new style searches */
/*email recipient search submit*/
var recipientsWindow = null;
function emailFormSaveInfo() {
    recipientsWindow = window.open("","recipientsPopup","toolbar=" + (IS_IE ? "no" : "yes") + ",location=" + (IS_IE ? "yes" : "no") + ",directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=yes,width=640,height=480");

    var emailForm = $("#emailForm")[0];
    emailForm.action = "email_recipients_search.jsp";
    emailForm.target = "recipientsPopup";
    emailForm.enctype = "";
    emailForm.submit();
}

var customizeWindow = null;
function submitReportSelectionForm(which, where, what) {
    // If "Customize Report" button was clicked
    if (which == 'customize') {
        // Set the action of the form
        if ( what == 'comp') {
            document.choosetypeform.action = '/jsp/search/report_customization.jsp';
        } else {
            document.choosetypeform.action = '/jsp/search/report_customization.jsp';
        }

        customizeWindow = window.open("","customizePopup","toolbar=" + (IS_IE ? "no" : "yes") + ",location=" + (IS_IE ? "yes" : "no") + ",directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,copyhistory=yes,width=640,height=680");

        document.choosetypeform.target = 'customizePopup';
        document.choosetypeform.submit();

    // If "Generate PDF" button was clicked
    } else if (which == 'generate') {
        // Open the popup window
        window.open('','pdfPopup','toolbar=no,location=no,directories=no,status=no,menubar=no,resizable=no,copyhistory=no,scrollbars=no,width=400,height=300');

        var pathDir = 'multi_listing'
        if ( what == 'comp') {
            pathDir = 'multi_comp';
        }

        var form;
        if (where == 'customize') {
            form = document.customizeForm;
        } else if (where == 'reorder') {
            form = document.reorder_form;
        } else {
            form = document.choosetypeform;
        }
        form.action = '/jsp/reports/report_viewpdf.jsp';
        form.target = 'pdfPopup';
        form.submit();

        if (customizeWindow) {
            customizeWindow.close();
        }
    }
}

// used by overview tab (see listing_suites_include.jsp)
function expandAllSuiteDetails() {
    $('.moreSuitesInfo').removeClass('hidden');
    $('#expandAllSuites').addClass('hidden');
    $('#hideAllSuites').removeClass('hidden');
}
function hideAllSuiteDetails() {
    $('.moreSuitesInfo').addClass('hidden');
    $('#hideAllSuites').addClass('hidden');
    $('#expandAllSuites').removeClass('hidden');
}
function toggleSuiteDetails(suiteID) {
    $('.suite-details-' + suiteID).toggleClass('hidden');
}

// overview tab (see listing_overview_actions_include.jsp)
function toggleListingAction(elementID) {
    var el = document.getElementById(elementID);
    var display = el.style.display;
    el.style.display=(display?'':'none');
 }




