/**
 * An unofficial, modified version of the work of:
 * remy sharp / http://remysharp.com
 * Twitter / @rem
 * http://remysharp.com/2007/05/18/add-twitter-to-your-blog-step-by-step/
 *
 * @params
 *   cssIdOfContainer: e.g. twitters
 *   options: 
 *     {
 *       id: {String} username,
 *       count: {Int} 1-20, defaults to 1 - max limit 20
 *       prefix: {String} '%name% said', defaults to blank
 *       clearContents: {Boolean} true, removes contents of element specified in cssIdOfContainer, defaults to true
 *       ignoreReplies: {Boolean}, skips over tweets starting with '@', defaults to false
 *       template: {String} HTML template to use for LI element (see URL above for examples), defaults to predefined template
 *       enableLinks: {Boolean} linkifies text, defaults to true,
 *       newwindow: {Boolean} opens links in new window, defaults to false
 *       timeout: {Int} How long before triggering onTimeout, defaults to 10 seconds
 *       onTimeoutCancel: {Boolean} Completely cancel twitter call if timedout, defaults to false
 *       onTimeout: {Function} Function to run when the timeout occurs. Function is bound to element specified with 
 *         cssIdOfContainer (i.e. 'this' keyword)
 *       callback: {Function} Callback function once the render is complete, doesn't fire on timeout
 *       maskLinkText: {String} replaces link text if present, default blank
 *       maxLinkLength : {Int} maximum length of links, defaults to 25
 *       relativeTime : {Boolean} Whether to use relative time stamps, defaults to true
 *
 *    CURRENTLY DISABLED DUE TO CHANGE IN TWITTER API:
 *       withFriends: {Boolean} includes friend's status
 *
 *     }
 *
 * @license MIT (MIT-LICENSE.txt)
 * @version 1.13.custom - unofficial new features and bug fixes
 * @date $Date: 2010-03-21 20:52:00 -0500 (Sun, 21 Mar 2010) $
 */

