var c = window.console;
/**
 * @author pete
 * Code for the widgets/forms that send tweets to twitter.  Takes the
 * id of the particular widget, the auth state of the user, the default
 * text of the tweet, and the url on tweetswirl that prcoesses
 * the request.  Most of the functions are private, or static if necessary
 * to reduce weight and issues with other stuff.  The goal is to be
 * very self contained so it can work or fail "atomically".
 */
function TweetWidget(conf){
    
    this.setMessageValue = function(val) {
        this.textArea.value = val + " ";
        this.textArea.focus();  
    };
    
    this.retweet = function(id) {
        if (!this.auth) {
            //create the buttons
            var buttons = '<div class="buttons">';
            buttons += '<a href="javascript:;" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');">Cancel</a> | ';
            buttons += '<input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');window.location.href=\'/twitteroauth/signin?after='+conf['after']+'\'">';
            buttons += '</div>';
            //append them to the message
            var content = {
                'title':'Login to Your twitter Account',
                'content': this.messages['requirelogin'] + buttons
            };
            //create the modal
            TweetSwirl.updateModal(this.modal_id, content);
            this.textArea.blur();
        } else {
            var request = new Request({
               url: this.retweet_url+id,
               data: '',
               method: 'get',
               onSuccess: (processSuccess).bind(this),
               onError: (processError).bind(this)
            });
            request.send();
        }
        return false;
    };
    
    var attachEvents = function(){
        //if the character count element is actually displayed
        if($('character_count')) {
            //add a key down event handler to update the count accordingly
            this.textArea.addEvents({
                'keypress': imposeMaxLength.bindWithEvent(this),
                'keyup': resetTweetCount.bind(this),
                'blur': resetTweetCount.bind(this),
                'click': resetTweetCount.bind(this),
                'focus': (function(event) {
                    if (!this.auth) {
                        //create the buttons
                        var buttons = '<div class="buttons">';
                        buttons += '<a href="javascript:;" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');">Cancel</a> | ';
                        buttons += '<input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');window.location.href=\'/twitteroauth/signin?after='+conf['after']+'\'">';
                        buttons += '</div>';
                        //append them to the message
                        var content = {
                            'title':'Login to Your twitter Account',
                            'content': this.messages['requirelogin'] + buttons
                        };
                        //create the modal
                        TweetSwirl.updateModal(this.modal_id, content);
                        this.textArea.blur();
                    }
                }).bind(this)
            });
        }
        
        this.sendButton.addEvents({
            'click': (function(event){
                event.stop();
                //button click --> login
                if (!this.auth) {
                    //create the buttons
                    var buttons = '<div class="buttons">';
                    buttons += '<a href="javascript:;" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');">Cancel</a> | ';
                    buttons += '<input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');window.location.href=\'/twitteroauth/signin?after='+conf['after']+'\'">';
                    buttons += '</div>';
                    //append them to the message
                    var content = {
                        'title':'Login to Your twitter Account',
                        'content': this.messages['requirelogin'] + buttons
                    };
                    //create the modal
                    TweetSwirl.updateModal(this.modal_id, content);
                    this.textArea.blur();
                } else {
                    //button click --> validate\post
                    var msg = this.textArea.value;
                    if (msg != '' && msg.match('([0-9a-zA-Z])+') && validateTweet.call(this, msg)) {
                        sendTweet.call(this, encodeURIComponent(msg));
                    }
                }
            }).bind(this)
        });
    };
    
    //function taken from http://blog.listcentral.me/2009/10/05/enforcing-max-length-on-a-textarea/
    var imposeMaxLength = function (event) {
        var whitelist = ['backspace', 'up', 'down', 'left', 'right', 'delete'];
        if (whitelist.contains(event.key)) {
            return true;
        } else {
            return (this.textArea.value.length < this.maxLength);
        }
    };

    
    /**
     * Send a tweet message by calling the server and using the Twitter Api.
     * @param {String} msg - the actual tweet message to send
     */
    var sendTweet = function(msg){
        var request = new Request({
           url: this.url,
           data: 'msg='+msg,
           method: 'get',
           onSuccess: (processSuccess).bind(this),
           onError: (processError).bind(this)
        });
        request.send();
        
    };
    
    var processSuccess = function(responseText) {
        //update the modal
        var button = '<div class="buttons"><input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');"></div>';
        
        var content = {
            'title':'',
            'content':responseText + button
        };
        TweetSwirl.updateModal(this.modal_id, content);
        resetTweetForm.call(this);
    };
    
    var processError = function(responseText) {
        //update the modal
        var button = '<div class="buttons"><input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');"></div>';
        var content = {
            'title':'',
            'content':responseText + button
        };
        TweetSwirl.updateModal(this.modal_id, content);
        resetTweetForm.call(this);
    };
    
    var resetTweetForm = function() {
        //reset the message and character count
        this.textArea.value = this.default_message;
        resetTweetCount.call(this);
    };
    
    var resetTweetCount = function() {
        $('character_count').innerHTML = (140 - this.textArea.value.length) + ' ' + this.countMessage;
    };
    
    /**
     * Validate the tweet message on the client side, make sure it can be
     * encoded and that the encoded version is less than 140 characters.
     * @param {String} msg
     */
    var validateTweet = function(msg){
        //check that the msg can be encoded.
        var orig_msg = msg;
        var valid = true;
        var modal_content = '';
        var button = '<div class="buttons"><input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');"></div>';
        try {
            msg = encodeURIComponent(orig_msg);//Encoder.htmlEncode(msg);
        } catch(e) {
            //show the modal
            modal_content = this.messages['tweetproblem'];
            modal_content += button;
            valid = false;    
        }
        //check if the msg is <= 140 characters encoded
        if(orig_msg.length > this.maxLength) {
            modal_content = this.messages['tweettoolong'];
            modal_content += button;
            valid = false;
        }
        
        //show a modal is something went wrong
        if (modal_content != '') {
            var content = {
                'title': '',
                'content': modal_content
            };
            TweetSwirl.updateModal(this.modal_id, content);
        }
        return valid;
    
    };
    
    (function(){
        if ($(conf['id'])) {
            this.auth = !!conf['auth'];
            
            var cont = this.cont = $(conf['id']);
            
            this.maxLength = 140;
            this.countMessage = conf['countMessage'] || '';
            this.url = conf['url'];
            this.retweet_url = conf['retweet_url'];
            this.textArea = cont.getElement('#tweetMessage');
            this.sendButton = cont.getElement('button');
            this.default_message = conf['default_message'];
            
            
            //get messages
            this.messages = conf['messages'];
            // Create modal
            this.modal_id = TweetSwirl.createModal('','modal_hide');
            attachEvents.call(this);
            resetTweetCount.call(this);
        }
        else {
            return null;
        }
    }).call(this);
};



