// ==UserScript==
// @name           Twitter History
// @description	   Gray-out tweets you've already read.
// @include        http://twitter.com/
// ==/UserScript==

var TWHIST = {};  // namespace


TWHIST.dprint = function(msg) {
	if ( "function" === typeof(console.log) ) {
		console.log("TWHIST: " + msg);
	}
	else {
		//alert(msg); // if really debugging, uncomment this
	}
};


TWHIST.init = function(retries) {
	// Need to know if GM_ APIs are available.
	TWHIST.bGM_API = TWHIST.detectGM_APIs();

	// We need to cross the boundary in some cases (onclick, onbeforeunload).
	TWHIST.window = ( TWHIST.bGM_API ? unsafeWindow : window );
	TWHIST.window.TWHISTver = 2;

	// Lots of race conditions across browsers and OSes.  
	if ( 0 == TWHIST.getElementsByTagAndClassName('div', 'stream-items').length ) {
		retries = ( "undefined" == typeof(retries) ? 0 : retries+1 );
		if ( retries < 20 ) {
			retries++;
			setTimeout(function() { TWHIST.init(retries); }, 500);
		}
		return;
	}

	// main functions
	TWHIST.markOldItems();
	TWHIST.watchLinks();
	TWHIST.setNewestViewedTweet();
	TWHIST.checkVersion();
};


// Search for items to gray out.
TWHIST.markOldItems = function() {
	var newestViewedTweet = TWHIST.getNewestViewedTweet();
	if ( null === newestViewedTweet || "undefined" == typeof(newestViewedTweet) ) {
		TWHIST.dprint("bailing: null === newestViewedTweet");
		return false;
	}

	var timeline = TWHIST.getElementsByTagAndClassName('div', 'stream-items')[0];
	var aItems = TWHIST.getElementsByTagAndClassName('div', 'stream-item', timeline);
	if ( !timeline || !aItems.length ) {
		return false;
	}

	var max = aItems.length;
	var bRead = 0;
	for ( var i = 0; i < max; i++ ) {
		var item = aItems[i];
		var id = item.id;
		if ( bRead ) {
			item.style.background = "#F0F0F0";
			item.style.borderBottom = "solid 1px #AAAAAA";
		}
		else if ( newestViewedTweet == item.getAttribute('data-item-id') ) {
			bRead = 1;
			item.style.background = "#F0F0F0";
			item.style.borderBottom = "solid 1px #AAAAAA";
			item.style.borderTop = "solid 6px #AAAAAA";
		}
	}

	return true;
}


// Return the ID of the topmost (most recent) visible item.
TWHIST.getNewestViewedTweet = function() {
	var valName = "TWHIST" + TWHIST.getPageType();
	var newestViewedTweet = TWHIST.gmGetValue(valName);
	TWHIST.dprint("getValue: " + valName + " = " + newestViewedTweet);
	return newestViewedTweet;
};


// 
TWHIST.setNewestViewedTweet = function() {
	var timeline = TWHIST.getElementsByTagAndClassName('div', 'stream-items')[0];
	if ( !timeline ) {
		return;
	}

	var aItems = TWHIST.getElementsByTagAndClassName('div', 'stream-item', timeline);
	if ( aItems.length ) {
		var item = aItems[0];
		var id = item.getAttribute('data-item-id');
		if ( id ) {
			var latest = id;
			var valName = "TWHIST" + TWHIST.getPageType();
			TWHIST.gmSetValue(valName, latest);
			TWHIST.dprint("setValue: " + valName + " = " + latest);
		}
	}
};


// What types of tweets are currently being viewed?
TWHIST.getPageType = function() {
	var pageType = "unknown"; // default

	var title = TWHIST.window.document.title;
	if ( title ) {
		if ( -1 != title.indexOf("Home") ) {
			pageType = "home";
		}
		else if ( -1 != title.indexOf("Twitter / @") ) {
			pageType = "replies";
		}
	}

	return pageType;
};


// Since Twitter is Ajaxy, we can't rely on the onload event to execute this GM script.
// So we watch for clicks on the Home and @<username> links.
TWHIST.watchLinks = function() {
	var home_tab = TWHIST.getElementsByTagAndClassName('li', 'stream-tab-home')[0];
	if ( ! home_tab ) {
		return;
	}
	home_tab.getElementsByTagName('a')[0].onclick = function() { TWHIST.handleClick(0); };
	
	var mentions_tab = TWHIST.getElementsByTagAndClassName('li', 'stream-tab-mentions')[0];
	if ( ! mentions_tab ) {
		return;
	}
	mentions_tab.getElementsByTagName('a')[0].onclick = function() { TWHIST.handleClick(0); };
};


