/**
 * jQuery Monotweet Plugin v1.0
 * http://www.isaac.nl/plugins/jquery/monotweet/
 * A JQuery plugin to show and animate the latest tweets one at a time
 *
 * February 15th, 2011
 *
 * Copyright (c) 2011 ISAAC BV (http://www.isaac.nl)
 * Dual licensed under the MIT and GPL licenses.
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.opensource.org/licenses/gpl-license.php
 *
 **/
(function($){

  // Unique name space for the JQuery.data way of saving sate on a DOM element.
  var ns = 'nl.isaac.monotweet.'

    // A list of methods that this plugin provided. Conforming to http://docs.jquery.com/Plugins/Authoring
    ,methods = {

  /**
   * Public function that refreshes all the twitter objects in the wrapped set.
   *
   * Usage: $('your_monotweet_selector').monotweet('refresh');
   */
  refresh: function() {
    return this.each(function() {
      methods._refreshOne(this);
    })
  }


  /* Private function that initializes everything. Is called automatically at creation time.
   *
   * @param {Object} overrideOptions  The override values for the dafault options.
   */
    ,_init: function(overrideOptions) {

      // default values for the options of this plugin
      var defaults = {
        twitterName: 'isaaceindhoven'    /*  Twitter account that will be used. */
        ,nrOfTweets: 10            /*  Number of tweets that will be shown in the list. */
        ,includeRetweets: true        /*  True if retweets need to be displayed, false otherwise. */
        ,animateSpeed: 200          /*  Speed in msec of the scroll animation. */
        ,autoScrollSpeed: 5500        /*  Time in msec between two scroll animations. */
        ,autoStart: true          /*  True: get tweets on pageload. False: retrieve tweets later. */
        ,autoRefresh: true          /*  True: automatically refresh tweets, false: refresh manually.
                            Auto refresh will start when the first tweets are loaded */
        ,autoRefreshSpeed: 60000      /*  Time in msec in between two auto refreshes. Only used if
                            'autoRefresh' is true. */
        ,listSelector: '.tweet-list'    /*  CSS3 selector that identifies a list tag (UL/OL) that will
                            get populated with LI tags containing the tweets (Tag will
                            be searched for only within the wrapped set's scope) */
        ,navSelector: '.tweet-list-control'  /*  CSS3 selector that identifies a list tag (UL/OL) that will
                            get populated with LI tags that function as navigation. */
        ,dateClass: 'tweet-date'      /*  Class name of the DIVs containing the date of the tweet. */
      }
      ,options = $.extend(defaults, overrideOptions)
      ,ws = this;

      // Loop over all the elements in the wrapped set. Ttypically there will be only one, because having two
      // identical twitter feeds on one page does not make sense most of the time.
      return this.each(function() {
        var t = this, $t = $(this);

        // Store state in the rootelement of the monotweet plugin for later use.
        $.data(t, ns + 'current', -1);      /* The currently selected tweet (-1: nothing is loaded yet) */
        $.data(t, ns + 'options', options);
        $.data(t, ns + 'tw', $t.find(options.listSelector));
        $.data(t, ns + 'twCtrl', $t.find(options.navSelector));

        // Bind the event that will make the scrolling halt or resume at a mouseover or mouseout.
        $t.bind('mouseover.monotweet', function() { methods._stopAutoScroll(t) })
          .bind('mouseout.monotweet', function() { methods._startAutoScroll(t) });

        // Load the tweets only if autoStart is true
        if (options.autoStart) {
          methods._refreshOne(t);
        }

        // Setup the auto refresh only if autoRefreh is true.
        if (options.autoRefresh) {
          setInterval(function() {

            // Only actually refresh if some tweets have been loaded before.
            if ($.data(t, ns + 'current') > -1) {
              ws.monotweet('refresh');
            }
          }, options.autoRefreshSpeed);
        }
      })
    }


    /* Private function that starts the auto scrolling of the tweets.
     *
     * @param {Element} elem  The DOM node in the wrapped set this plugin was called on.
     */
    ,_startAutoScroll : function(elem) {
      var options = $.data(elem, ns + 'options');
      methods._stopAutoScroll(elem);
      $.data(elem, ns + 'interval', setInterval(function() { methods._scrollTo(elem, false) }, options.autoScrollSpeed));
    }


    /* Private function that ends the auto scrolling of the tweets.
     *
     * @param {Element} elem  The DOM node in the wrapped set this plugin was called on.
     */
    ,_stopAutoScroll : function(elem) {
      var i = $.data(elem, ns + 'interval');
      if (i) {
        clearInterval(i);
      };
      $.data(elem, ns + 'interval', null);
    }


    /* Private function that replaces URL's in the given string and wrapes them in a <A> tag.
     *
     * @param {String} txt  The string we need to replace the URL's in.
     */
    ,_addLinks: function(txt) {
      var url = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig
        ,tweep = /(@\w*)/ig;
      return txt
        .replace(url, "<a href='$1' target='_blank'>$1</a>")
        .replace(tweep, "<a href='http://twitter.com/$1' target='_blank'>$1</a>");
    }


    /* Private function that scrolls to one of the tweets in the list.
     *
     * @param {Element} elem      The DOM node in the wrapped set this plugin was called on.
     * @param {Boolean} skipAnimation  (Optional) True if no animation must be used, false otherwise.
     * @param {Integer} i        (Optional) The index of the tweets in the list that must be
     *                   scrolled to. If omitted the next in line will be scrolled to.
     */
    ,_scrollTo: function(elem, skipAnimation, i) {
      var tw = $.data(elem, ns + 'tw')
        ,twCtrl = $.data(elem, ns + 'twCtrl')
        ,current = $.data(elem, ns + 'current')
        ,options = $.data(elem, ns + 'options');

      i = i === undefined? current + 1: i;
      i = i % tw.find('li').size();

      // do the actual scrolling (animated if desired)
      tw.stop().animate({left: -i * $(elem).width()}, skipAnimation? 0: options.animateSpeed);

      // Set the 'active' class to the LI element in the controller list that represents the
      // currently selected  (scrolled to) tweet.
      twCtrl.find('li').removeClass('active');
      twCtrl.find('li:nth-child(' + (i + 1) + ')').addClass('active');

      // Save the index of the currently selected (scrolled to) tweet.
      $.data(elem, ns + 'current', i);
    }


    /* Private function that refreshes the tweets of the given DOM node.
     *
     * @param {Element} elem  The DOM node in the wrapped set this plugin was called on.
     */
    ,_refreshOne: function(elem) {
      var options = $.data(elem, ns + 'options')
        ,tw = $.data(elem, ns + 'tw')
        ,twCtrl = $.data(elem, ns + 'twCtrl')
        ,current = $.data(elem, ns + 'current');

      if (options) {
        var api = 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name='
          + options.twitterName + '&count=' + options.nrOfTweets + '&callback=?'
          + '&include_rts=' + (options.includeRetweets? 'true': 'false');

        // Do the ajax call to the twitter API
        $.getJSON(api, null, function (data) {

          // We safe the state the scrolling was in, so we can determine if we need to
          // start scrolling after the update.
          var wasScrolling = $.data(elem, ns + 'interval') || current == -1;

          // Stop the autoscrolling
          methods._stopAutoScroll(elem);

          // remove the old tweets and controls
          tw.empty();
          twCtrl.empty();

          // For each tweet: add the tweet LI and control LI to the lists
          $.each(data, function (i, item) {
            
            // Format the date
            var dat = item.created_at.substr(8, 2) + ' ' + item.created_at.substr(4, 3).toLowerCase()
              
              // If the tweet is a retweet the item.text can contain truncated text. That's 
              // why we use the original tweet in that case, prefixed with the username.
              ,rt = item.retweeted_status
              ,txt = rt
                ?('RT @' + rt.user.screen_name + ': ' + rt.text)
                :item.text;
              
            // Add the tweet LI
            tw.append('<li>' + methods._addLinks(txt) + '<div class="' + options.dateClass + '">' + dat + '</div></li>');

            // Add the control LI and bind the event that lets the user control the visible tweet by hovering
            twCtrl.append($('<li></li>').bind('mouseover.monotweet', function() {
              methods._scrollTo(elem, false, i);
            }));
          });

          // scroll to the last visble tweet
          methods._scrollTo(elem, true, current == -1? 0: current);

          // Start the scrolling again if we were scrolling before
          if (wasScrolling) {
            methods._startAutoScroll(elem);
          }
        });
      }
    }
  };


  /**
   *
   * @param {Object} method
   */
  $.fn.monotweet = function( method ) {
    if ( methods[method] ) {
      return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
    } else if ( typeof method === 'object' || ! method ) {
      return methods._init.apply( this, arguments );
    } else {
      $.error( 'Method ' +  method + ' does not exist on jQuery.monotweet' );
    }
  };
})(jQuery);
