/**
 *  Search base class.
 *  Subscriptions:
 *      "history:set" - set browser history data
 */
SunGard.Search = Class.create({
    _filterGroupCache: {}, // filter cache
    
    // Constructor
    initialize: function()
    {
        // set site value from first class name on filter container
        this.searchCriteriaObject['Site'] = $w($('filters').className).first();
        
        // cache element references
        this.filterGroupContainer = $('groups');
        
        // initialize controls
        this.uiManager = new SunGard.Search.UI(this, this.filterGroupContainer, this.customErrorTitle, this.customErrorHint);
        this.history = new SunGard.Search.History(this);
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('history:set', this.setBrowserHistory.bind(this));
    },
    
    setBrowserHistory: function(event)
    {
        this.initialLoad = event.memo.initialLoad;
        this.currentSearchData = event.memo.data;
        this.currentSearchOrder = event.memo.order;
        this.currentPage = event.memo.currentPage;
        this.newSearch = (this.currentSearchOrder.length == 0) && (this.currentPage == 1);
        
        this.resetSearchCriteria();
        this.setSearchCriteria();
        this.callWebService();
    },
    
    resetSearchCriteria: function()
    {
        this.searchCriteria = Object.clone(this.searchCriteriaObject);
    },
    
    setSearchCriteria: function()
    {
        // only continue if not a new search (must cache data first)
        if (this.initialLoad || this.newSearch) return;
        
        // set "display-type" data
        this.searchCriteria['CurrentPage'] = this.currentPage;
        
        // set "filter-type" data
        $H(this.currentSearchData).each(function(currentSearchItem)
        {
            var key = currentSearchItem.key;
            var value = currentSearchItem.value;
            
            if (key == 'Query')
            {
                // encode quotes
                var encodedValue = value.replace(/\"/g, '%22').replace(/\'/g, '%27');
                this.searchCriteria[key] = encodedValue;
            }
            else
            {
                // hacked value(s)
                if (this._filterGroupCache[key] == null || !this._filterGroupCache[key].isValidAttribute(value))
                {
                    return;
                }
                
                if (key == 'Date')
                {
                    var dateObj = this._filterGroupCache[key].getAttributeValue(value);
                    this.searchCriteria['StartDate'] = dateObj['StartDate'];
                    this.searchCriteria['EndDate'] = dateObj['EndDate'];
                }
                else
                {
                    this.searchCriteria[key] = value;
                }
            }
        }.bind(this));
    },
    
    callWebService: function($super)
    {
        this.postBody = '{initialLoad:' + this.initialLoad + ', searchCriteria:\'' + Object.toJSON(this.searchCriteria) + '\'}';
        
        new Ajax.Request(this._webServicePath, {
            postBody: this.postBody,
            contentType:'application/json; charset=utf-8',
            onSuccess: this.renderResults.bind(this),
            onFailure: this.renderError.bind(this)
        });
    },
    
    cacheData: function()
    {
        $H(this.data.Filters).each(function(filterGroup)
        {
            this._filterGroupCache[filterGroup.key] = new SunGard.Search.FilterGroup(this, filterGroup.key, filterGroup.value, this.filterGroupContainer);
        }.bind(this));
    },
    
    renderResults: function(response)
    {
        this.data = response.responseJSON.d.evalJSON();
        
        if (this.initialLoad)
        {
            this.initialLoad = false;
            
            this.cacheData();
            this.setSearchCriteria();
            this.callWebService();
            
            return;
        }
        
        // fire custom event (current search info)
        document.fire('search:succeeded', {data: this.data, searchCriteria: this.searchCriteria, currentSearchData: this.currentSearchData, currentSearchOrder: this.currentSearchOrder, filterGroupCache: this._filterGroupCache});
    },
    
    renderError: function(response)
    {
        // fire custom event
        document.fire('search:failed');
    },
    
    // Public Methods
    getTemplateType: function(data)
    {
        // Omniture Tracking data for all resources and media types except internal links and audio/video (DemosAndTours is handled by SunGard.MediaLinks)
        if (data.ResourceType == null || data.MediaType == "internal" || data.MediaType == "video" || data.MediaType == "audio") return;
        
        // WebcastsAndWebinars and Events are "exit" links, otherwise "download" links
        if (data.ResourceType == 'webcasts-and-webinars' || data.ResourceType == 'events' || data.MediaType == "external")
        {
            data.OmnitureTagging = " onclick=\"var title=$(this).childNodes[0].data.strip(); var s=s_gi(s_account); s.tl(this,'e',title);\"";
        }
        else
        {
            data.OmnitureTagging = " onclick=\"var title=$(this).childNodes[0].data.strip(); var s=s_gi(s_account); s.linkTrackVars='prop11,prop12,eVar11,eVar12,events'; s.linkTrackEvents='event1'; s.prop11=title; s.prop12='" + data.ResourceType + "'; s.eVar11=s.prop11; s.eVar12=s.prop12; s.events='event1'; s.tl(this,'d',title);\"";
        }
    }
});

/**
 *  Search History (uses SWFAddress for deep linking and forward/backward functionality)
 *  Subscriptions:
 *      "criteria:selected" - add individual key/value pair to browser history
 *      "criteria:removed" -  remove individual key/value pair from browser history
 *      "criteria:cleared" - clear browser history
 */
SunGard.Search.History = Class.create({
    _delimiter: '/',
    
    // Constructor
    initialize: function(searchManager)
    {
        this.searchManager = searchManager;
        this.initialLoad = true;
        
        // cache variables
        this.key;
        this.value;
        this.currentHash;
        this.pair;
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        SWFAddress.addEventListener(SWFAddressEvent.CHANGE, this.onChange.bind(this)); // also called on initial load
        document.observe('criteria:selected', this.add.bind(this))
        .observe('criteria:removed', this.remove.bind(this))
        .observe('criteria:cleared', this.clear.bind(this));
    },
    
    onChange: function(event)
    {
        // fire custom FX event
        if (this.isBrowserAction) // make sure to capture back/forward button interaction
        {
            document.fire('search:action');
        }
        this.isBrowserAction = true; // reset flag
        
        this.parseHash(event.path);
        
        // fire custom event with browser history data
        document.fire('history:set', {initialLoad: this.initialLoad, data: this.data, order: this.order, currentPage: this.currentPage});
    },
    
    parseHash: function(hash)
    {
        this.data = {};
        this.order = [];
        this.currentPage = 1;
        
        if (hash == this._delimiter) return;
        
        hash.split(this._delimiter).each(function(hashItems, i)
        {
            if (hashItems == '' || hashItems == '#') return;
            
            var hashKeyValue = hashItems.split('=');
            this.key = (hashKeyValue[0] == 'q') ? 'Query' : hashKeyValue[0].capitalize().camelize();
            this.value = (this.key == 'Query') ? hashKeyValue[1].replace(/\+/g, ' ') : hashKeyValue[1];
            if (this.key == 'CurrentPage')
            {
                this.currentPage = this.value;
            }
            else
            {
                this.data[this.key] = this.value;
                this.order.push(this.key);
            }
        }.bind(this));
    },
    
    setInternalData: function(event)
    {
        this.initialLoad = false;
        this.key = event.memo.group;
        this.urlFriendlyKey = (this.key == 'Query') ? 'q' : this.key.underscore().dasherize();
        this.value = (this.key == 'Query') ? event.memo.attribute.replace(/\s/g, '+') : event.memo.attribute;
        this.currentHash = SWFAddress.getValue();
        this.pair = this.urlFriendlyKey + '=' + this.value + this._delimiter;
    },
    
    // Public Methods
    add: function(event)
    {
        this.setInternalData(event);
        this.isBrowserAction = false;
        
        // remove "CurrentPage" filter when:
        // 1. on a page other than the first
        // 2. performing a new search
        // b/c the new search may not have that valid "CurrentPage"
        if (this.key != 'CurrentPage' && this.currentPage && this.currentPage != 1)
        {
            this.currentHash = this.currentHash.replace(/(current-page)\=\d+\//, '');
        }
        
        var filterHasBeenSet = this.data[this.key];
        var pageHasBeenSet = (this.key == 'CurrentPage' && this.currentHash.indexOf('current-page') != -1);
        // if search has already been performed on this filter, replace the value in the hash
        if (filterHasBeenSet || pageHasBeenSet)
        {
            var regex = new RegExp(this.urlFriendlyKey + '\=(\\w+\\s*\'*\\+*\"*)+\/');
            SWFAddress.setValue(this.currentHash.replace(regex, this.pair));
        }
        else
        {
            SWFAddress.setValue(this.currentHash + this.pair);
        }
    },
    
    remove: function(event)
    {
        this.setInternalData(event);
        this.isBrowserAction = false;
        
        SWFAddress.setValue(this.currentHash.replace(this.pair, ''));
    },
    
    clear: function()
    {
        this.initialLoad = false;
        this.isBrowserAction = false;
        SWFAddress.setValue('');
    }
});

/**
 *  Search UI Manager
 *  Subscriptions:
 *      "search:failed" - clear result items
 *      "search:action" - fade out results container
 *      "search:displayResults, search:displayNoResults" - fade in results container
 *      "search:succeeded" - build UI
 */
SunGard.Search.UI = Class.create({
    // Constructor
    initialize: function(searchManager, filterGroupContainer, customErrorTitle, customErrorHint)
    {
        this.searchManager = searchManager;
        this.filterGroupContainer = filterGroupContainer;
        this.customErrorTitle = customErrorTitle;
        this.customErrorHint = customErrorHint;
        
        // cache element references
        this.infoContainer = $('results-info');
        this.resultsContainer = $('results-body');
        this.itemsContainer = $('items');
        
        // initialize controls
        this.filterHistory = new SunGard.Search.FilterHistory(this.searchManager, this.filterGroupContainer);
        this.queryFilter = new SunGard.Search.QueryFilter(this.searchManager);
        this.loading = new SunGard.Search.UI.Loading(this);
        this.searchTotals = new SunGard.Search.UI.Totals(this, this.infoContainer);
        this.pagination = new SunGard.Search.UI.Pagination(this.searchManager, this, this.itemsContainer);
        this.messaging = new SunGard.Search.UI.Messaging(this.customErrorTitle, this.customErrorHint);
        this.keyMatch = new SunGard.Search.UI.KeyMatch(this.infoContainer);
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('search:failed', this.clearItems.bind(this))
        .observe('search:action', this.fadeOut.bind(this))
        .observe('search:displayResults', this.fadeIn.bind(this))
        .observe('search:displayNoResults', this.fadeIn.bind(this))
        .observe('search:succeeded', this.buildUI.bind(this));
    },
    
    buildUI: function(event)
    {
        this.data = event.memo.data;
        this.searchCriteria = event.memo.searchCriteria;
        
        this.omnitureTagging();
        
        this.clearItems();
        
        if (this.data.TotalResults > 0) this.buildResults();
        
        // set filter title display flag
        this.hideFilterTitle = (event.memo.data.AllFiltersSelected.toLowerCase() == 'true' || event.memo.data.TotalResults == 0) ? true : false;
        
        // display "Narrow Search" title
        this.displayFilterTitle();
    },
    
    omnitureTagging: function()
    {
        if (s == undefined || s == null) return; // check that Omniture JS file was loaded (where s is set)
        
        // set search variables
        s.events = 'event11';
        s.prop1 = Object.toQueryString(this.searchCriteria);
        var searchTerms = unescape(this.searchCriteria['Query']).toLowerCase();
        s.prop2 = (this.searchCriteria['Query'] == '') ? 'faceted search' : searchTerms;
        s.eVar1 = s.prop2;
        s.prop3 = this.data.TotalResults;
        s.eVar2 = s.prop3;
        
        // clear other variables
        s.linkTrackVars = '';
        s.linkTrackEvents = '';
        s.eVar11 = '';
        s.eVar12 = '';
        s.eVar15 = '';
        s.prop11 = '';
        s.prop12 = '';
        s.prop15 = '';
        
        // call tracking method
        s.t();
    },
    
    buildResults: function()
    {
        var resultsList = new Element('ul', {className: 'list-results'});
        this.data.Results.each(function(resultsData)
        {
            // update results
            resultsList.insert(this.searchManager.getTemplateType(resultsData));
        }.bind(this));
        
        // insert results into DOM
        this.itemsContainer.insert(resultsList);
        
        // fire custom events
        document.fire('search:displayResults', {currentPage: parseInt(this.searchCriteria['CurrentPage']), resultsPerPage: parseInt(this.searchCriteria['ResultsPerPage']), numberOfReturnedResults: this.data.Results.length, totalResults: parseInt(this.data.TotalResults), keyMatch: this.data.KeyMatch});
        document.fire('copy:reset');
        
        // return to top of page
        $('header').scrollTo();
    },
    
    clearItems: function()
    {
        this.itemsContainer.childElements().invoke('remove');
    },
    
    fadeIn: function()
    {
        // only run after initial load (same as after first fadeOut has occurred)
        if (this.fadeOutFx)
        {
            this.fadeOutFx.cancel();
            
            this.fadeInFx = new Effect.Opacity(this.resultsContainer,
                {
                    duration: 1,
                    from: 0.3,
                    to: 1.0
                }
            );
        }
    },
    
    fadeOut: function()
    {
        this.fadeOutFx = new Effect.Opacity(this.resultsContainer,
            {
                duration: 1,
                from: 1.0,
                to: 0.3
            }
        );
    },
    
    displayFilterTitle: function()
    {
        // insert filter groups title "Narrow Search" into DOM
        if (this.filterGroupTitle == null)
        {
            this.filterGroupTitle = new Element('h2').update('Narrow Search');
            this.filterGroupContainer.insert({top: this.filterGroupTitle});
        }
        
        // determine whether to show or hide "Narrow Search" header
        this.filterGroupTitle[(this.hideFilterTitle) ? 'hide' : 'show']();
    }
});

/**
 *  Query Filter (inherits from SunGard.GenericSearchFilter)
 *  Subscriptions:
 *      "search:failed" - disable
 *      "search:succeeded" - set value
 */
SunGard.Search.QueryFilter = Class.create(SunGard.GenericSearchFilter, {
    // Constructor
    initialize: function($super, searchManager)
    {
        this.searchManager = searchManager;
        
        // cache element references
        this.fld = $$('#query input')[0];
        
        // call base class method
        $super();
    },
    
    // Private Methods
    attachEvents: function($super)
    {
        document.observe('search:failed', this.disable.bind(this))
        .observe('search:succeeded', this.setValue.bind(this));
        
        $super();
    },
    
    setValue: function($super, event)
    {
        $super(unescape(event.memo.searchCriteria['Query']));
    },
    
    // Public Methods
    runQuery: function()
    {
        // fire custom FX event
        document.fire('search:action');
        
        if (this.fld.value == '')
        {
            // remove "Query" from search
            document.fire('criteria:removed', {group: 'Query', attribute: this.value});
        }
        else
        {
            // add "Query" to search
            document.fire('criteria:selected', {group: 'Query', attribute: this.fld.value});
        }
    }
});

/**
 *  Search Loading handles the display of the loading animation
 *  Subscriptions:
 *      "history:set, search:action" - show
 *      "search:displayResults, search:displayNoResults" - hide
 */
SunGard.Search.UI.Loading = Class.create({
    // Constructor
    initialize: function(uiManager)
    {
        this.uiManager = uiManager;
        
        // cache element references
        this.icon = $('loading').select('img')[0];
        this.animatedIconSrc = this.icon.src;
        this.disabledIconSrc = this.animatedIconSrc.split('-animated.gif')[0] + '-disabled.gif';
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('history:set', this.show.bind(this))
        .observe('search:action', this.show.bind(this))
        .observe('search:displayResults', this.hide.bind(this))
        .observe('search:displayNoResults', this.hide.bind(this));
    },
    
    show: function()
    {
        this.icon.src = this.animatedIconSrc;
    },
    
    hide: function()
    {
        this.icon.src = this.disabledIconSrc;
    }
});

/**
 *  Search Totals handles information about the number of results being displayed on the current page.
 *  Ex UI: Results 1-10 of 24
 *  Subscriptions:
 *      "search:displayResults" - build UI
 *      "search:displayNoResults" - destroy UI
 */
SunGard.Search.UI.Totals = Class.create({
    // Constructor
    initialize: function(uiManager, parentContainer)
    {
        this.uiManager = uiManager;
        this.parentContainer = parentContainer;
        
        // cache container
        this.container = new Element('div', {id: 'totals'});
        
        // insert container into DOM
        this.parentContainer.insert(this.container);
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('search:displayResults', this.buildUI.bind(this))
        .observe('search:displayNoResults', this.destroyUI.bind(this));
    },
    
    buildUI: function(event)
    {
        var currentPage = event.memo.currentPage;
        var resultsPerPage = event.memo.resultsPerPage;;
        var numberOfReturnedResults = event.memo.numberOfReturnedResults;
        var totalResults = event.memo.totalResults;
        
        var pageStart = (resultsPerPage * (currentPage - 1)) + 1;
        var pageEnd = (numberOfReturnedResults == resultsPerPage) ? (currentPage * resultsPerPage) : totalResults;
        
        // update content
        this.container.update(new Element('p').update('Results ' + pageStart + '&#8211;' + pageEnd + ' of ' + totalResults));
    },
    
    destroyUI: function()
    {
        // remove content
        this.container.update('');
    }
});

/**
 *  Pagination handles the pagination functionality at the bottom of the results list.
 *  Subscriptions:
 *      "search:displayResults" - build UI
 *      "search:displayNoResults" - destroy UI
 */
SunGard.Search.UI.Pagination = Class.create({
    _maxNumberOfDisplayedPages: 9,
    
    // Constructor
    initialize: function(searchManager, uiManager, parentContainer)
    {
        this.searchManager = searchManager;
        this.uiManager = uiManager;
        this.parentContainer = parentContainer;
        
        // space on either side of current page in order to center current page when possible
        this.buffer = Math.floor(this._maxNumberOfDisplayedPages / 2);
        
        // cache container and attached event
        this.container = new Element('div', {id: 'pagination'}).observe('click', this.onClick.bind(this));
        
        // insert container into UI
        this.parentContainer.insert({after: this.container});
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('search:displayResults', this.buildUI.bind(this))
        .observe('search:displayNoResults', this.destroyUI.bind(this));
    },
    
    buildUI: function(event)
    {
        this.currentPage = event.memo.currentPage;
        this.resultsPerPage = event.memo.resultsPerPage;;
        this.totalResults = event.memo.totalResults;
        
        // don't need pagination b/c only 1 page of results
        if (this.totalResults <= this.resultsPerPage)
        {
            this.destroyUI();
            return;
        }
        
        // for individual page numbers
        this.numberOfPages = this.getNumberOfPages();
        this.numberOfDisplayedPages = this.getNumberOfDisplayedPages();
        
        var previous = (this.currentPage == 1) ? new Element('p', {id: 'previous', className: 'off'}).update('Previous') : new Element('p', {id: 'previous', className: 'on'}).insert(new Element('a', {href: '#' + (this.currentPage - 1)}).update('Previous'));
        var list = new Element('ul');
        for (var i = 1, j = this.getFirstPageNumber(); i <= this.numberOfDisplayedPages; i++, j++)
        {
            list.insert((j == this.currentPage) ? new Element('li').update('<strong>' + j + '</strong>') : new Element('li').insert(new Element('a', {href: '#' + j}).update(j)));
        }
        var next = (this.currentPage == this.numberOfPages) ? new Element('p', {id: 'next', className: 'off'}).update('Next') : new Element('p', {id: 'next', className: 'on'}).insert(new Element('a', {href: '#' + (this.currentPage + 1)}).update('Next'));
        
        this.container.update(previous).insert(list).insert(next);
    },
    
    destroyUI: function()
    {
        // remove content
        this.container.update('');
    },
    
    getNumberOfPages: function()
    {
        return Math.ceil(this.totalResults / this.resultsPerPage);
    },
    
    getNumberOfDisplayedPages: function()
    {
        return this[(this.numberOfPages > this._maxNumberOfDisplayedPages) ? '_maxNumberOfDisplayedPages' : 'numberOfPages'];
    },
    
    getFirstPageNumber: function()
    {
        if (this.numberOfPages <= this._maxNumberOfDisplayedPages || (this.currentPage - this.buffer) < 1)
        {
            return 1;
        }
        else if ((this.currentPage + this.buffer) > this.numberOfPages)
        {
            return (this.numberOfPages - this._maxNumberOfDisplayedPages + 1);
        }
        else
        {
            return (this.currentPage - this.buffer);
        }
    },
    
    onClick: function(event)
    {
        // stop default click event
        Event.stop(event);
        
        var element = Event.element(event);
        
        // make sure link was clicked
        if (element.tagName != 'A') return;
        
        // fire custom FX event
        document.fire('search:action');
        
        // make sure clicked link loses focus
        $(element).blur();
        
        // add "CurrentPage" to search
        document.fire('criteria:selected', {group: 'CurrentPage', attribute: element.hash.substr(1)});
    }
});

/**
 *  Messaging handles search error/no-results messages
 *  Subscriptions:
 *      "search:succeeded" - clear
 *      "search:failed" - build error
 */
SunGard.Search.UI.Messaging = Class.create({
    // Constructor
    initialize: function(customErrorTitle, customErrorHint)
    {
        this.customErrorTitle = customErrorTitle;
        this.customErrorHint = (customErrorHint == '' || customErrorHint == null) ? '' : '<li>' + customErrorHint + '</li>';
        
        // cache elements
        this.container = $('messages');
        this.title = new Element('h3');
        this.instructions = new Element('div');
        // cache strings
        this.errorTitle = 'We apologize&#8212;this service is temporarily unavailable. Please try again later.';
        this.defaultInstructions = '<p>Suggestions:</p><ul><li>Make sure all words are spelled correctly.</li><li>Try different search terms.</li><li>Try fewer or more general search terms.</li><li>If using the &#8220;Narrow Search&#8221; feature on the left side, hit &#8220;Clear Search&#8221; and try again.</li>' + this.customErrorHint + '</ul>';
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('search:succeeded', this.buildUI.bind(this))
        .observe('search:failed', this.buildError.bind(this));
    },
    
    buildUI: function(event)
    {
        this.data = event.memo.data;
        this.searchCriteria = event.memo.searchCriteria;
        
        this.clear();
        
        if (this.data.TotalResults == 0) this.buildNoResults();
    },
    
    buildNoResults: function()
    {
        if (this.customErrorTitle && this.searchCriteria['Query'] == '') // only show search specific error msg if it exists and no "query" search was performed
        {
            this.title.update(this.customErrorTitle);
            this.instructions.update('');
        }
        else
        {
            this.title.update((this.searchCriteria['Query'] == '') ? 'Your search has returned no results.' : 'Your search &#8212; <strong>&#8220;' + unescape(this.searchCriteria['Query']) + '&#8221;</strong> &#8212; has returned no results.');
            this.instructions.update(this.defaultInstructions);
        }
        
        this.display();
    },
    
    buildError: function()
    {
        this.title.update(this.errorTitle);
        this.instructions.update('');
        
        this.clear();
        this.display();
    },
    
    display: function()
    {
        this.container.insert(this.title).insert(this.instructions);
        
        // fire custom event
        document.fire('search:displayNoResults');
    },
    
    clear: function()
    {
        this.container.childElements().invoke('remove');
    }
});

/**
 *  Key Match
 *  Subscriptions:
 *      "search:displayResults, search:displayNoResults"
 */
SunGard.Search.UI.KeyMatch = Class.create({
    _id: 'key-match',
    
    // Constructor
    initialize: function(parentContainer)
    {
        this.parentContainer = parentContainer;
        
        // cache elements
        this.link = new Element('a', {href: ''});
        this.container = new Element('div', {id: this._id}).update('Did you mean: ').insert(this.link);
        // cache bound function calls
        this.bOnClick = this.onClick.bindAsEventListener(this);
        
        // set up methods
        this.attachEvents();
    },
    
    // Private Methods
    attachEvents: function()
    {
        document.observe('search:displayResults', this.brain.bind(this))
        .observe('search:displayNoResults', this.brain.bind(this));
    },
    
    brain: function(event)
    {
        this.data = event.memo.KeyMatch;
        
        // build/destroy depending on data
        this[(this.data == '' || this.data == null) ? 'destroy' : 'build']();
    },
    
    build: function()
    {
        this.link.update(this.data).observe('click', this.bOnClick);
        
        // only insert container into DOM if it isn't currently
        if ($(this._id)) return;
        this.parentContainer.insert({before: this.container});
    },
    
    destroy: function()
    {
        // only remove key match container from DOM if it exists
        if (!$(this._id)) return;
        
        // stop listening
        this.link.stopObserving('click', this.bOnClick);
        
        // remove element
        this.container.remove();
    },
    
    onClick: function(event)
    {
        // stop default click event
        Event.stop(event);
        
        // run animation
        new Effect.BlindUp(this.container,
            {
                duration: .5
            }
        );
        
        // fire custom FX event
        document.fire('search:action');
        
        // change query to "KeyMatch"
        document.fire('criteria:selected', {group: 'Query', attribute: this.data});
    }
});

/**
 *  Event Search (inherits from SunGard.Search)
 */
SunGard.Search.Event = Class.create(SunGard.Search, {
    _webServicePath: '/webservices/NewsEventsSearchService.asmx/GetEventsBySearchCriteria',
    
    // Constructor
    initialize: function($super)
    {
        // set search criteria object
        this.searchCriteriaObject = es.Criteria;
        
        // set Events specific error title
        this.customErrorTitle = 'Sorry, there are no upcoming events.  Please check again in the near future.';
        
        // call base class method
        $super();
    },
    
    // Public Methods
    getTemplateType: function($super, data)
    {
        $super(data);
        
        return new es.Template(data);
    }
});
var es = SunGard.Search.Event;

/**
 *  Event Template
 */
es.Template = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = (this.data.Url == '' || this.data.Url == '#') ? '<h3>#{Title}</h3>' : '<h3><a href="#{Url}" target="_blank"#{OmnitureTagging}>#{Title} ' + SunGard.MediaTypeIcons[this.data.MediaType] + '</a></h3>';
        var date = (this.data.EndDate == '' || (this.data.StartDate == this.data.EndDate)) ? '<p class="date">#{StartDate}</p>' : '<p class="date">#{StartDate}&#8211;#{EndDate}</p>';
        date = (this.data.StartDate == '') ? '<p class="date">#{EndDate}</p>' : date;
        date = (this.data.StartDate == '' && this.data.EndDate == '') ? '' : date;
        var location = (this.data.Location == '') ? '' : '<p class="location">#{Location}</p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        
        var t = new Template('<li>'+ title + date + location + summary + '</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Event Search Criteria
 */
es.Criteria = {
    StartDate: '',
    EndDate: '',
    Regions: '',
    EventTypes: '',
    Products: '',
    Solutions: '',
    Services: '',
    Query: '',
    CurrentPage: 1,
    ResultsPerPage: 10,
    Site: ''
};

/**
 *  Press Release Search (inherits from SunGard.Search)
 */
SunGard.Search.PressRelease = Class.create(SunGard.Search, {
    _webServicePath: '/webservices/NewsEventsSearchService.asmx/GetPressReleasesBySearchCriteria',
    
    // Constructor
    initialize: function($super)
    {
        // set search criteria object
        this.searchCriteriaObject = prs.Criteria;
        
        // call base class method
        $super();
    },
    
    // Public Methods
    getTemplateType: function($super, data)
    {
        $super(data);
        
        return new prs.Template(data);
    }
});
var prs = SunGard.Search.PressRelease;

/**
 *  Press Release Template
 */
prs.Template = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}">#{Title} ' + SunGard.MediaTypeIcons[this.data.MediaType] + '</a></h3>';
        var dateAndLocation = (this.data.Location == '') ? '<p class="date">#{Date}</p>' : '<p class="date-location">#{Date} &#8212; #{Location}</p>';
        dateAndLocation = (this.data.Date == '') ? '<p class="location">#{Location}</p>' : dateAndLocation;
        dateAndLocation = (this.data.Date == '' && this.data.Location == '') ? '' : dateAndLocation;
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        
        var t = new Template('<li>'+ title + dateAndLocation + summary +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Press Release Search Criteria
 */
prs.Criteria = {
    StartDate: '',
    EndDate: '',
    Regions: '',
    Topics: '',
    Products: '',
    Solutions: '',
    Services: '',
    Query: '',
    CurrentPage: 1,
    ResultsPerPage: 10,
    Site: ''
};

/**
 *  Resource Search (inherits from SunGard.Search)
 */
SunGard.Search.Resource = Class.create(SunGard.Search, {
    _webServicePath: '/webservices/ResourceLibrarySearchService.asmx/GetResourceLibraryItemsBySearchCriteria',
    
    // Constructor
    initialize: function($super)
    {
        // set search criteria object
        this.searchCriteriaObject = rs.Criteria;
        
        // call base class method
        $super();
    },
    
    // Public Methods
    getTemplateType: function($super, data)
    {
        $super(data);
        
        var type = data.ResourceType.camelize();
        type = type.charAt(0).toUpperCase() + type.substring(1);
        
        return new rs.Templates[type](data);
    }
});
var rs = SunGard.Search.Resource;

/**
 *  Resource Search Criteria
 */
rs.Criteria = {
    ResourceTypes: '',
    Products: '',
    Solutions: '',
    Regions: '',
    Services: '',
    Query: '',
    CurrentPage: 1,
    ResultsPerPage: 10,
    Site: ''
};

/**
 *  Resource Templates namespace
 *      SunGard.Search.Resource.Templates.BrochuresAndDatasheets (Brochures & Datasheets)
 *      SunGard.Search.Resource.Templates.DemosAndTours (Demos & Tours)
 *      SunGard.Search.Resource.Templates.CaseStudies (Case Studies)
 *      SunGard.Search.Resource.Templates.WhitePapersAndArticles (Whitepapers & Articles)
 *      SunGard.Search.Resource.Templates.WebcastsAndWebinars (Webcasts & Webinars)
 *      SunGard.Search.Resource.Templates.Newsletters (Newsletters)
 *      SunGard.Search.Resource.Templates.Audios (Audios)
 *      SunGard.Search.Resource.Templates.Videos (Videos)
 */
rs.Templates = {};

/**
 *  Brochures & Datasheets Template
 */
rs.Templates.BrochuresAndDatasheets = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title}</a></h3>';
        var type = '<p><strong>Brochure &amp; Datasheet</strong></p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var file = (this.data.FileSize == '') ? '<p><strong>' + SunGard.MediaTypeIcons[this.data.MediaType] + '[PDF]</strong></p>' : '<p><strong>' + SunGard.MediaTypeIcons[this.data.MediaType] + '[PDF, #{FileSize} KB]</strong></p>';
        
        var t = new Template('<li>'+ title + type + summary + file +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Demos & Tours Template
 */
rs.Templates.DemosAndTours = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var videoFileAttributes = (this.data.MediaType == 'video') ? ' class="video GetDemoFlashVideoByID"' : '';
        var title = '<h3><a href="#{Url}"' + videoFileAttributes + '>#{Title} ' + SunGard.MediaTypeIcons[this.data.MediaType] + '</a></h3>';
        var type = '<p><strong>Demo &amp; Tour</strong></p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        
        var t = new Template('<li>'+ title + type + summary + '</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Case Studies Template
 */
rs.Templates.CaseStudies = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title}</a></h3>';
        var type = '<p><strong>Case Study</strong></p>';
        var date = (this.data.Date == '') ? '' : '<p class="date">#{Date}</p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var file = (this.data.FileSize == '') ? '' : '<p><strong>' + icon + '[PDF, #{FileSize} KB]</strong></p>';
        
        var t = new Template('<li>'+ title + type + date + summary + file +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  White Papers & Articles Template
 */
rs.Templates.WhitePapersAndArticles = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title}</a></h3>';
        var type = (this.data.PaperType == '') ? '<p><strong>White Paper &amp; Article</strong></p>' : '<p><strong>#{PaperType}</strong></p>';
        var date = (this.data.Date == '') ? '' : '<p class="date">#{Date}</p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var file = (this.data.FileSize == '') ? '' : '<p><strong>' + icon + '[PDF, #{FileSize} KB]</strong></p>';
        
        var t = new Template('<li>'+ title + type + date + summary + file +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Webcasts & Webinars Template
 */
rs.Templates.WebcastsAndWebinars = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title} ' + SunGard.MediaTypeIcons[this.data.MediaType] + '</a></h3>';
        var type = '<p><strong>Webcast &amp; Webinar</strong></p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var date = (this.data.Date == '') ? '' : '<p class="date">#{Date}</p>';
        
        var t = new Template('<li>'+ title + type + date + summary +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Newsletters Template
 */
rs.Templates.Newsletters = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title}</a></h3>';
        var type = '<p><strong>Newsletter</strong></p>';
        var date = (this.data.Date == '') ? '' : '<p class="date">#{Date}</p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var file = (this.data.FileSize == '') ? '' : '<p><strong>' + icon + '[PDF, #{FileSize} KB]</strong></p>';
        
        var t = new Template('<li>'+ title + type + date + summary + file +'</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Audios Template
 */
rs.Templates.Audio = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}" class="audio GetAudioByID">#{Title}</a></h3>';
        var type = '<p><strong>Audio</strong></p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var info = '<p><strong>' + icon + ((this.data.Time == null || this.data.Time == '') ? '' : '[Total time #{Time}]') + '</strong></p>';
        
        var t = new Template('<li>'+ title + type + summary + info + '</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Videos Template
 */
rs.Templates.Video = Class.create({
    // Constructor
    initialize: function(data)
    {
        this.data = data;
    },
    
    // Public Methods
    toElement: function()
    {
        var title = '<h3><a href="#{Url}" class="video GetVideoByID">#{Title}</a></h3>';
        var type = '<p><strong>Video</strong></p>';
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var info = '<p><strong>' + icon + ((this.data.Time == null || this.data.Time == '') ? '' : '[Total time #{Time}]') + '</strong></p>';
        
        var t = new Template('<li>'+ title + type + summary + info + '</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Sitewide Search (inherits from SunGard.Search)
 */
SunGard.Search.Sitewide = Class.create(SunGard.Search, {
    _webServicePath: '/webservices/SitewideSearchService.asmx/Search',
    
    // Constructor
    initialize: function($super)
    {
        this.searchCriteriaObject = sw.Criteria;
        
        // set Sitewide specific error hint
        this.customErrorHint = 'Or, please <a href="'+ rootPath + sitePath + 'sitemap.aspx">visit our site map</a>.';
        
        // call base class method
        $super();
    },
    
    // Public Methods
    getTemplateType: function($super, data)
    {
        $super(data);
        
        return new sw.Template(data, this.searchCriteria['Query']);
    }
});
var sw = SunGard.Search.Sitewide;

/**
 *  Sitewide Template
 */
sw.Template = Class.create({
    // Constructor
    initialize: function(data, query)
    {
        this.data = data;
        this.query = query;
    },
    
    // Public Methods
    toElement: function()
    {
        var noSiteAvailable = this.data.Url == '' || this.data.Url == '#';
        var title = (this.data.MediaType == 'external') ? '<h3><a href="#{Url}" target="_blank"#{OmnitureTagging}>#{Title} ' + SunGard.MediaTypeIcons[this.data.MediaType] + '</a></h3>' : '<h3><a href="#{Url}"#{OmnitureTagging}>#{Title}</a></h3>';
        title = (this.data.MediaType == 'video') ? '<h3><a href="#{Url}" class="video GetVideoByID">#{Title}</a></h3>' : title;
        title = (this.data.MediaType == 'audio') ? '<h3><a href="#{Url}" class="audio GetAudioByID">#{Title}</a></h3>' : title;
        title = (this.data.ResourceType == '') ? '<h3><a href="#{Url}">#{Title}</a></h3>' : title;
        title = noSiteAvailable ? '<h3>#{Title}</h3>' : title;
        var summary = (this.data.Summary == '') ? '' : '<p>#{Summary}</p>';
        var displayUrl = (this.data.DisplayUrl == '' || this.data.DisplayUrl == '#' || this.data.MediaType == 'video' || this.data.MediaType == 'audio') ? '' : '<p><strong>#{DisplayUrl}</strong></p>';
        var icon = SunGard.MediaTypeIcons[this.data.MediaType];
        var file = (this.data.MediaType == 'pdf') ? '<p><strong>' + icon + ' [PDF, ' + this.data.FileSize + ' KB]</strong></p>' : '';
        var media = (this.data.MediaType == 'video' || this.data.MediaType == 'audio') ? '<p><strong>' + icon + ((this.data.Time == null || this.data.Time == '') ? '' : '[Total time #{Time}]') + '</strong></p>' : '';        
        var filter = noSiteAvailable ? '<p><strong>See all events listings for &#8220;<a href="' + rootPath + sitePath + 'events.aspx#/q=' + this.query.replace(/\s/g, '+') + '/">' + this.query + '</a>&#8221;</strong></p>' : '';
        
        var t = new Template('<li>'+ title + summary + displayUrl + file + media + filter + '</li>');
        return t.evaluate(this.data);
    }
});

/**
 *  Sitewide Search Criteria
 */
sw.Criteria = {
    Query: '',
    Businesses: '',
    Sections: '',
    CurrentPage: 1,
    ResultsPerPage: 10
};

/**
 *  Filter History
 *  history of chosen filters for undoing
 *  Subscriptions:
 *      "search:succeeded" - build UI
 */
SunGard.Search.FilterHistory = Class.create({
    // Constructor
    initialize: function(searchManager, filterUiContainer)
    {
        this.searchManager = searchManager;
        this.filterUiContainer = filterUiContainer;
        
        // cache elements, container and attached event
        this.title = new Element('h2').update('Current Search');
        this.list = new Element('ul')
        this.clear = new Element('p').insert(new Element('a', {href: '#clear'}).update('Clear Search'));
        this.container = new Element('div', {id: 'history'}).insert(this.title).insert(this.list).insert(this.clear).observe('click', this.onClick.bind(this)).hide();
        
        // insert container into UI
        this.filterUiContainer.insert({before: this.container});
        
        // attach custom events
        document.observe('search:succeeded', this.buildUI.bind(this));
    },
    
    // Private Methods
    buildUI: function(event)
    {
        this.data = event.memo.data;
        this.searchData = event.memo.currentSearchData;
        this.searchOrder = event.memo.currentSearchOrder;
        this.filterGroupCache = event.memo.filterGroupCache;
        
        this[(this.searchOrder.length == 0) ? 'hide' : 'show']();
    },
    
    show: function()
    {
        // get current history attributes
        this.getAttributes();
        
        // visible AND Empty = hide
        if (this.container.visible() && (this.list.select('li').size() == 0))
        {
            this.hide();
            return;
        }
        // visible AND List = nothing
        if (this.container.visible() && (this.list.select('li').size() > 0)) return;
        // hidden AND Empty = nothing
        if (!this.container.visible() && (this.list.select('li').size() == 0)) return;
        
        new Effect.BlindDown(this.container,
            {
                duration: .75
            }
        );
    },
    
    hide: function()
    {
        // only animate visibility if currently visible
        if (!this.container.visible()) return;
        new Effect.BlindUp(this.container,
            {
                duration: .75,
                afterFinish: function()
                {
                    this.container.hide(); // hide when done
                    document.fire('criteria:cleared');
                }.bind(this),
                queue:
                {
                    position: 'end',
                    scope: 'history',
                    limit: 1
                }
            }
        );
    },
    
    onClick: function(event)
    {
        // stop default click event
        Event.stop(event);
        
        var element = Event.element(event);
        // make sure link was clicked
        if (element.tagName != 'A') return;
        // make sure clicked link loses focus
        $(element).blur();
        
        // fire custom FX event
        document.fire('search:action');
        
        if (element.hash.substr(1) == 'clear' || this.list.select('li').size() == 1) // clear btn clicked OR only attribute in history clicked
        {
            this.hide();
        }
        else // remove single attribute
        {
            // get container element to animate and remove
            var container = Event.findElement(event, 'LI');
            new Effect.BlindUp(container,
                {
                    duration: .5,
                    afterFinish: function() {
                        container.remove();
                        document.fire('criteria:removed', {group: element.id, attribute: this.searchData[element.id]});
                    }.bind(this)
                }
            );
        }
    },
    
    getAttributes: function()
    {
        // clear prev
        this.list.childElements().invoke('remove');
        
        // set new
        this.searchOrder.each(function(key)
        {
            var value = this.searchData[key];
            // confirm attribute is valid
            if (key == 'Query')
            {
                this.list.insert({top: '<li><a id="' + key + '" href="">Query: ' + value + '</a></li>'});
            }
            if (this.data.Filters[key] && this.data.Filters[key][value])
            {
                this.list.insert({top: '<li><a id="' + key + '" href="">' + this.filterGroupCache[key].getAttributeTitle(value) + '</a></li>'});
            }
        }.bind(this));
    }
});

/**
 *  Filter Group
 *  Subscriptions:
 *      "search:succeeded" - build UI
 */
SunGard.Search.FilterGroup = Class.create({
    _maxNumberOfVisibleAttributes: 10,
    
    // Constructor
    initialize: function(searchManager, name, groupData, filterGroupContainer)
    {
        this.searchManager = searchManager;
        this.name = name;
        this.groupData = groupData;
        this.filterGroupContainer = filterGroupContainer;
        
        // set up methods
        this.generateElementCache();
        this.generateAttributeCache();
        this.insert();
        this.attachEvents();
    },
    
    // Private Methods
    generateElementCache: function()
    {
        // create UI-friendly name (split group "name" into separate words)
        this.uiFriendlyName = this.name.underscore().gsub('_', ' ').capitalize();
        
        // create URL-friendly name
        this.urlFriendlyName = this.name.underscore().dasherize();
        
        // cache toggle (More/Fewer) elements
        this.toggleMoreText = 'More ' + this.uiFriendlyName;
        this.toggleFewerText = 'Fewer ' + this.uiFriendlyName;
        this.toggleLink = new Element('a', {href: ''}).observe('click', this.onToggleClick.bind(this));
        
        // cache elements, container and click event
        this.title = new Element('h3').update(this.uiFriendlyName); // filter group header
        this.list = new Element('ul').observe('click', this.onClick.bind(this)); // attribute list
        this.container = new Element('div', {className: 'filter'}).insert(this.title).insert(this.list).hide(); // filter group container
    },
    
    onClick: function(event)
    {
        // stop default click event
        Event.stop(event);
        
        var element = Event.element(event);
        // make sure link was clicked
        if (element.tagName != 'A') return;
        // make sure clicked link loses focus
        $(element).blur();
        
        // fire custom FX event
        document.fire('search:action');
        
        new Effect.BlindUp(this.container,
            {
                duration: 0.5,
                afterFinish: function() { document.fire('criteria:selected', {group: this.name, attribute: element.id}); }.bind(this) // add filter to search
            }
        );
    },
    
    generateAttributeCache: function()
    {
        // generate attribute cache and insert into list
        this.filterAttributeCache = {};
        this.groupData.each(function(attributeData)
        {
            this.filterAttributeCache[attributeData.ID] = new SunGard.Search.FilterGroup.FilterAttribute(this, this.name, this.urlFriendlyName, attributeData);
            this.list.insert(this.filterAttributeCache[attributeData.ID]);
        }.bind(this));
    },
    
    insert: function()
    {
        // insert container into UI
        this.filterGroupContainer.insert(this.container);
    },
    
    attachEvents: function()
    {
        // attach "subscription" events
        document.observe('search:succeeded', this.buildUI.bind(this));
    },
    
    buildUI: function(event)
    {
        this.isCurrentSearchMember = (event.memo.currentSearchData[this.name] && this.filterAttributeCache[event.memo.currentSearchData[this.name]]) ? true : false; // make sure group was previously picked AND value is valid for test
        if (this.isCurrentSearchMember) // hide if group is part of current search criteria
        {
            this.hide();
            return;
        }
        
        this.attributeQuantities = event.memo.data.Filters[this.name];
        this.configureAttributes();
        if (this.numberOfVisibleAttributes == 0) // hide if group has no visible attributes (quantities > 0)
        {
            this.hide();
            return;
        }
        
        this.show();
        this.configureToggle();
    },
    
    show: function()
    {
        this.container.show();
    },
    
    hide: function()
    {
        this.container.hide();
    },
    
    configureAttributes: function()
    {
        this.attributesOutsideOfVisibleRange = [];
        this.numberOfVisibleAttributes = 0;
        
        $H(this.filterAttributeCache).each(function(attribute, i)
        {
            if (this.attributeQuantities[attribute.key] == 0)
            {
                attribute.value.hide();
                return;
            }
            
            this.numberOfVisibleAttributes++;
            attribute.value.show();
            
            if (this.numberOfVisibleAttributes <= this._maxNumberOfVisibleAttributes) return;
            
            this.attributesOutsideOfVisibleRange.push(attribute.value); // store for future toggle use
            attribute.value.hide();
        }.bind(this));
    },
    
    configureToggle: function()
    {
        if (this.numberOfVisibleAttributes > this._maxNumberOfVisibleAttributes)
        {
            // update text (will revert to hidden)
            this.toggleLink.update(this.toggleMoreText);
            
            // create and insert toggle first time around
            if (this.toggle == null)
            {
                // create toggle
                this.toggle = new Element('p', {className: 'more'}).insert(this.toggleLink);
                
                // insert toggle into DOM
                this.container.insert(this.toggle);
                
                return;
            }
            // show toggle on subsequent visits
            this.toggle.show();
        }
        else
        {
            if (this.toggle == null) return;
            
            // hide toggle
            this.toggle.hide();
        }
    },
    
    onToggleClick: function(event)
    {
        // stop default click event
        Event.stop(event);
        
        var element = Event.element(event);
        // make sure link loses focus
        $((element.tagName != 'A') ? Event.findElement(event, 'A') : element).blur();
        
        if (this.toggle.hasClassName('more'))
        {
            // show all attributes
            this.showAttributesOutsideVisibleRange();
            
            // swap class names
            this.toggle.removeClassName('more').addClassName('fewer');
            
            // swap copy
            this.toggleLink.update(this.toggleFewerText);
        }
        else
        {
            // show max number of attributes
            this.hideAttributesOutsideVisibleRange();
            
            // swap class names
            this.toggle.removeClassName('fewer').addClassName('more');
            
            // swap copy
            this.toggleLink.update(this.toggleMoreText);
        }
    },
    
    showAttributesOutsideVisibleRange: function()
    {
        this.attributesOutsideOfVisibleRange.invoke('show');
    },
    
    hideAttributesOutsideVisibleRange: function()
    {
        this.attributesOutsideOfVisibleRange.invoke('hide');
    },
    
    // Public Methods
    isValidAttribute: function(attributeID)
    {
        return (this.filterAttributeCache[attributeID]) ? true : false;
    },
    
    getAttributeValue: function(attributeID)
    {
        return this.filterAttributeCache[attributeID].getValue();
    },
    
    getAttributeTitle: function(attributeID)
    {
        return this.filterAttributeCache[attributeID].getTitle();
    }
});

/**
 *  Filter Attribute
 *  Subscriptions:
 *      "search:succeeded" - build UI
 */
SunGard.Search.FilterGroup.FilterAttribute = Class.create({
    // Constructor
    initialize: function(filterGroup, filterGroupName, filterGroupUrlFriendlyName, data)
    {
        this.filterGroup = filterGroup;
        this.filterGroupName = filterGroupName;
        this.filterGroupUrlFriendlyName = filterGroupUrlFriendlyName;
        this.data = data;
        
        // object data
        this.Title = this.data.Title;
        this.ID = this.data.ID;
        this.Value = (this.data.StartDate && this.data.EndDate) ? {'StartDate':this.data.StartDate, 'EndDate':this.data.EndDate} : this.ID; // Date attribute unique b/c StartDate and EndDate
        this.Quantity = 0; // Quantity is set after the initial load
        
        // set up methods
        this.generateElementCache();
        this.attachEvents();
    },
    
    // Private Methods
    generateElementCache: function()
    {
        // cache elements and container
        this.link = new Element('a', {href: '#/' + this.filterGroupUrlFriendlyName + '=' + this.ID + '/', id: this.ID}).update(this.Title);
        this.matches = new Element('em');
        this.container = new Element('li').insert(this.link).insert(this.matches);
    },
    
    attachEvents: function()
    {
        // attach "subscription" events
        document.observe('search:succeeded', this.buildUI.bind(this));
    },
    
    toElement: function()
    {
        return this.container;
    },
    
    buildUI: function(event)
    {
        this.Quantity = event.memo.data.Filters[this.filterGroupName][this.ID];
        this.matches.update(' (' + this.Quantity + ')');
    },
    
    // Public Methods
    show: function()
    {
        this.container.show();
    },
    
    hide: function()
    {
        this.container.hide();
    },
    
    getValue: function()
    {
        return this.Value;
    },
    
    getTitle: function()
    {
        return this.Title;
    }
});

/**
 *  Setup Search
 *  Engines:
 *      Events
 *      Press Releases
 *      Resources (Brochures & Datasheets, Demos & Tours, Case Studies, Whitepapers & Articles, Webcasts & Webinars, Newsletters)
 *      Sitewide
 */
document.observe('dom:loaded', function(){   
    var filterContainer = $('filters');
    // make sure on correct page before running
    if (filterContainer == null) return;
    
    // load correct search engine based off class name
    if (filterContainer.hasClassName('events')) new es();
    if (filterContainer.hasClassName('press-releases')) new prs();
    if (filterContainer.hasClassName('resources')) new rs();
    if (filterContainer.hasClassName('sitewide')) new sw();
});