/**
 * @author pete
 * Code for the top requested widget.  This is very similar to the
 * tweet widget, but for now its just copy and paste.  This does the same
 * sort of checking for logged in, but otherwise it just sends a hardcoded
 * tweet.
 */
function TopRequestedWidget(conf){

    var attachEvents = function(thumblink){
        thumblink.addEvents({
            'click': (function(event){
                event.stop();
                //button click --> login
                if (!this.auth) {
                    //create the buttons
                    var buttons = '<div class="buttons">';
                    buttons += '<a href="javascript:;" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');">Cancel</a> | ';
                    buttons += '<input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');window.location.href=\'/twitteroauth/signin?after='+conf['after']+'\'">';
                    buttons += '</div>';
                    //append them to the message
                    var content = {
                        'title': 'Login to your Twitter Account',
                        'content': this.messages['requirelogin'] + buttons
                    };
                    //create the modal
                    TweetSwirl.updateModal(this.modal_id, content);
                } else {
                    //button click --> validate\post
                    var href = thumblink.href;
                    var msg = href.substring(href.indexOf('@tweetswirl'), href.indexOf('&after'));
                    var content = {
                        'title': 'Login to your Twitter Account',
                        'content': this.messages['tweetsending']
                    };
                    TweetSwirl.updateModal(this.modal_id, content);
                    sendTweet.call(this, msg);
                }
            }).bind(this)
        });
    };
    
    /**
     * Send a tweet message by calling the server and using the Twitter Api.
     * @param {String} msg - the actual tweet message to send
     */
    var sendTweet = function(msg){
        var request = new Request({
           url: this.url,
           data: 'msg='+msg,
           method: 'get',
           onSuccess: (processSuccess).bind(this),
           onError: (processError).bind(this)
        });
        request.send();
    };
    
    var processSuccess = function(responseText) {
        //update the modal
        var button = '<div class="buttons"><input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');"></div>';
        var content = {
            'title': 'Success',
            'content': responseText + button
        };
        TweetSwirl.updateModal(this.modal_id, content);
    };
    
    var processError = function(responseText) {
        //update the modal
        var button = '<div class="buttons"><input type="image" src="../images/common/btnOk.gif" onclick="TweetSwirl.hideModal(\''+this.modal_id+'\');"></div>';
        var content = {
            'title': 'Error',
            'content': responseText + button
        };
    };
    
    (function(){
        if ($(conf['id'])) {
            this.auth = !!conf['auth'];
            
            var cont = this.cont = $(conf['id']);
            this.url = conf['url'];
            this.default_message = conf['default_message'];
            
            //get messages
            this.messages = conf['messages'];
            // Create modal
            this.modal_id = TweetSwirl.createModal('','modal_hide');
            
            //get the links and attach the events
            var thumbs = cont.getElements('.showbutton');
            thumbs.each(attachEvents, this);
        }
        else {
            return null;
        }
    }).call(this);
};

/**
 * @author roel
 * Code for widget that will filter/search for feeds
 */
