/* Custom code to combine Twitter feed with a Carousel https://github.com/seaofclouds/tweet/ */



/*
* jQuery Infinite Carousel
* @author admin@catchmyfame.com - http://www.catchmyfame.com
* @version 2.0.2
* @date June 12, 2010
* @category jQuery plugin
* @copyright (c) 2009 admin@catchmyfame.com (www.catchmyfame.com)
* @license CC Attribution-Share Alike 3.0 - http://creativecommons.org/licenses/by-sa/3.0/
*/

(function ($) {
    $.fn.extend({
        infiniteCarousel: function (options) {
            var defaults =
			{
			    transitionSpeed: 800,
			    displayTime: 6000,
			    textholderHeight: .25,
			    displayProgressBar: true,
			    displayThumbnails: true,
			    displayThumbnailNumbers: true,
			    displayThumbnailBackground: true,
			    thumbnailWidth: '20px',
			    thumbnailHeight: '20px',
			    thumbnailFontSize: '.7em',
			    easeLeft: 'linear',
			    easeRight: 'linear',
			    imagePath: '/assets/scripts/images/',
			    inView: 1,
			    padding: '10px',
			    advance: 1,
			    showControls: true,
			    autoHideControls: false,
			    autoHideCaptions: false,
			    autoStart: true,
			    prevNextInternal: true,
			    enableKeyboardNav: true,
			    onSlideStart: function () { },
			    onSlideEnd: function () { },
			    onPauseClick: function () { }
			};
            var options = $.extend(defaults, options);

            return this.each(function () {
                var randID = Math.round(Math.random() * 100000000);
                var o = options;
                var obj = $(this);
                var autopilot = o.autoStart;

                var numImages = $('img', obj).length; // Number of images
                var imgHeight = $('img:first', obj).height();
                var imgWidth = $('img:first', obj).width();

                if (o.inView > numImages - 1) o.inView = numImages - 1; // check to make sure inview isnt greater than the number of images. inview should be at least two less than numimages (otherwise hinting wont work and animating left may catch a flash), but one less can work
                $('p', obj).hide(); // Hide any text paragraphs in the carousel
                $(obj).css({ 'position': 'relative', 'overflow': 'hidden' }).width((imgWidth * o.inView) + (o.inView * parseInt(o.padding) * 2)).height(imgHeight + (parseInt(o.padding) * 2)); //,'overflow':'hidden'
                $('ul', obj).css({ 'list-style': 'none', 'margin': '0', 'padding': '0', 'position': 'relative' }).width(imgWidth * numImages);
                $('li', obj).css({ 'display': 'inline', 'float': 'left', 'padding': o.padding });

                // Move rightmost image over to the left
                $('li:last', obj).prependTo($('ul', obj));
                $('ul', obj).css('left', -imgWidth - (parseInt(o.padding) * 2) + 'px').width(9999);

                // Build progress bar
                if (o.displayProgressBar) {
                    $(obj).append('<div id="progress' + randID + '" class="ic-progress-bar" style="position:absolute;bottom:0;background:#bbb;left:0;z-index:1"></div>');
                    $('#progress' + randID).width('100%').height(5).css('opacity', '.6');
                }

                // Animate progress bar
                function startProgressBar(barTime) {
                    barTime = (barTime == null) ? o.displayTime : barTime;
                    $('#progress' + randID).width('100%').height(5);
                    $('#progress' + randID).animate({ 'width': 0 }, barTime);
                }

                // Build textholder div(s) as wide as one image and as tall as the textholderHeight option
                var containerBorder = parseInt($(obj).css('border-bottom-width')) + parseInt($(obj).css('border-top-width'));
                if (isNaN(containerBorder)) containerBorder = 0; // IE returns NaN for $(obj).css('border-bottom-width')
                var containerPaddingLeft = parseInt($(obj).css('padding-left')); // Normally we'd do both left and right but only left matters here
                for (i = 1; i <= o.inView; i++) {
                    $(obj).append('<div id="textholder' + randID + '_' + i + '" class="textholder" style="position:absolute;width:' + imgWidth + 'px;bottom:0px;margin-bottom:' + -(imgHeight * o.textholderHeight + containerBorder) + 'px;"><span></span></div>');
                    $('#textholder' + randID + '_' + i).css({ 'left': (i - 1) * (imgWidth + parseInt(o.padding) * 2), 'margin-left': parseInt(o.padding) + containerPaddingLeft, 'margin-right': o.padding });
                    $('#textholder' + randID + '_' + i).height(imgHeight * o.textholderHeight).css({ 'backgroundColor': '#FFF', 'opacity': '0.5' });
                    html = '<div class="minmax" id="minmax' + randID + '_' + i + '" style="width:8px;height:8px;position:absolute;top:1px;right:10px;cursor:pointer;background:url(' + o.imagePath + 'caption.gif) no-repeat 0 -8px"></div>';
                    html += '<div class="close" id="close' + randID + '_' + i + '" style="width:8px;height:8px;position:absolute;top:1px;right:1px;cursor:pointer;background:url(' + o.imagePath + 'caption.gif) no-repeat 0 0"></div>';
                    $('#textholder' + randID + '_' + i).append(html);
                    $('#minmax' + randID + '_' + i).hide();
                    $('#close' + randID + '_' + i).hide();
                    if (!o.autoHideCaptions) showtext($('li:eq(' + i + ') p', obj).html(), i);
                }
                var textholderPadding = parseInt($('#textholder' + randID + '_1').css('padding-left')) + parseInt($('#textholder' + randID + '_1').css('padding-right'));
                if (textholderPadding > 0) $('.textholder', obj).width(imgWidth - textholderPadding);

                $('.close', obj).each(function (i) { // Need to use each() because a loop doesn't work in this situation. see http://www.bennadel.com/blog/534-The-Beauty-Of-The-jQuery-Each-Method.htm
                    $(this).click(function () { $('#textholder' + randID + '_' + (i + 1)).animate({ marginBottom: (-imgHeight * o.textholderHeight) - containerBorder - 1 + 'px' }, 500) });
                });
                $('.minmax', obj).each(function (i) { // Same reason as previous chunk
                    $(this).click(function () {
                        if (parseInt($('#textholder' + randID + '_' + (i + 1)).css('margin-bottom')) == 0) {
                            $('#textholder' + randID + '_' + (i + 1)).animate({ marginBottom: ((-imgHeight * o.textholderHeight) - containerBorder + 12) + 'px' }, 500, function () {
                                $('#minmax' + randID + '_' + (i + 1)).css('background-position', '0 -12px')
                            });
                        }
                        else {
                            $('#textholder' + randID + '_' + (i + 1)).animate({ marginBottom: '0px' }, 500, function () {
                                $('#minmax' + randID + '_' + (i + 1)).css('background-position', '0 -8px')
                            });
                        }

                    });
                });

                function showtext(t, i) {
                    if (autopilot) {
                        $('#minmax' + randID + '_' + i).hide();
                        $('#close' + randID + '_' + i).hide();
                    }
                    if (t != null) {
                        $('#textholder' + randID + '_' + i + ' span').html(t); // Change textholder content
                        $('#textholder' + randID + '_' + i).stop().animate({ marginBottom: '0px' }, 500); // Raise textholder
                        $('#minmax' + randID + '_' + i).css('background-position', '0 -8px');
                        showminmax();
                    }
                }

                function showminmax() {
                    if (!autopilot) {
                        $('.minmax', obj).fadeIn(250);
                        $('.close', obj).fadeIn(250);
                    }
                }

                function hideCaption() { $('.textholder', obj).stop().animate({ marginBottom: (-imgHeight * o.textholderHeight - containerBorder - 1) + 'px' }, o.transitionSpeed) }

                if (o.displayThumbnails) {
                    function thumbclick(event) {
                        target_num = this.id.split('_'); // we want target_num[1]
                        if (viewable[0] != target_num[1]) {
                            status = 'pause';
                            $('#progress' + randID).stop().fadeOut();
                            clearTimeout(clearInt);
                            $('#thumbs' + randID + ' div').css({ 'cursor': 'default' }).unbind('click'); // Unbind the thumbnail click event until the transition has ended
                            autopilot = 0;
                            setTimeout(function () { $('#play_pause_btn' + randID).css('background-position', '0 -12px') }, o.transitionSpeed);
                            $('#play_pause_btn' + randID).unbind('click').bind('click', function () { forceStart(); });
                        }
                        if (target_num[1] > viewable[0]) {
                            diff = target_num[1] - viewable[0];
                            moveLeft(diff);
                        }
                        if (target_num[1] < viewable[0]) {
                            diff = viewable[0] - target_num[1];
                            moveRight(diff);
                        }
                    }

                    var viewable = []; // track which images are being displayed
                    var unviewable = []; // track which images are being displayed
                    // Build thumbnail viewer and thumbnail divs
                    $(obj).after('<div id="thumbs' + randID + '" style="position:relative;overflow:auto;clear:left;text-align:left;padding-top:5px;"></div>');
                    for (i = 0; i <= numImages - 1; i++) {
                        thumb = $('img:eq(' + (i + 1) + ')', obj).attr('src');
                        $('#thumbs' + randID).append('<div class="thumb" id="thumb' + randID + '_' + (i + 1) + '" style="cursor:pointer;background-image:url(' + thumb + ');display:inline;float:left;width:' + o.thumbnailWidth + ';height:' + o.thumbnailHeight + ';line-height:' + o.thumbnailHeight + ';padding:0;overflow:hidden;text-align:center;border:2px solid #ccc;margin-right:4px;font-size:' + o.thumbnailFontSize + ';font-family:Arial;color:#000;text-shadow:0 0 3px #fff">' + (i + 1) + '</div>');
                        if (i <= o.inView) $('#thumb' + randID + '_' + i).css({ 'border-color': '#ff0000' });
                        unviewable.push(i + 1);
                    }
                    // Initialize viewable/unviewable arrays
                    for (i = 1; i <= o.inView; i++) viewable.push(unviewable.shift());

                    // Next two lines are a special case to handle the first list element which was originally the last
                    thumb = $('img:first', obj).attr('src');
                    $('#thumb' + randID + '_' + numImages).css({ 'background-image': 'url(' + thumb + ')' });
                    $('#thumbs' + randID + ' div.thumb:not(:first)').css({ opacity: .65 }); // makes all thumbs 65% opaque except the first one

                    $('#thumbs' + randID + ' div.thumb').hover(function () { $(this).animate({ 'opacity': 1 }, 150) }, function () { if (viewable[0] != this.id.split('_')[1]) $(this).animate({ 'opacity': .65 }, 250) }); // add hover to thumbs
                    // Assign click handler for the thumbnails. Normally the format $('.thumb') would work but since it's outside of our object (obj) it would get called multiple times
                    $('#thumbs' + randID + ' div').bind('click', thumbclick); // We use bind instead of just plain click so that we can repeatedly remove and reattach the handler

                    if (!o.displayThumbnailNumbers) $('#thumbs' + randID + ' div').text('');
                    if (!o.displayThumbnailBackground) $('#thumbs' + randID + ' div').css({ 'background-image': 'none' });
                }

                if (o.showControls) {
                    // Pause/play button(img)
                    html = '<div id="play_pause_btn' + randID + '" style="cursor:pointer;position:absolute;top:7px;right:10px;border:none;width:12px;height:12px;background:url(' + o.imagePath + 'playpause.gif) no-repeat 0 0"></div>';
                    //$(obj).append(html);
                    // append play/pause button to footer of twitter widget 
                    $('.m-twitter-feed .m-f').append(html);
                    var status = 'play';
                    $('#play_pause_btn' + randID).css('opacity', .5).hover(function () { $(this).animate({ opacity: '1' }, 250) }, function () { $(this).animate({ opacity: '.5' }, 250) });
                    $('#play_pause_btn' + randID).click(function () {
                        status = (status == 'play') ? 'pause' : 'play';
                        (status == 'play') ? forceStart() : forcePause();
                    });

                    if (!o.prevNextInternal) {
                        wrapID = $(obj).attr('id') + 'Wrapper';
                        $(obj).wrap('<div id="' + wrapID + '"></div>').css('margin', '0 auto');
                        $('#' + wrapID).css('position', 'relative').width(($(obj).width() + 40 + parseInt($(obj).css('padding-left')) + parseInt($(obj).css('padding-right'))));
                    }

                    // Prev/next button(img)
                    arrowsTop = ((imgHeight / 2) - 15) + parseInt(o.padding);
                    html = '<div id="btn_rt' + randID + '" style="position:absolute;right:30px;top:7px;cursor:pointer;border:none;width:12px;height:12px;background:url(' + o.imagePath + 'leftright.gif) no-repeat 0 0"></div>';
                    html += '<div id="btn_lt' + randID + '" style="position:absolute;left:135px;top:7px;cursor:pointer;border:none;width:12px;height:12px;background:url(' + o.imagePath + 'leftright.gif) no-repeat -12px 0"></div>';
                    (o.prevNextInternal) ? $(obj).parent().append(html) : $('.m-twitter-feed .m-f').append(html);

                    $('#btn_rt' + randID).css('opacity', .5).click(function () {
                        forcePrevNext('next');
                    }).hover(function () { $(this).animate({ opacity: '1' }, 250) }, function () { $(this).animate({ opacity: '.5' }, 250) });
                    $('#btn_lt' + randID).css('opacity', .5).click(function () {
                        forcePrevNext('prev');
                    }).hover(function () { $(this).animate({ opacity: '1' }, 250) }, function () { $(this).animate({ opacity: '.5' }, 250) });

                    if (o.autoHideControls && o.prevNextInternal) {
                        function showcontrols() {
                            $('#play_pause_btn' + randID).stop().animate({ top: '3px', right: '3px' }, 250);
                            $('#btn_rt' + randID).stop().animate({ top: arrowsTop + 'px', right: '2px' }, 250);
                            $('#btn_lt' + randID).stop().animate({ top: arrowsTop + 'px', left: '2px' }, 250);
                        }
                        function hidecontrols() {
                            $('#play_pause_btn' + randID).stop().animate({ top: -16 - containerBorder + 'px', right: -16 - containerBorder + 'px' }, 250);
                            $('#btn_rt' + randID).stop().animate({ right: '-12px' }, 250);
                            $('#btn_lt' + randID).stop().animate({ left: '-12px' }, 250);
                        }
                        $(obj).hover(showcontrols, hidecontrols);
                        hidecontrols();
                    }
                    if (o.autoHideCaptions) {
                        var isHover;
                        function autoShowCap() { isHover = true; for (i = 1; i <= o.inView; i++) showtext($('li:eq(' + i + ') p', obj).html(), i); }
                        function autoHideCap() { isHover = false; hideCaption(); }
                        $(obj).hover(autoShowCap, autoHideCap);
                        hideCaption();
                    }
                }

                function keyBind() {
                    if (o.enableKeyboardNav) {
                        $(document).keydown(function (event) {
                            if (event.keyCode == 39) {
                                forcePrevNext('next');
                                $(document).unbind('keydown');
                            }
                            if (event.keyCode == 37) {
                                forcePrevNext('prev');
                                $(document).unbind('keydown');
                            }
                            if (event.keyCode == 80 || event.keyCode == 111) forcePause();
                            if (event.keyCode == 83 || event.keyCode == 115) {
                                forceStart();
                                $(document).unbind('keydown');
                            }
                        });
                    }
                }

                function forcePrevNext(dir) {
                    o.onPauseClick.call(this);
                    $('#btn_rt' + randID).unbind('click');
                    $('#btn_lt' + randID).unbind('click');
                    setTimeout(function () { $('#play_pause_btn' + randID).css('background-position', '0 -12px') }, o.transitionSpeed - 1);
                    autopilot = 0;
                    $('#progress' + randID).stop().fadeOut();
                    status = 'pause';
                    clearTimeout(clearInt);
                    (dir == 'prev') ? moveRight() : moveLeft();
                    $('#play_pause_btn' + randID).unbind('click');
                    setTimeout(function () {
                        $('#play_pause_btn' + randID).bind('click', function () { forceStart(); });
                        $('#btn_rt' + randID).bind('click', function () { forcePrevNext('next') });
                        $('#btn_lt' + randID).bind('click', function () { forcePrevNext('prev') });
                    }, o.transitionSpeed);
                }

                function forcePause() {
                    $('#play_pause_btn' + randID).unbind('click'); // unbind the click, wait for transition, then reenable
                    if (autopilot) {
                        o.onPauseClick.call(this);
                        $('#play_pause_btn' + randID).fadeTo(250, 0, function () { $(this).css({ 'background-position': '0 -12px' }); }).animate({ opacity: .5 }, 250);
                        autopilot = 0;
                        showminmax();
                        $('#progress' + randID).stop().fadeOut();
                        clearTimeout(clearInt);
                        setTimeout(function () { $('#play_pause_btn' + randID).bind('click', function () { forceStart(); }) }, o.transitionSpeed);
                    }
                }

                function forceStart() {
                    $('#play_pause_btn' + randID).unbind('click'); // unbind the click, wait for transition, then reenable
                    if (!autopilot) {
                        setTimeout(function () { $('#play_pause_btn' + randID).css('background-position', '0 0') }, o.transitionSpeed - 1);
                        autopilot = 1;
                        moveLeft();
                        clearInt = setInterval(function () { moveLeft(); }, o.displayTime + o.transitionSpeed);
                        setTimeout(function () { $('#play_pause_btn' + randID).bind('click', function () { forcePause(); }) }, o.transitionSpeed);
                    }
                }

                function preMove() {
                    hideCaption();
                    // Fade out play/pause/left/right
                    if (o.showControls && o.prevNextInternal) {
                        $('#play_pause_btn' + randID).fadeOut(200);
                        $('#btn_lt' + randID).fadeOut(200);
                        $('#btn_rt' + randID).fadeOut(200);
                    }
                    if (o.displayThumbnails) for (i = 1; i <= numImages; i++) $('#thumb' + randID + '_' + i).css({ 'border-color': '#ccc' }).animate({ 'opacity': .65 }, 500);
                }

                function postMove() {
                    if (o.showControls && o.prevNextInternal) {
                        $('#play_pause_btn' + randID).fadeIn(200);
                        $('#btn_lt' + randID).fadeIn(200);
                        $('#btn_rt' + randID).fadeIn(200);
                    }
                    keyBind();
                    if (o.autoHideCaptions && isHover) autoShowCap();
                    if (o.displayThumbnails) for (i = 0; i < viewable.length; i++) $('#thumb' + randID + '_' + viewable[i]).css({ 'border-color': '#ff0000' }).animate({ 'opacity': 1 }, 500);
                    if (!o.autoHideCaptions) for (i = 1; i <= o.inView; i++) showtext($('li:eq(' + i + ') p', obj).html(), i);
                    if (o.displayThumbnails) $('#thumbs' + randID + ' div').unbind('click').bind('click', thumbclick).css({ 'cursor': 'pointer' });
                    ary = [];
                    for (x = 1; x <= o.inView; x++) { ary.push($('img:eq(' + x + ')', obj).attr('src')) }
                    o.onSlideEnd.call(this, ary);
                }

                function moveLeft(dist) {
                    if (dist == null) dist = o.advance;
                    preMove();
                    if (o.displayThumbnails) {
                        for (i = 1; i <= dist; i++) {
                            viewable.push(unviewable.shift());
                            unviewable.push(viewable.shift());
                        }
                    }
                    if (o.displayTime == 0) { clearInterval(clearInt); } // If running a contonuous show with no display time, fist clear the interval. Then below, recursively call moveLeft
                    $('li:lt(' + dist + ')', obj).clone(true).insertAfter($('li:last', obj)); // Copy the first image (offscreen to the left) to the end of the list (offscreen to the right)
                    o.onSlideStart.call(this, viewable, 'left');
                    $('ul', obj).animate({ left: -imgWidth * (dist + 1) - (parseInt(o.padding) * (dist + 1)) * 2 }, o.transitionSpeed, o.easeLeft, function () { // Animate the entire list to the left
                        $('li:lt(' + dist + ')', obj).remove(); // When the animation finishes, remove the first image (on the left). It has already been copied to the end of the list (right)
                        $(this).css({ 'left': -imgWidth - parseInt(o.padding) * 2 });
                        if (o.displayProgressBar && autopilot) startProgressBar();
                        postMove();
                        if (o.displayTime == 0) { moveLeft(); }
                    });
                }
                function moveRight(dist) {
                    if (dist == null) dist = o.advance;
                    preMove();
                    if (o.displayThumbnails) {
                        for (i = 1; i <= dist; i++) {
                            viewable.unshift(unviewable.pop());
                            unviewable.unshift(viewable.pop());
                        }
                    }
                    $('li:gt(' + (numImages - (dist + 1)) + ')', obj).clone(true).insertBefore($('li:first', obj)); // Copy rightmost (last) li and insert it after the first li
                    o.onSlideStart.call(this, viewable, 'right');
                    $('ul', obj).css('left', -(imgWidth * (dist + 1)) - (parseInt(o.padding) * ((dist + 1) * 2)))
					.animate({ left: -imgWidth - (parseInt(o.padding) * 2) }, o.transitionSpeed, o.easeRight, function () {
					    $('li:gt(' + (numImages - 1) + ')', obj).remove();
					    postMove();
					});
                }

                // Kickoff the show
                if (autopilot) {
                    var clearInt = setInterval(function () { moveLeft(); }, o.displayTime + o.transitionSpeed);
                    if (o.displayProgressBar) startProgressBar(o.displayTime + o.transitionSpeed);
                } else { status = 'pause'; $('#play_pause_btn' + randID).css({ 'background-position': '0 -12px' }); }
                keyBind();
            });
        }
    });

    $.fn.tweet = function (o) {
        var s = $.extend({
            username: null,                           // [string or array] required unless using the 'query' option; one or more twitter screen names
            list: null,                               // [string]   optional name of list belonging to username
            favorites: false,                         // [boolean]  display the user's favorites instead of his tweets
            query: null,                              // [string]   optional search query
            avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
            count: 3,                                 // [integer]  how many tweets to display?
            fetch: null,                              // [integer]  how many tweets to fetch via the API (set this higher than 'count' if using the 'filter' option)
            retweets: true,                           // [boolean]  whether to fetch (official) retweets (not supported in all display modes)
            intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
            outro_text: null,                         // [string]   do you want text AFTER your tweets?
            join_text: null,                         // [string]   optional text in between date and tweet, try setting to "auto"
            auto_join_text_default: "i said,",        // [string]   auto text for non verb: "i said" bullocks
            auto_join_text_ed: "i",                   // [string]   auto text for past tense: "i" surfed
            auto_join_text_ing: "i am",               // [string]   auto tense for present tense: "i was" surfing
            auto_join_text_reply: "i replied to",     // [string]   auto tense for replies: "i replied to" @someone "with"
            auto_join_text_url: "i was looking at",   // [string]   auto tense for urls: "i was looking at" http:...
            loading_text: null,                       // [string]   optional loading text, displayed while tweets load
            refresh_interval: null,                  // [integer]  optional number of seconds after which to reload tweets
            twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
            twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
            twitter_search_url: "search.twitter.com", // [string]   custom twitter search url, if any (apigee, etc.)
            template: "{avatar}{time}{join}{text}",   // [string or function] template used to construct each tweet <li> - see code for available vars
            comparator: function (tweet1, tweet2) {    // [function] comparator used to sort tweets (see Array.sort)
                return tweet2["tweet_time"] - tweet1["tweet_time"];
            },
            filter: function (tweet) {                 // [function] whether or not to include a particular tweet (be sure to also set 'fetch')
                return true;
            }
        }, o);

        $.fn.extend({
            linkUrl: function () {
                var returning = [];
                // See http://daringfireball.net/2010/07/improved_regex_for_matching_urls
                // M Fosbrook - Original regex didn't work so replaced with one from here http://thomasbillenstein.com/jTweetsAnywhere/demo/ 
                // the regex to markup links                 
                var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi;
                this.each(function () {
                    returning.push(this.replace(regexp,
                                      function (match) {
                                          var url = (/^[a-z]+:/i).test(match) ? match : "http://" + match;
                                          
                                          return "<a href=\"" + url + "\">" + match + "</a>";                                          
                                          
                                      }));
                });
                return $(returning);
            },
            linkUser: function () {
                var returning = [];
                var regexp = /[\@]+(\w+)/gi;
                this.each(function () {
                    returning.push(this.replace(regexp, "@<a href=\"http://" + s.twitter_url + "/$1\">$1</a>"));
                    
                });
                return $(returning);
            },
            linkHash: function () {
                var returning = [];
                // Support various latin1 (\u00**) and arabic (\u06**) alphanumeric chars
                var regexp = /[#]+([A-Za-z0-9-_]+)/gi;
                var usercond = (s.username && s.username.length == 1) ? '&from=' + s.username.join("%2BOR%2B") : '';
                this.each(function () {
                    
                   returning.push(this.replace(regexp, ' <a href="http://' + s.twitter_search_url + '/search?q=&tag=$1&lang=all' + usercond + '">#$1</a>'));

                });
                return $(returning);
            },
            capAwesome: function () {
                var returning = [];
                this.each(function () {
                    returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
                });
                return $(returning);
            },
            capEpic: function () {
                var returning = [];
                this.each(function () {
                    returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
                });
                return $(returning);
            },
            makeHeart: function () {
                var returning = [];
                this.each(function () {
                    returning.push(this.replace(/(&lt;)+[3]/gi, "<tt class='heart'>&#x2665;</tt>"));
                });
                return $(returning);
            }
        });

        function parse_date(date_str) {
            // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
            // cannot handle in IE. We therefore perform the following transformation:
            // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
            return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
        }

        function relative_time(date) {
            var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
            var delta = parseInt((relative_to.getTime() - date) / 1000, 10);
            var r = '';
            if (delta < 60) {
                r = delta + ' seconds ago';
            } else if (delta < 120) {
                r = '1 minute ago';
            } else if (delta < (45 * 60)) {
                r = (parseInt(delta / 60, 10)).toString() + ' minutes ago';
            } else if (delta < (2 * 60 * 60)) {
                r = '1 hour ago';
            } else if (delta < (24 * 60 * 60)) {
                r = '' + (parseInt(delta / 3600, 10)).toString() + ' hours ago';
            } else if (delta < (48 * 60 * 60)) {
                r = '1 day ago';
            } else {
                r = (parseInt(delta / 86400, 10)).toString() + ' days ago';
            }
            //return 'about ' + r;
            return '' + r;
        }

        function build_url() {
            var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
            var count = (s.fetch === null) ? s.count : s.fetch;
            if (s.list) {
                return proto + "//" + s.twitter_api_url + "/1/" + s.username[0] + "/lists/" + s.list + "/statuses.json?per_page=" + count + "&callback=?";
            } else if (s.favorites) {
                return proto + "//" + s.twitter_api_url + "/favorites/" + s.username[0] + ".json?count=" + s.count + "&callback=?";
            } else if (s.query === null && s.username.length == 1) {
                return proto + '//' + s.twitter_api_url + '/1/statuses/user_timeline.json?screen_name=' + s.username[0] + '&count=' + count + (s.retweets ? '&include_rts=1' : '') + '&callback=?';
            } else {
                var query = (s.query || 'from:' + s.username.join(' OR from:'));
                return proto + '//' + s.twitter_search_url + '/search.json?&q=' + encodeURIComponent(query) + '&rpp=' + count + '&callback=?';
            }
        }

        return this.each(function (i, widget) {
            var list = $('<ul class="tweet_list">').appendTo(widget);
            var intro = '<p class="tweet_intro">' + s.intro_text + '</p>';
            var outro = '<p class="tweet_outro">' + s.outro_text + '</p>';
            var loading = $('<p class="loading">' + s.loading_text + '</p>');

            if (s.username && typeof (s.username) == "string") {
                s.username = [s.username];
            }

            var expand_template = function (info) {
                if (typeof s.template === "string") {
                    var result = s.template;
                    for (var key in info) {
                        var val = info[key];
                        result = result.replace(new RegExp('{' + key + '}', 'g'), val === null ? '' : val);
                    }
                    return result;
                } else return s.template(info);
            };

            if (s.loading_text) $('.m-twitter-feed #carousel').prepend(loading);
            $(widget).bind("load", function () {
                $.getJSON(build_url(), function (data) {
                    if (s.loading_text) loading.remove();
                    if (s.intro_text) list.before(intro);
                    list.empty();

                    var tweets = $.map(data.results || data, function (item) {
                        var join_text = s.join_text;

                        // auto join text based on verb tense and content
                        if (s.join_text == "auto") {
                            if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
                                join_text = s.auto_join_text_reply;
                            } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
                                join_text = s.auto_join_text_url;
                            } else if (item.text.match(/^((\w+ed)|just) .*/im)) {
                                join_text = s.auto_join_text_ed;
                            } else if (item.text.match(/^(\w*ing) .*/i)) {
                                join_text = s.auto_join_text_ing;
                            } else {
                                join_text = s.auto_join_text_default;
                            }
                        }

                        // Basic building blocks for constructing tweet <li> using a template
                        var screen_name = item.from_user || item.user.screen_name;
                        var source = item.source;
                        var user_url = "http://" + s.twitter_url + "/" + screen_name;
                        var avatar_size = s.avatar_size;
                        var avatar_url = item.profile_image_url || item.user.profile_image_url;
                        var tweet_url = "http://" + s.twitter_url + "/" + screen_name + "/status/" + item.id_str;
                        var retweet = (typeof (item.retweeted_status) != 'undefined');
                        var retweeted_screen_name = retweet ? item.retweeted_status.user.screen_name : null;
                        var tweet_time = parse_date(item.created_at);
                        var tweet_relative_time = relative_time(tweet_time);
                        var tweet_raw_text = retweet ? ('RT @' + retweeted_screen_name + ' ' + item.retweeted_status.text) : item.text; // avoid '...' in long retweets
                        var tweet_text = $([tweet_raw_text]).linkUrl().linkUser().linkHash()[0];

                        // Default spans, and pre-formatted blocks for common layouts
                        var user = '<a class="tweet_user" href="' + user_url + '">' + screen_name + '</a>';
                        var join = ((s.join_text) ? ('<span class="tweet_join"> ' + join_text + ' </span>') : ' ');
                        var avatar = (avatar_size ?
                          ('<a class="tweet_avatar" href="' + user_url + '"><img src="' + avatar_url +
                           '" height="' + avatar_size + '" width="' + avatar_size +
                           '" alt="' + screen_name + '\'s avatar" title="' + screen_name + '\'s avatar" border="0"/></a>') : '');
                        var time = '<span class="tweet_time"><a href="' + tweet_url + '" title="View tweet on twitter">' + tweet_relative_time + '</a></span>';
                        var text = '<span class="tweet_text">' + $([tweet_text]).makeHeart().capAwesome().capEpic()[0] + '</span>';

                        return { item: item, // For advanced users who want to dig out other info
                            screen_name: screen_name,
                            user_url: user_url,
                            avatar_size: avatar_size,
                            avatar_url: avatar_url,
                            source: source,
                            tweet_url: tweet_url,
                            tweet_time: tweet_time,
                            tweet_relative_time: tweet_relative_time,
                            tweet_raw_text: tweet_raw_text,
                            tweet_text: tweet_text,
                            retweet: retweet,
                            retweeted_screen_name: retweeted_screen_name,
                            user: user,
                            join: join,
                            avatar: avatar,
                            time: time,
                            text: text
                        };
                    });

                    tweets = $.grep(tweets, s.filter).sort(s.comparator).slice(0, s.count);
                    list.append($.map(tweets,
                            function (t) { return "<li><img src='/assets/images/tweet-spacer.gif' class='tweet-spacer' width='170' height='140' />" + expand_template(t) + "</li>"; }).join('')).children('li:first').addClass('tweet_first').end().children('li:odd').addClass('tweet_even').end().children('li:even').addClass('tweet_odd');


                    $('#carousel').infiniteCarousel({
                        transitionSpeed: 500,
                        displayTime: 5000,
                        inView: 1,
                        advance: 1,
                        autoHideCaptions: true,
                        prevNextInternal: false,
                        displayProgressBar: false,
                        displayThumbnails: false
                    });
                    $('#carousel').css('min-height', '160px', 'height', '160px');
                    $('#carouselWrapper').css({ width: '190px' });

                    if (s.outro_text) list.after(outro);
                    $(widget).trigger("loaded").trigger((tweets.length === 0 ? "empty" : "full"));

                    if (s.refresh_interval) {
                        window.setTimeout(function () { $(widget).trigger("load"); }, 1000 * s.refresh_interval);
                    }
                });

            }).trigger("load");
        });
    };

})(jQuery);