// to protect variables from resetting if included more than once
if (typeof getTwitters != 'function') (function () {
  /** Private variables */
  
  var defaultOptions = {
    id : '',
    count : 1,
    prefix : '',
    clearContents : true,
    ignoreReplies : false,
    template : '<span class="twitterStatus">%text%</span><span class="twitterSpacer"></span><span class="twitterTime"><a href="http://twitter.com/%user_screen_name%/statuses/%id%/">%time%</a></span>',
    enableLinks : true,
    newwindow : false,
    timeout : 10,
    onTimeoutCancel : false,
    onTimeout : null,
    callback : null,
    maskLinkText : '',
    maxLinkLength : 25,
    relativeTime : true
  }
  
  // only used for the DOM ready, since IE & Safari require special conditions
  var browser = (function() {
    var b = navigator.userAgent.toLowerCase();

    // Figure out what browser is being used
    return {
      webkit: /(webkit|khtml)/.test(b),
      opera: /opera/.test(b),
      msie: /msie/.test(b) && !(/opera/).test(b),
      mozilla: /mozilla/.test(b) && !(/(compatible|webkit)/).test(b)
    };
  })();

  var guid = 0;
  var readyList = [];
  var isReady = false;
  
  var monthDict = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  
  function renderTwitters(obj, options) {
  
    // based on Dustin Diaz's ify, but with my fixes :-)
    // ...and my modifications...   [N]
    var ify = {
      "link": function(t) {
        return t.replace(/[a-z]+(:\/\/)?[a-z0-9-_]+\.[a-z0-9-_:~%&\?\/.=]*[^!:\.,\(\)\s*$]/ig, function(m, m1) {
          if (!m1 && m.indexOf('/') < 4 && m.length - m.lastIndexOf('.') > 4) return m;
          var lt = options.maskLinkText, ll = options.maxLinkLength;
          var t = lt ? lt : m.substr(m1 ? m.indexOf(m1)+m1.length : 0, ll) + (m.length > ll ? '...' : '');
          return '<a href="' + (m1 ? '' : 'http://') + m + '"> ' + t + ' </a>';
        });
      },
      "at": function(t) {
          return t.replace(/(^|[^\w]+)\@([a-zA-Z0-9_]{1,15}(\/[a-zA-Z0-9-_]+)*)/g, function(m, m1, m2) {
            if (m.length < 4) return m;
            return m1 + '@<a href="http://twitter.com/' + m2 + '">' + m2 + '</a>';
        });
      },
      "hash": function(t) {
          return t.replace(/(^|[^&\w'"]+)\#([a-zA-Z0-9_]+)/g, function(m, m1, m2) {
            if (m.length < 4) return m;
            return m1 + '#<a href="http://search.twitter.com/search?q=%23' + m2 + '">' + m2 + '</a>';
        });
      },
      "clean": function(tweet) {
        return this.hash(this.at(this.link(tweet)));
      }
    };

    var target = document.getElementById(options.twitterTarget);
    var data, li, statusSpan, timeSpan, i, markup;
    var ul = document.createElement('ul'); 
    var max = obj.length > options.count ? options.count : obj.length;
    
    for (i = 0; i < max && obj[i]; i++) {
         
      if (options.ignoreReplies && obj[i].text.substr(0, 1) == '@') {
        max++;
        continue; // skip
      }
      
      data = getTwitterData(obj[i], options);
      markup = '';
      
      if (options.prefix) {
        var p = options.prefix.replace(/%(.*?)%/g, function (m, l) {
          return obj[i].user[l];
        });
        markup += '<span class="twitterPrefix">' + p + '</span>';
      }
      
      markup += options.template.replace(/%([a-z_\-\.]*)%/ig, function (m, l) {
        var r = data[l] + "" || "";
        if (l == 'text' && options.enableLinks) r = ify.clean(r);
        return r;
      });
      
      if (options.newwindow) {
        markup = markup.replace(/<a href/gi, '<a target="_blank" href');
      }
      
      li = document.createElement('li');
      li.innerHTML = markup;
      ul.appendChild(li);
    }

    if (options.clearContents) {
      while (target.firstChild) {
        target.removeChild(target.firstChild);
      }
    }

    target.appendChild(ul);
    
    if (typeof options.callback == 'function') {
      options.callback();
    }
  }
  
  /** Global functions */
  
  // to create a public function within our private scope, we attach the 
  // the function to the window object
  
  window.getTwitters = function (target, id, count, userOptions) {

    var options = {};
    
    guid++;

    if (typeof id == 'object') {
      userOptions = id;
      id = userOptions.id;
    } 

    if (!userOptions) userOptions = {};
    
    for (var i in defaultOptions){
      if (i in userOptions) options[i] = userOptions[i];
      else if (i in defaultOptions) options[i] = defaultOptions[i];
    }
    
    // need to make these global since we can't pass in to the twitter callback
    options['twitterTarget'] = target;
    
    // Hack to disable withFriends, twitter changed their API so this requires auth
    // http://getsatisfaction.com/twitter/topics/friends_timeline_api_call_suddenly_requires_auth
    if (options.withFriends) options.withFriends = false;

    // this looks scary, but it actually allows us to have more than one twitter
    // status on the page, which in the case of my example blog - I do!
    window['twitterCallback' + guid] = function (obj) {
      if (typeof options.onTimeout == 'function') {
        clearTimeout(window['twitterTimeout' + guid]);
      }
      getTwitters.renderTwitters(obj, options);
    };

    // check out the mad currying!
    ready((function(options, guid) {
      return function () {
        // if the element isn't on the DOM, don't bother
        if (!document.getElementById(options.twitterTarget)) {
          return;
        }
        var count = options.count ? options.ignoreReplies ? options.count * 3 : options.count : 1;
        var url = 'http://www.twitter.com/statuses/' + (options.withFriends ? 'friends_timeline' : 'user_timeline') + '/' + id + '.json?callback=twitterCallback' + guid + '&count=' + count + '&cb=' + Math.random();

        if (typeof options.onTimeout == 'function') {
          window['twitterTimeout' + guid] = setTimeout(function () {
            // cancel callback
            if (options.onTimeoutCancel) window['twitterCallback' + guid] = function () {};
            options.onTimeout.call(document.getElementById(options.twitterTarget));
          }, options.timeout * 1000);
        }
        
        var script = document.createElement('script');
        script.setAttribute('src', url);
        document.getElementsByTagName('head')[0].appendChild(script);
      };
    })(options, guid));
  };
  
  getTwitters.renderTwitters = renderTwitters;
    
  // GO!
  DOMReady();
  
  /** Private functions */
  
  function getTwitterData(orig, options) {
    var data = orig, i;
    for (i in orig.user) {
      data['user_' + i] = orig.user[i];
    }
    data.time = fuzzy_time(orig.created_at, options.relativeTime);
    return data;
  }
  
  function ready(callback) {
    if (!isReady) {
      readyList.push(callback);
    } else {
      callback.call();
    }
  }
  
  function fireReady() {
    isReady = true;
    var fn;
    while (fn = readyList.shift()) {
      fn.call();
    }
  }

  // ready and browser adapted from John Resig's jQuery library (http://jquery.com)
  function DOMReady() {
    if ( document.addEventListener && !browser.webkit ) {
      document.addEventListener( "DOMContentLoaded", fireReady, false );
    } else if ( browser.msie ) {
      // If IE is used, use the excellent hack by Matthias Miller
      // http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited

      // Only works if you document.write() it
      document.write("<scr" + "ipt id=__ie_init defer=true src=//:><\/script>");

      // Use the defer script hack
      var script = document.getElementById("__ie_init");

      // script does not exist if jQuery is loaded dynamically
      if (script) {
        script.onreadystatechange = function() {
          if ( this.readyState != "complete" ) return;
          this.parentNode.removeChild( this );
          fireReady.call();
        };
      }

      // Clear from memory
      script = null;

    } else if ( browser.webkit ) {
      // Continually check to see if the document.readyState is valid
      var safariTimer = setInterval(function () {
        // loaded and complete are both valid states
        if ( document.readyState == "loaded" || 
        document.readyState == "complete" ) {

          // If either one are found, remove the timer
          clearInterval( safariTimer );
          safariTimer = null;
          // and execute any waiting functions
          fireReady.call();
        }
      }, 10);
    }
  }
  
  var convertSecondsTo = {
    'minutes' : function(v) { return v / 60; },
    'hours'   : function(v) { return this.minutes(v) / 60; },
    'days'    : function(v) { return this.hours(v) / 24; },
    'weeks'   : function(v) { return this.days(v) / 7; },
    'months'  : function(v) { return this.weeks(v) / 4.34812141; },
    'years'   : function(v) { return this.months(v) / 12; }
  };
  
  function formatTime(date) {
    var h = date.getHours(), m = '' + date.getMinutes();
    return (h > 12 ? h - 12 : h) + ':' + (m.length == 1 ? '0' : '' ) + m + ' ' + (h < 12 ? 'AM' : 'PM');
  }
  
  function formatDate(date) {
    var ds = date.toDateString().split(/ /),
        mon = monthDict[date.getMonth()],
        day = date.getDate()+'',
        dayi = parseInt(day),
        year = date.getFullYear(),
        thisyear = (new Date()).getFullYear(),
        th = 'th';
    
    // anti-'th' - but don't do the 11th, 12th or 13th
    if ((dayi % 10) == 1 && day.substr(0, 1) != '1') {
      th = 'st';
    } else if ((dayi % 10) == 2 && day.substr(0, 1) != '1') {
      th = 'nd';
    } else if ((dayi % 10) == 3 && day.substr(0, 1) != '1') {
      th = 'rd';
    }
    
    if (day.substr(0, 1) == '0') {
      day = day.substr(1);
    }
    
    return mon + ' ' + day + th + (thisyear != year ? ', ' + year : '');
  }
    
  function fuzzy_time(time_value, is_relative) {
    var values = time_value.split(" "),
        parsed_date = Date.parse(values[1] + " " + values[2] + ", " + values[5] + " " + values[3] + " UTC"),
        date = new Date(parsed_date),
        relative_to = new Date(),
        r = '',
        delta = parseInt((relative_to.getTime() - date.getTime()) / 1000);
    
    if (!is_relative)
      return formatTime(date) + ' ' + formatDate(date);
        
    if (delta < 30) 
      return 'less than a minute ago';
    var minutes = parseInt(convertSecondsTo.minutes(delta)+0.5);
    if (minutes <= 1) 
      return 'about a minute ago';
    var hours = parseInt(convertSecondsTo.hours(delta)+0.5);
    if (hours < 1) 
      return minutes + ' minutes ago';
    if (hours == 1) 
      return 'about an hour ago';
    var days = parseInt(convertSecondsTo.days(delta)+0.5);
    if (days < 1) 
      return hours + ' hours ago';
    if (days==1) 
      return formatTime(date) + ' yesterday';
    var weeks = parseInt(convertSecondsTo.weeks(delta)+0.5);
    if (weeks < 2) 
      return formatTime(date) + ' ' + days + ' days ago';
    var months = parseInt(convertSecondsTo.months(delta)+0.5);
    if (months < 2) 
      return weeks + ' weeks ago';
    var years = parseInt(convertSecondsTo.years(delta)+0.5);
    if (years < 2) 
      return months + ' months ago';
    return years + ' years ago';
  }
})();