function FilterWidget(conf) {
    
    /**
     * Setup widget by creating class variables and adding events to DOM elements
     */
    var setupWidget = function() {
        this.scrollLengthFull = 0;
        this.scrollPartial = 0;
        this.scrollMaxPosition = 0;
        
        this.selectedImageCurrent = null;
        this.selectedImageNext = null;
        this.selectedImageOffset = 4;
        
        this.AJAXRefreshInterval = null;
        this.FilmstripAutoplayInterval = null;
        
        setupElements.call(this);
        checkURLFilter.call(this)
        filmstripCalculations.call(this);
        attachEvents.call(this);
        updateArrows.call(this);  
    };
    
    /**
     * Retreive elements in widghet
     */
    var setupElements = function() { 
        this.feeds = conf['id'];
        this.url = conf['url'];
        this.participant_name = conf['participant_name'];
        this.page_name = conf['page_name'];
        
        this.element = {
            'hub_left': $('hubLeft'),
            'search_filters': $$('#feeds .header .search_filters')[0],
            'filmstrip': $$('#feeds .participant_filters .filmstrip')[0],
            'filmstrip_viewport': $$('#feeds .participant_filters .filmstrip .viewport')[0],
            'filmstrip_gallery': $$('#feeds .participant_filters .filmstrip .viewport .gallery')[0],
            'filmstrip_thumbnails': $$('#feeds .participant_filters .filmstrip .viewport .gallery .thumb'),  
            'filmstrip_arrow_left': $$('#feeds .participant_filters .filmstrip .arrow_left')[0],
            'filmstrip_arrow_right': $$('#feeds .participant_filters .filmstrip .arrow_right')[0],
            'clear_selection_link': $$('#feeds .participant_filters .link_clear_selection')[0],
            'results': $$('#feeds .results')[0],
            'feedback': $$('#feeds .results .feedback')[0],
            'feedback_header': $$('#feeds .results .feedback h2')[0],
            'feedback_throbber': $$('#feeds .results .feedback .throbber')[0],
            'tweets': $$('#feeds .results .tweets'),
            'more_results_link': $$('#feeds .results .pagination .link_more_results')[0]  
        };
        
        this.galleryTween = new Fx.Tween(this.element['filmstrip_gallery'], {
           'onComplete':updateArrows.bind(this) 
        });
    };
    
    /**
     * Check if URL contains participant name (highlight thumbnail on page load)
     */
    var checkURLFilter = function() {
        if (this.participant_name) {
            var thumbnails = this.element['filmstrip_thumbnails'];
            var numThumbs = thumbnails.length;
            for (var i=0; i < numThumbs; i++) {
                var image = thumbnails[i];
                if (image.alt == this.participant_name) {
                    this.selectedImageCurrent = image;
                    break;
                };
            };
        };
    };
    
    /**
     * Conduct calculations for filmstrip
     */
    var filmstripCalculations = function() {
        var viewport = this.element['filmstrip_viewport'];
        var gallery = this.element['filmstrip_gallery'];
        var thumbnails = this.element['filmstrip_thumbnails'];
        
        if (thumbnails.length != 0) {
            var image = thumbnails[0];
            var displacement = parseInt(image.getStyle('border-left-width')) +
            parseInt(image.getStyle('width')) +
            parseInt(image.getStyle('border-right-width')) +
            parseInt(image.getStyle('margin-right')) +
            parseInt(image.getStyle('padding-right')) +
            parseInt(image.getStyle('padding-left'));
            
            calculateGalleryWidth.apply(this, [displacement, viewport, gallery, thumbnails]);
            calculateScrollLength.apply(this, [displacement, viewport, gallery, thumbnails]);
        }
    };
    
    /**
     * Calculate and set the width of the gallery
     */
    var calculateGalleryWidth = function(displacement, viewport, gallery, thumbnails) {
        gallery.setStyle('width',(thumbnails.length*displacement) + 'px');
    };
    
    /**
     * Calculate the amount of pixels the filmstrip should scroll by
     */
    var calculateScrollLength = function(displacement, viewport, gallery, thumbnails) {
        var image = thumbnails[0];
        var numImages = thumbnails.length;
        var numDisplacedImages = Math.floor((viewport.offsetWidth + parseInt(image.getStyle('margin-right')))/displacement);
        
        this.scrollLengthFull =  numDisplacedImages*displacement;
        this.scrollPartial = ((numImages%numDisplacedImages)*displacement - parseInt(image.getStyle('margin-right'))) - (viewport.offsetWidth - this.scrollLengthFull);
        this.scrollMaxPosition = ((Math.floor(numImages/numDisplacedImages)-1)*this.scrollLengthFull) + this.scrollPartial;
        
        if (parseInt(gallery.getStyle('width')) <= this.scrollLengthFull) {
            this.element['filmstrip_arrow_left'].setStyle('visibility','hidden');
            this.element['filmstrip_arrow_right'].setStyle('visibility','hidden');
        };
        
        if (this.scrollMaxPosition < 0) {
            this.scrollMaxPosition  = 0;
        };
    };
    
    /**
     * Attach events to components in filter template
     */
    var attachEvents = function() {
        addEvent_SearchFilters.call(this);
        addEvent_GalleryImages.call(this);
        addEvent_MoveGalleryLeft.call(this);
        addEvent_MoveGalleryRight.call(this);
        addEvent_MoreResultsLink.call(this);
        addEvent_ClearSelectionLink.call(this);
        addEvent_AJAXRefresh.call(this);
        addEvent_FilmstripAutoplay.call(this);
    };
    
    /**
     * Add event to filter feeds by search parameter (e.g. About, From)
     */
    var addEvent_SearchFilters = function() {
        var searchFilters = this.element['search_filters']; 
        
        searchFilters.getChildren('a').each(function(link){
            link.addEvent('click', function(event){
                event.stop();
                clearInterval(this.AJAXRefreshInterval);
                clearInterval(this.FilmstripAutoplayInterval);
                
                new Request.HTML({
                    url: link.href,
                    data: 'only_feeds=true',
                    method: 'get',
                    onSuccess: (processSuccess_SearchFilters).bind(this),
                    onError: (processError).bind(this)
                }).send();
            }.bind(this));
        }.bind(this));
    };
    
    /**
     * Add events to filter by participant and add hover states to thumbnails
     */
    var addEvent_GalleryImages = function() {
        var thumbnails = this.element['filmstrip_thumbnails'];
        var clearSelectionLink = this.element['clear_selection_link'];
        
        thumbnails.each(function(image){
            image.addEvent('click', function(event){
                event.stop();
                clearInterval(this.AJAXRefreshInterval);
                clearInterval(this.FilmstripAutoplayInterval);
                
                var requestURL = '';
                var filterText = '';
                var searchFilterText = '';
                
                if (this.selectedImageCurrent == image) {
                    this.selectedImageCurrent.className = 'thumb';
                    this.selectedImageCurrent = null;
                    this.selectedImageNext = null;
                    
                    clearSelectionLink.className = 'link_clear_selection hide';
                    requestURL = clearSelectionLink.href;
                    filterText = this.page_name;
                } 
                else {
                    if (!this.selectedImageCurrent) {
                        this.selectedImageCurrent = image;
                        this.selectedImageCurrent.className = 'thumb selected';
                    } 
                    else {
                        this.selectedImageNext = image;
                        this.selectedImageNext.className = 'thumb selected';
                        this.selectedImageCurrent.className = 'thumb';
                        this.selectedImageCurrent = this.selectedImageNext;
                    };
                    
                    clearSelectionLink.className = 'link_clear_selection show';
                    requestURL = image.getParent().href;
                    filterText = decodeURIComponent(requestURL.substring(requestURL.indexOf('filter=')+7,requestURL.indexOf('&')));
                    searchFilterText = decodeURIComponent(requestURL.substring(requestURL.indexOf('search_filter=')+14,requestURL.length));
                    //filterText = this.participant_name
                };
                
                toggleThrobber.call(this, filterText, searchFilterText);
                
                var request = new Request.HTML({
                    url: requestURL,
                    data: 'only_results=true',
                    method: 'get',
                    onSuccess: processSuccess_GalleryImage.bind(this),
                    onFailure: processError.bind(this),
                    onComplete: toggleThrobber.bind(this, filterText, searchFilterText)
                });
                
                (function(){
                    request.send();                    
                }).delay(1550, this); //artificial delay so the throbber shows (should really only show after some time)
            }.bind(this));
            
            image.addEvent('mouseover', function(event){
                event.stop();
                if (this.selectedImageCurrent != image) {
                    image.className = 'thumb hover';
                }
            }.bind(this));
            
            image.addEvent('mouseout', function(event){
                event.stop();
                if (this.selectedImageCurrent != image) {
                    image.className = 'thumb';
                }
            }.bind(this));
        }.bind(this));
    };
    
    /**
     * Move gallery to the left
     */
    var addEvent_MoveGalleryLeft = function() {
        var gallery = this.element['filmstrip_gallery'];
        var rightArrow = this.element['filmstrip_arrow_right'];
        
        rightArrow.addEvent('mousedown', function(event) {
            if (rightArrow.active) {
                rightArrow.addClass('active');
            }
        });
        rightArrow.addEvent('mouseup', function(event) {
            rightArrow.removeClass('active');
        });
        rightArrow.addEvent('click', function(event) {
            event.stop();
            clearInterval(this.FilmstripAutoplayInterval);
            
            var currentPosition = parseInt(gallery.getStyle('left'));
            var newPosition = currentPosition - this.scrollLengthFull;
            
            if (this.scrollMaxPosition != 0) {
                if ((newPosition <= -this.scrollMaxPosition) && (currentPosition != -this.scrollMaxPosition)) {
                    this.galleryTween.start('left', -this.scrollMaxPosition);
                }                
                else if (newPosition > -this.scrollMaxPosition) {
                    this.galleryTween.start('left', newPosition);
                }
                
            };
        }.bind(this));
    };
    
    /**
     * Move gallery to the right
     */
    var addEvent_MoveGalleryRight = function() {
        var gallery = this.element['filmstrip_gallery'];
        var leftArrow = this.element['filmstrip_arrow_left'];
        
        leftArrow.addEvent('mousedown', function(event) {
            if (leftArrow.active) {
                leftArrow.addClass('active');
            }
        });
        leftArrow.addEvent('mouseup', function(event) {
            leftArrow.removeClass('active');
        });
        leftArrow.addEvent('click', function(event) {
            event.stop();
            clearInterval(this.FilmstripAutoplayInterval);
            
            var currentPosition = parseInt(gallery.getStyle('left'));
            var newPosition = currentPosition + this.scrollLengthFull;
            
            if (this.scrollMaxPosition != 0) {
                if ((newPosition >= 0) && (currentPosition != 0)) {
                    this.galleryTween.start('left', 0);
                }
                else if (newPosition < 0) {
                    this.galleryTween.start('left', newPosition);
                }
            }
        }.bind(this));
    };
    
    /**
     * Show more results for current filter
     */
    var addEvent_MoreResultsLink = function() {
        var lastTweetSet = this.element['tweets'][this.element['tweets'].length-1];
        var tweetListings = lastTweetSet.getChildren('li');
        var moreResultsLink = this.element['more_results_link'];
        
        if (moreResultsLink) {
            moreResultsLink.addEvent('click', function(event){
                event.stop();
                var lastTweet = tweetListings[tweetListings.length-1];
                if (lastTweet.id) {
                    new Request.HTML({
                        url: moreResultsLink.href,
                        data: 'only_results=true&last_id=' + lastTweet.id,
                        method: 'get',
                        onSuccess: (processSuccess_MoreResultsLink).bind(this),
                        onError: (processError).bind(this)
                    }).send(); 
                }
            }.bind(this));
        };
    };
    
    /**
     * Remove filters and show default feed
     */
    var addEvent_ClearSelectionLink = function() {
        var clearSelectionLink = this.element['clear_selection_link'];
        
        clearSelectionLink.addEvent('click', function(event){
            event.stop();
            clearInterval(this.AJAXRefreshInterval);
            clearInterval(this.FilmstripAutoplayInterval);
            
            this.selectedImageCurrent.className = 'thumb';
            this.selectedImageCurrent = null;
            this.selectedImageNext = null;
            
            clearSelectionLink.className = 'link_clear_selection hide';
            
            var clearSelectionLinkHREF = clearSelectionLink.href;
            var searchFilterText = decodeURIComponent(clearSelectionLinkHREF.substring(clearSelectionLinkHREF.indexOf('search_filter=')+14,clearSelectionLinkHREF.length));
            
            toggleThrobber.call(this, this.page_name, searchFilterText);
            
            var request = new Request.HTML({
                url: clearSelectionLink.href,
                data: 'only_results=true',
                method: 'get',
                onSuccess: (processSuccess_ClearSelectionLink).bind(this),
                onError: (processError).bind(this),
                onComplete: toggleThrobber.bind(this, this.page_name, searchFilterText)
            });
            (function(){
                request.send();                    
            }).delay(1550, this); //artificial delay so the throbber shows (should really only show after some time)
        }.bind(this));
    };
    
    /**
     * Add in new feeds via AJAX refresh
     */
    var addEvent_AJAXRefresh = function() {
        
        if (this.AJAXRefreshInterval) {
            clearInterval(this.AJAXRefreshInterval);
        };
        
        var clearSelectionLink = this.element['clear_selection_link'];
        var firstTweetSet = this.element['tweets'][0];
        var tweetListings = firstTweetSet.getChildren('li');
        var interval = 30*1000;
        
        this.AJAXRefreshInterval = setInterval(function() {
            var firstTweet = tweetListings[0];
            if (firstTweet.id) {
                var requestURL = clearSelectionLink.href;
                if (this.selectedImageCurrent) {
                    requestURL = this.selectedImageCurrent.getParent().href;
                }
                new Request.HTML({
                    url: requestURL,
                    data: 'only_results=true&page=-1&first_id=' + firstTweet.id,
                    method: 'get',
                    onSuccess: (processSuccess_AJAXRefresh).bind(this),
                    onFailure: (processError).bind(this)
                }).send(); 
            }
        }.bind(this),interval)
    };
    
    /**
     * Run filmstrip in autoplay mode
     */
    var addEvent_FilmstripAutoplay = function() {
        
        if (this.FilmstripAutoplayInterval) {
            clearInterval(this.FilmstripAutoplayInterval);
        };
        
        var interval = 5*1000;
        var moveDirection = 'left';
        
        this.FilmstripAutoplayInterval = setInterval(function() {
            var gallery = this.element['filmstrip_gallery'];
            var currentPosition = parseInt(gallery.getStyle('left'));
            
            if (currentPosition == 0) {
                moveDirection = 'left';
            }
            else if (currentPosition == -this.scrollMaxPosition) {
                moveDirection = 'right';
            }
            
            if (moveDirection == 'left') {
                var newPosition = currentPosition - this.scrollLengthFull;
                
                if (this.scrollMaxPosition != 0) {
                    if ((newPosition <= -this.scrollMaxPosition) && (currentPosition != -this.scrollMaxPosition)) {
                        this.galleryTween.start('left', -this.scrollMaxPosition);
                    }                
                    else if (newPosition > -this.scrollMaxPosition) {
                        this.galleryTween.start('left', newPosition);
                    }
                    
                };
            } 
            else if (moveDirection == 'right') {
                var newPosition = currentPosition + this.scrollLengthFull;
                
                if (this.scrollMaxPosition != 0) {
                    if ((newPosition >= 0) && (currentPosition != 0)) {
                        this.galleryTween.start('left', 0);
                    }
                    else if (newPosition < 0) {
                        this.galleryTween.start('left', newPosition);
                    }
                };
            };

        }.bind(this),interval);
    };
    
    /**
     * Update the arrows to be active or depending on the position of the filmstrip
     */
    var updateArrows = function() {
        var viewport = this.element['filmstrip_viewport'];
        var gallery = this.element['filmstrip_gallery'];
        var leftArrow = this.element['filmstrip_arrow_left'];
        var rightArrow = this.element['filmstrip_arrow_right'];
        
        //if there is more to go make this active, otherwise disable it
        var currentPosition = Math.abs(parseInt(gallery.getStyle('left')));
        var max = this.scrollMaxPosition;
        
        if (gallery.offsetWidth <= viewport.offsetWidth) {
            leftArrow.active = false;
            leftArrow.removeClass('more_participants');
            
            rightArrow.active = false;
            rightArrow.removeClass('more_participants');
        } 
        else if (currentPosition == 0) {
            leftArrow.active = false;
            leftArrow.removeClass('more_participants');
            
            //all the way left, but see if there is more to the right
            if (currentPosition < max) {
                rightArrow.active = true;
                rightArrow.addClass('more_participants');
            }
        } 
        else if (currentPosition == max) {
            leftArrow.active = true;
            leftArrow.addClass('more_participants');
            
            rightArrow.active = false;
            rightArrow.removeClass('more_participants');
        } 
        else {
            rightArrow.active = true;
            rightArrow.addClass('more_participants');
            
            leftArrow.active = true;
            leftArrow.addClass('more_participants');            
        };
    };
    
    /**
     * Toggle throbber when loading in feeds
     */
    var toggleThrobber = function(filterText, searchFilterText){
        var feedbackHeader = this.element['feedback_header'];
        var feedbackThrobber = this.element['feedback_throbber'];
        var proposition = '';
        
        if (searchFilterText == 'about') {
            proposition = 'on ';
        }
        else if (searchFilterText == 'from') {
            proposition = 'from';
        }
        
        feedbackHeader.innerHTML = "Twitter Buzz " + proposition + " " +filterText.replace('+',' ','g');
        
        if (feedbackThrobber.hasClass('hide')) {
            feedbackThrobber.className = 'throbber';
        }
        else {
            feedbackThrobber.className = 'throbber hide';
        }
    };

   /**
    * Callback function - Search filters
    */
    var processSuccess_SearchFilters = function(responseTree, responseElements, responseHTML, responseJavaScript) {
        this.element['hub_left'].innerHTML = responseHTML;
        setupWidget.call(this);
    };
    
   /**
    * Callback function - Gallery images
    */
    var processSuccess_GalleryImage = function(responseTree, responseElements, responseHTML, responseJavaScript) {
        this.element['results'].innerHTML = responseHTML;
        setupElements.call(this);
        addEvent_MoreResultsLink.call(this);
        addEvent_AJAXRefresh.call(this);
    };
    
   /**
    * Callback function - 'Clear Selection' link
    */
    var processSuccess_ClearSelectionLink = function(responseTree, responseElements, responseHTML, responseJavaScript) {
        this.element['results'].innerHTML = responseHTML;
        setupElements.call(this);
        addEvent_MoreResultsLink.call(this);
        addEvent_AJAXRefresh.call(this);
    };
    
   /**
    * Callback function - 'More' link
    */
    var processSuccess_MoreResultsLink = function(responseTree, responseElements, responseHTML, responseJavaScript) {
        var results = this.element['results'];
        var moreResultsLink = this.element['more_results_link'];
        
        // Remove old 'More' link
        var oldMoreLinkContainer = moreResultsLink.getParent();
        oldMoreLinkContainer.getParent().removeChild(oldMoreLinkContainer);
        
        // Create HTML elements from raw HTML returned from AJAX request
        var el = new Element('div');
        el.innerHTML = responseHTML;
        
        // Add search results to page (will always return at least one element (e.g. no more feeds message))
        var newTweets = el.getElement('ul.tweets');
        if (newTweets) {
            var newListings = newTweets.getElements('li');
            var numListings = newListings.length;
            if (numListings > 0) {
                newTweets.inject(results);
            }
        };
        
        // Add add 'More' link to page
        var newMoreLinkContainer = el.getElement('div.pagination');
        if (newMoreLinkContainer) {
            newMoreLinkContainer.inject(results);
        };
        
        // Update click event for 'More' link to reflect new page number
        setupElements.call(this);
        addEvent_MoreResultsLink.call(this);
    };
    
   /**
    * Callback function - AJAX refresh
    */
    var processSuccess_AJAXRefresh = function(responseTree, responseElements, responseHTML, responseJavaScript){
        var feedback = this.element['feedback'];
        
        // Create HTML elements from raw HTML returned from AJAX request
        var el = new Element('div');
        el.innerHTML = responseHTML;
        
        // Add search results to page
        var newTweets = el.getElement('ul.tweets');
        if (newTweets) {
            var newListings = newTweets.getElements('li');
            var numListings = newListings.length;
            if (numListings > 0) {
                newTweets.inject(feedback, 'after');
                var startColor = '#FFFACD';
                var endColor = '#FFFFFF';
                var time = 2500;
                newListings.each(function(listing) {
                    listing.setStyle('background-color',startColor);
                    listing.highlight(startColor, endColor, [{duration: time}]);
                    listing.getElement('a.reply').tween('background-color', startColor, endColor, [{duration: time}]);
                    listing.getElement('a.retweet').tween('background-color', startColor, endColor, [{duration: time}]);
                });
            }
        };
    };
       /**
    * Error callback functions when requesting feeds via AJAX
    */
    var processError = function(responseText) {
        //update the modal
    };
    
    (function(){
        if ($(conf['id'])) {
            this.conf = conf;
            setupWidget.call(this);          
        }
        else {
            return null;
        }
    }).call(this);
};