// Handle the user switching from Home to Replies and back.
TWHIST.handleClick = function(attempts, bDontSetLastSeen) {
	attempts = ( "undefined" === typeof(attempts) ? 0 : attempts+1 );

	if ( 20 < attempts ) {
		return;
	}

	var timeline = TWHIST.getElementsByTagAndClassName('div', 'stream-items')[0];
	if ( !timeline ) {
		return;
	}

	var numTweets = timeline.getElementsByTagName('li').length;
	var aItems = TWHIST.getElementsByTagAndClassName('div', 'stream-item', timeline);
	if ( 1 == attempts ) {
		if ( ! bDontSetLastSeen ) {
			// When we click the "more" link, we don't want to set last seen.
			TWHIST.setNewestViewedTweet();
		}

		timeline.TWHIST = numTweets;     // set a flag so we can watch this change
		setTimeout(function () { TWHIST.handleClick(attempts); }, 500);
	}
	else {
		if ( timeline.TWHIST && timeline.TWHIST === numTweets ) {
			setTimeout(function () { TWHIST.handleClick(attempts); }, 500);
		}
		else {
			TWHIST.markOldItems();
			TWHIST.watchLinks();
			TWHIST.setNewestViewedTweet();
		}
	}
};


// Alert the user if there's a newer version of the script to install.
TWHIST.checkVersion = function() {
	var lastCheck = TWHIST.gmGetValue('TWHISTlastcheck');
	if ( "undefined" === typeof(lastCheck) || Number(new Date()) > (lastCheck + (2*24*60*60)) ) { // only check every 2 days
		TWHIST.gmSetValue('TWHISTlastcheck', '' + Number(new Date()));
		TWHIST.loadScript("http://stevesouders.com/twhistversion.js?t=" + Number(new Date()));
	}
};


// Generic function to load a script dynamically.
TWHIST.loadScript = function(url, onload) {
	var domscript = TWHIST.window.document.createElement('script');
	domscript.src = url;
	if ( onload ) {
		domscript.onloadDone = false;
		domscript.onload = function() { 
			if ( !domscript.onloadDone ) {
				domscript.onloadDone = true; 
				onload(); 
			}
		};
		domscript.onreadystatechange = function() {
			if ( ( "loaded" === domscript.readyState || "complete" === domscript.readyState ) && !domscript.onloadDone ) {
				domscript.onloadDone = true;
				domscript.onload();
			}
		}
	}
	TWHIST.window.document.getElementsByTagName('head')[0].appendChild(domscript);
};


TWHIST.gmSetValue = function(name, value) {
	if ( TWHIST.bGM_API ) {
		return GM_setValue(name, value);
	}
	else if ( "undefined" != typeof(localStorage) ) {
		return TWHIST.lsSetValue(name, value);
	}
	else {
		return TWHIST.createCookie(name, value, 365);
	}
};


TWHIST.gmGetValue = function(name) {
	if ( TWHIST.bGM_API ) {
		return GM_getValue(name);
	}
	else if ( "undefined" != typeof(localStorage) ) {
		return TWHIST.lsGetValue(name);
	}
	else {
		return TWHIST.readCookie(name);
	}
};



//
// Use localStorage.
// based on http://userscripts.org/topics/41177
//
TWHIST.lsSetValue = function(name, value) {
	value = (typeof value)[0] + value;
	localStorage.setItem(name, value);
};


TWHIST.lsGetValue = function(name, defaultValue) {
	var value = localStorage.getItem(name);
	if (!value) {
		return defaultValue;
	}

	var type = value[0];
	value = value.substring(1);
	switch (type) {
	case 'b':
		return value == 'true';
	case 'n':
		return Number(value);
	default:
		return value;
	}
};


//
// Use cookies.
// based on PPK: http://www.quirksmode.org/js/cookies.html
//
TWHIST.createCookie = function(name, value, days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else {
		var expires = "";
	}
	TWHIST.window.document.cookie = name + "=" + value + expires + "; path=/";
};


TWHIST.readCookie = function(name) {
	var nameEQ = name + "=";
	var ca = TWHIST.window.document.cookie.split(';');
	for( var i=0; i < ca.length; i++) {
		var c = ca[i];
		while ( c.charAt(0)==' ' ) {
			c = c.substring(1,c.length);
		}
		if ( c.indexOf(nameEQ) == 0 ) {
			return c.substring(nameEQ.length,c.length);
		}
	}
	return null;
};


TWHIST.getElementsByTagAndClassName = function(tag, classname, parent) {
	var aElems = ( parent ? parent.getElementsByTagName(tag) : TWHIST.window.document.getElementsByTagName(tag) );
	var len = aElems.length;
	var aResults = [];
	for ( i = 0; i < len; i++ ) {
		var elem = aElems[i];
		if ( -1 != elem.className.indexOf(classname) ) {
			aResults[aResults.length] = elem;
		}
	}
	return aResults;
};


TWHIST.detectGM_APIs = function() {
	if ( "function" != typeof(GM_getValue) || "function" != typeof(GM_setValue) ) {
		return false;
	}

	// Strangely, GM_setValue *IS* defined in Chrome, but doesn't do anything.
	// So we have to test that it actually works.
	// This will display a message like "undefined is not supported" in Chrome's console log.
	GM_setValue("TWHISTtest", 128);
    return ( 128 == GM_getValue("TWHISTtest") );
};


TWHIST.init();