/**
 * @author roel
 * Code for filmstrip widget
 */
function FilmstripWidget(conf) {
    
    /**
     * Conduct calculations for filmstrip
     */
    var filmstripCalculations = function() {
        var frame = this.frames[0];
        var displacement = frame.offsetWidth + parseInt(frame.getStyle('margin-right'));
        calculateGalleryWidth.apply(this,[frame,displacement]);
        calculateScrollLength.apply(this,[frame,displacement]);
    };
    
    /**
     * Calculate and set the width of the gallery
     */
    var calculateGalleryWidth = function(frame,displacement) {
        this.gallery.setStyle('width',(this.frames.length*displacement) - parseInt(frame.getStyle('margin-right')) + 'px');
    };
    
    /**
     * Calculate the amount of pixels the filmstrip should scroll by
     */
    var calculateScrollLength = function(frame,displacement) {
        var numFrames = this.frames.length;
        var numDisplacedFrames = Math.floor((this.viewport.offsetWidth + parseInt(frame.getStyle('margin-right')))/displacement);
        this.scrollLengthFull =  numDisplacedFrames*displacement;
        this.scrollLengthPartial = ((numFrames%numDisplacedFrames) * displacement - parseInt(frame.getStyle('margin-right'))) - (this.viewport.offsetWidth - this.scrollLengthFull);
        this.scrollMaxPosition = ((Math.floor(numFrames/numDisplacedFrames)-1)*this.scrollLengthFull) + this.scrollLengthPartial;
        
        if (this.scrollMaxPosition < 0) {
            this.scrollMaxPosition = 0;
        }
    };
    
    /**
     * Attach events to filmstrip
     */
    var attachEvents = function() {
        addEvent_MoveGalleryLeft.call(this);
        addEvent_MoveGalleryRight.call(this);
    };
    
    var addEvent_MoveGalleryLeft = function() {
        this.arrowRight.addEvent('mousedown', function(event) {
            if (this.active) {
                $(this).addClass('active');
            }
        });
        this.arrowRight.addEvent('mouseup', function(event) {
            $(this).removeClass('active');
        });
        this.arrowRight.addEvent('click', function(event) {
            event.stop();
            var currentPosition = parseInt(this.gallery.getStyle('left'));
            var newPosition = currentPosition - this.scrollLengthFull;
            
            if (this.scrollMaxPosition != 0) {
                if ((newPosition <= -this.scrollMaxPosition) && (currentPosition != -this.scrollMaxPosition)) {
                    this.galleryTween.start('left', -this.scrollMaxPosition);
                }                
                else if (newPosition > -this.scrollMaxPosition) {
                    this.galleryTween.start('left', newPosition);
                }
                
            }
        }.bind(this));
    };
    
    var addEvent_MoveGalleryRight = function() {
        this.arrowLeft.addEvent('mousedown', function(event) {
            if (this.active) {
                $(this).addClass('active');
            }
        });
        this.arrowLeft.addEvent('mouseup', function(event) {
            $(this).removeClass('active');
        });
        this.arrowLeft.addEvent('click', function(event) {
            event.stop();
            var currentPosition = parseInt(this.gallery.getStyle('left'));
            var newPosition = currentPosition + this.scrollLengthFull;
            
            if (this.scrollMaxPosition != 0) {
                if ((newPosition >= 0) && (currentPosition != 0)) {
                    this.galleryTween.start('left', 0);
                }
                else if (newPosition < 0) {
                    this.galleryTween.start('left', newPosition);
                }
            }
        }.bind(this));
    };
    
    var updateArrows = function() {
        var currentPosition = Math.abs(parseInt(this.gallery.getStyle('left')));
        var max = this.scrollMaxPosition;
        
        right = this.arrowRight;
        left = this.arrowLeft;
        
        if (this.gallery.offsetWidth <= this.viewport.offsetWidth) {
            left.active = false;
            left.removeClass('more_participants');
            
            right.active = false;
            right.removeClass('more_participants');
        }
        else if (currentPosition == 0) {
            left.active = false;
            left.removeClass('more_participants');
            
            right.active = true;
            right.addClass('more_participants');
        } 
        else if (currentPosition == max) {
            left.active = true;
            left.addClass('more_participants');
            
            right.active = false;
            right.removeClass('more_participants');
        } 
        else {
            right.active;
            right.addClass('more_participants');
            
            left.active;
            left.addClass('more_participants');
        }
    };
        
    (function(){
        if ($(conf['id'])) {
            var id = conf['id'];
            
            this.filmstrip = $(id);
            this.arrowLeft = $$('#' + id + ' .arrow_left')[0];
            this.arrowRight = $$('#' + id + ' .arrow_right')[0];
            this.viewport = $$('#' + id + ' .viewport')[0];
            this.gallery = $$('#' + id + ' .gallery')[0];
            this.frames = $$('#' + id + ' .frame');  
            
            this.scrollLengthFull = 0;
            this.scrollLengthPartial = 0;
            this.scrollMaxPosition = 0;  
            
            this.galleryTween = new Fx.Tween(this.gallery, {
               'onComplete':updateArrows.bind(this) 
            });
            
            filmstripCalculations.call(this);    
            attachEvents.call(this); 
            updateArrows.call(this);
        }
        else {
            return null;
        }
    }).call(this);
};

FollowWidget = function(conf) {
    
    var applyFilter = function(filter) {
        updateActiveTab.call(this, filter);
        filterParticipants.call(this, filter);
    };
    
    var filterParticipants = function(filter) {
        var filters = filter.getAttribute('class').trim().split(" ");
        filters.erase('last');
        
        //go through the participants showing those that match
        //the filter and hide those that don't.  A match
        //is determined by the participant having a class that
        //matches one in the current tab
        var last_participant = null;
        this.follow_participants.getElements('li').each(function(p) {
            var p_class = p.getAttribute('class').trim().split(" ");
            if(p_class.intersect(filters).length) {
                p.removeClass('hidden');
                p.removeClass('last');
                last_participant = p;
            } else {
                p.addClass('hidden');  
            }
        });
        //if there were any participants under this filter, make
        //the last one have the "last" css class
        if(last_participant) {
            last_participant.addClass('last');
        }
    };
    
    var filterSubLists = function(filter) {
        var filters = filter.getAttribute('class').trim().split(" ");
        
        //go through the participants showing those that match
        //the filter and hide those that don't.  A match
        //is determined by the participant having a class that
        //matches one in the current tab
        this.sublists.each((function(list) {
            var list_class = list.getAttribute('class').trim().split(" ");
            if(list_class.intersect(filters).length) {
                list.removeClass('hidden');
                //show the participants for the defalt filter of this list
                applyFilter.call(this, list.getElement(".default"));
            } else {
                list.addClass('hidden');
            }
        }).bind(this));
    };
    
    var updateActiveTab = function(filter){
        filter.parentNode.getChildren().each(function(li) {
            if (filter != li) {
                li.removeClass('active');
            } else {
                filter.addClass('active');
            }
        });
    };
    
    var attachSubListEvents = function() {
        this.sublists.each((function(list) {
            list.getElements('li').each((function(tab) {
                tab.addEvent('click', (function(e) {
                    e.stop();
                    applyFilter.call(this, tab);
                }).bind(this));
            }).bind(this));
        }).bind(this));
    };
    
    var attachTabEvents = function() {
        this.tabs.getElements('li').each((function(tab) {
            tab.addEvent('click', function(e) {
                e.stop();
                applyFilter.call(this, tab);
                filterSubLists.call(this, tab);
            }.bindWithEvent(this));
        }).bind(this));
    };
    
    (function() {
        if($(conf['filter_tabs'])) {
            this.tabs = $(conf['filter_tabs']);
            this.follow_participants = $(conf['follow_participants']);
            this.sublists = $$(conf['follow_subfilters']);
            attachTabEvents.call(this);
            attachSubListEvents.call(this);        
        }
        
    }).call(this);
};



/**
 * @author Pete
 * The main tweetswirl object that provides helper functions to the rest
 * of the widgets on the site.
 */
var TweetSwirl = {
    /**
     * Create a modal with some given content.  This should
     * follow some generic modal design/layout, and a className
     * can be passed in to change the display.
     *
     * Returns the dom id of the modal.
     *
     * @param {Object} content - the header and content to be shown
     * @param {String} className - the css class name for the modal
     * @return {int} id - the dom id of the modal
     */
    createModal: function(content, className){
        var id = new Date().getTime().toString();
        var className = className || "modal";
        
        
        //main div
        var modal = new Element('div');
        modal.id = id;
        modal.className = className;
        
        //title
        var title = new Element('div');
        title.className = 'title';
        
        //content
        var content = new Element('div');
        content.className = 'content';
                
        content.title = content.title || '';
        content.content = content.content || '';
        
        title.innerHTML = content.title;
        content.innerHTML = content.content;
        
        modal.appendChild(title);
        modal.appendChild(content);
        
        //add it to the body
        modal.injectInside($$('body')[0]);
        
        var modalMask = new Element('div');
        modalMask.id = id + 'mask';
        modalMask.className = className || "modalmask"
        modalMask.injectInside($$('body')[0]);
        
        return id;
    },
    
    
    /**
     * Delete a modal from the dom.
     * @param {Object} id
     */
    deleteModal: function(id){
        $(id).destroy();
        $(id + 'mask').destroy();
    },
    
    /**
     * Instead of deleting and recreating modals a given modal
     * can have its content swapped out.
     * @param {Int} id - the id of the modal ot modify
     * @param {String} content - the new content
     * @param {String} className - the name of the class
     */
    resetModal: function(id, content, className) {
        var className = className || 'modal';
        $(id).className = className;
        
        TweetSwirl.setModalContent(id, content);
    },
    
    /**
     * Set the title and main content for the modal.
     * Can take either a string that's assumed to be used for
     * innerHTML or an element, which is assume to be the new
     * content.  Either way a title can be passed in.
     * @param {Object} id
     * @param {Object} content
     */
    setModalContent: function(id, content) {
        content.title = content.title || '';
        content.content = content.content || '';
        
        $(id).getElements('.title')[0].innerHTML = content.title;
        if ($type(content.content) == 'element') {
            $(id).getElements('.content')[0].empty().appendChild(content.content);
        } else {
            $(id).getElements('.content')[0].innerHTML = content.content;
        }
    },
    
    /**
     * Shows the modal by setting a css class.
     * @param {Int} id - the id of ht emodal to show
     */
    showModal: function(id) {
        $(id).className = 'modal modal_show';
        $(id + 'mask').className = 'modalmask modalmask_show';
    },
    
    /**
     * Hide the modal by setting a css class.
     * @param {Int} id - the id of the modal to hide
     */
    hideModal: function(id) {
        $(id).className = 'modal modal_hide';
        $(id + 'mask').className = 'modalmask modalmask_hide';
    },
    
    /**
     * Hide the modal by setting a css class to be
     * display none.
     * @param {Int} id - the id of the modal to disappear
     */
    disappearModal: function(id) {
        $(id).className = 'modal modal_disappear';
        $(id + 'mask').className = 'modalmask modalmask_dissappear';
    },
    
    /**
     * Show the modal by setting a css class to be
     * display block.
     * @param {Int} id - the id of the modal to appear
     */
    appearModal: function(id) {
        $(id).className = 'modal modal_appear';
        $(id + 'mask').className = 'modalmask modalmask_appear';
    },
    
    /**
     * Position the modal in the middle of the browser window
     * @param {Int} id - the id of the modal to position
     */
    positionModal: function(id){
        var modal = $(id);
        //use mootools dimensions
        var browserDimensions = [window.innerWidth, window.innerHeight];

        modal.style.top = (window.getSize().y/2) - (modal.getSize().y/2) + window.getScroll().y + 'px';
        modal.style.left = (window.getSize().x/2) - (modal.getSize().x/2) + window.getScroll().x + 'px';
    },
    
    /**
     * Reset the modal and show it to user with the new content.
     * @param {Int} id - the id of the modal to update
     * @param {String} content - the new content to display
     */
    updateModal: function(id, content){
        TweetSwirl.hideModal(id);
        TweetSwirl.resetModal(id,content);
        TweetSwirl.appearModal(id);
        TweetSwirl.positionModal(id);
        TweetSwirl.showModal(id);        
    }
};


sfHover = function() {
    var sfEls = document.getElements(".menu_nav").getElements("li");
    for (var i=0; i<sfEls.length; i++) {
        sfEls[i].onmouseover=function() {
            this.className+=" sfhover";
        }
        sfEls[i].onmouseout=function() {
            this.className=this.className.replace(new RegExp(" sfhover\\b"), "");
        }
    }
}
if (window.attachEvent) window.attachEvent("onload", sfHover);





/* here for now*/
/* taken from http://www.esteak.net/plugin/Array_Extras*/
Array.implement({
    
    /**
     * Creates an intersection of the current system and the given one.
     * Returns as new array.
     * @param Array array
     */
    intersect: function(other) {
        var cpy = this.slice();
        this.each(function(el) {
            if (other.indexOf(el) < 0) {            
                cpy.splice(cpy.indexOf(el), 1);
            }
        }, this);
        return cpy;
    },
    
    /**
     * Returns the symmetric difference between this array and the given one.
     * Means the items both arrays include are removed from both and then both are combined.
     */
    differentiate: function(other) {
        var src = this.slice();
        var cmp = other.slice();
        other.each(function(elem) {
            if (src.indexOf(elem) > -1) {
                // remove from both
                src.splice(src.indexOf(elem), 1);
                cmp.splice(cmp.indexOf(elem), 1);
            }
        }, this);
        // combine remaining items
        return src.combine(cmp);
    },
    
    /**
     * Returns the given number of elements from the array starting 
     * at the given index.
     * @param int start index
     * @param int number of elements to return
     */
    getRange: function(start, elements) {
        var res = [];
        var j = 0;
        var upper = start + elements > this.length ? this.length : start + elements;
        if (start >= 0) {
            for (var i = start; i < upper; i++) {
                res[j++] = this[i];
            }
        }
        return res;
    }

});