
// ==UserScript==
// @name The Game Dude: Working Version
// @namespace	http://adamv.com/greases/
// @description 	Cross-links game sites so you don't have to.
// @include	http://adamv.com/dev/grease/moviedude*
// @include	http://imdb.com/title/*
// @include	http://*.imdb.com/title/*
// @include	http://amazon.com/*
// @include	http://*.amazon.com/*
// @include http://*.mobygames.com/game/*
// @include http://mobygames.com/game/*
// @include http://www.gamefaqs.com/*
// @include http://www.the-underdogs.info/*
// @include http://www.gamespot.com/*
// @include http://*.ign.com/objects/*
// @include http://www.ebgames.com/product.asp?*
// @include       http://www.gamefly.com/products/*
// @include       http://www.lemon64.com/*
// @include       http://product.half.ebay.com/*
// ==/UserScript==

var Version = "1.6";

var icons = {
	allmovie : "data:image/gif;base64,R0lGODlhEAAQAKIAAJPH5S%2BPyQx7v1yq2f3%2B%2F7%2Fg8%2B32%2FN3t%2BCH5BAAAAAAALAAAAAAQABAAAANJKLrcriA8FwiYrZCDVzAEIXXDEV4dAIDH%2BAyXSQwYQANhMQXFEOAEA80xKKhWp4dtMQi1Gr8GaMb49SSBksFQ0HkCLrDY1WkkAAA7",
	blockbuster: 'data:image/gif;base64,R0lGODlhEAAQAKIAACMiNunXmvXRY9u4aGVJIKOFSRAibXF0fCH5BAAAAAAALAAAAAAQABAAAANjaLp7zgoQd2S4gJ0y7ijEIASZclyCGAyhIBBLyxYjQQsFI7JhALqDkmEgKtB4gZGQs0sRWgGYIrTbcUaVDIDIcxmBNgPg+rS6VgFTkeMjXN6mVOpCGIMAQq6xDtHUhX2BggYJADs=',

	noFavicon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAGHSURBVHjaYvz%2F%2Fz8DIyOjDAMDgzgQMzPgB6%2BA%2BBFQzz%2BYAEAAMYAMAALjHz9%2BfPj9%2B%2FffP3%2F%2B%2FMeGgfL%2Fp0yZMhGoVgGImUD6QBgggGAGmIE0f%2Fny5f%2FXr1%2Fh%2BOPHj%2F%2Ffv3%2F%2F%2F%2BXLl2BDnj9%2F%2Fn%2Fq1KkohgAE4GiOUQCAQhCA8u9%2Fgq7YFASGkx8dwkVfHj8DLtydSCZ9u6uZUXcHACA%2FqaogBr4AYoQaYApUcPLv379wr%2F379w%2FsOqA4A9BQBkFBQZTAYGFhMQdSpwECiAVZkImJCRSgcDbIQGZmZgY2NjYGoEvAhoIMFBMTg%2BsBCCAUA2CaYWyQZmQ%2BzEBkABBALPjiDNkQEBuEYQEHAwABxEIg3sGakL2GDgACiImBCADTDHINyDBkABBAOF2A7FRoagUHIjoACCCCXkA2CN12EAAIIBZ8zobZjA8ABBCyAf%2BhiQprtKKBf1DX%2FQcIIFB6Bmn8B4zj7zAJAgCk9huQBidbgABihDpTFpqdiYoVqOZXQL2PAQIMAIeX65Ph3kulAAAAAElFTkSuQmCC'
};

var Defaults = {
	xpath: "//*[@class='title']",
};

var SiteGroups = [
	["Information", ["mobygames", "gamefaqs", "imdb", "wikipedia", "gamespot", "ign"]],
	["Downloads", ["hotu"]],
	["Rentals", ["bb"]],
	["Critics", ["metacritic"]],
	["Sales", ["amazon", "ebgames", "goodwill", "ebay", "walmart"]]
];


// -- Site definitions
var Sites = {
	mobygames: {
		name: "Moby Games",
		icon: "http://www.mobygames.com/favicon.ico",
		scanURL: "mobygames.com",
		link: "http://www.mobygames.com/search/quick?q={search}",
		xpath: "//div[@id='gameTitle']"
	},

	gamefaqs: {
		name: "GameFaqs",
		icon: "http://www.gamefaqs.com/favicon.ico",
		scanURL: "gamefaqs.com",
		link: "http://www.gamefaqs.com/search/index.html?game={search}&platform=All+Platforms",
		xpath: "//div[@id='content']/h1"
	},

	gamespot: {
		name: "GameSpot",
		icon: "http://www.gamespot.com/favicon.ico",
		scanURL: "gamespot.com",
		link: "http://www.gamespot.com/search.html?type=11&stype=all&tagg=search;button&qs={search}",
		xpath: "//div[@id='content_wrap']//h1",

		processTitleNode: function(titleNode){
			var node = document.createElement("div");
			node.innerHTML = "<h3>"+$T(titleNode)+"</h3>";
			var parent = titleNode.parentNode.parentNode;
			parent.removeChild(titleNode.parentNode);
			parent.insertBefore(node, parent.firstChild);

			return node;
		},
	},

	gamefly: {
		name: "Game Fly",
		link: "http://www.gamefly.com/products/search.asp?k={search}&sb=alpha",
		icon: "http://www.gamefly.com/favicon.ico",
		scanURL: "gamefly.com",
		xpath: "//span[@class='producttitle']",
	},

	ign: {
		name: "IGN",
		icon: "http://media.ign.com/ign/favicon.ico",
		link: "http://search.ign.com/products?qtype=0&query={search}&objtName=game&sort=date&so=exact&ns=true&genNav=true",
		scanURL: "ign.com",
		xpath: "//div[@id='objectName']",

		processTitleNode: function(titleNode){
			var node = document.createElement("span");
			node.innerHTML = selectNode("text()",titleNode).nodeValue;
			titleNode.replaceChild(node, titleNode.firstChild);
			return node;
		}
	},

	hotu: {
		name: "Home of the Underdogs",
		icon: "http://www.the-underdogs.info/favicon.ico",
		scanURL: "underdogs.info",
		link: "http://www.the-underdogs.info/search.php?search_game={search}",
		xpath: "//div[@class='gameheader']"
	},

	goodwill: {
		name: "Shop Goodwill",
		icon: "http://shopgoodwill.com/favicon.ico",
		link: "http://shopgoodwill.com/search/searchKey.asp?itemTitle={search}&srchdesc=on&catID=7&sellerID=all&closed=no&minPrice=&maxPrice=&sortBy=itemEndTime&sortOrder=a&submit1=Search",
	},

	froogle: {
		name: "Froogle",
		icon: "http://froogle.google.com/favicon.ico",
		link: "http://froogle.google.com/froogle?q={search}",
	},

	ebay: {
		name: "eBay",
		icon: "http://cgi.ebay.com/favicon.ico",
		link: "http://video-games.search-desc.ebay.com/{search}_Video-Games_W0QQcatrefZC5QQfbdZ1QQfclZ3QQflocZ1QQfposZ27514QQfromZR14QQfrppZ50QQfsclZ1QQfsooZ1QQfsopZ1QQfssZ0QQftrtZ1QQftrvZ1QQftsZ2QQnojsprZyQQpfidZ0QQsaaffZafdefaultQQsacatZ1249QQsacqyopZgeQQsacurZ0QQsadisZ200QQsargnZQ2d1QQsaslcZ0QQsaslopZ1QQsofocusZbsQQsorefinesearchZ1",

	},

	half: {
		name: "Half.com",
		link: "http://search.half.ebay.com/{search}_W0QQmZvideoQ2dgames",
		icon: "http://cgi.ebay.com/favicon.ico",
//		validPage: function(pageTitle){return (pageTitle.indexOfAny(["Video Games"]) > -1);},
		xpath: "//b[@class='pagetitle']",
		scanURL: "half.ebay.com",
	},

	amazon: {
		name: "Amazon",
		xpath: "//b[@class='sans']",
		icon: "http://www.amazon.com/favicon.ico",
		form: ["http://amazon.com/exec/obidos/search-handle-form/102-1662869-1268955",
			 {"url": "index=videogames", "field-keywords": "*"}],
		scanURL:"amazon.com",
		validPage: function(pageTitle){return (pageTitle.indexOfAny(["ames"]) > -1);}
	},

	bb: {
		name: "Blockbuster",
		xpath: "//*[@class='headline1']",
		icon: icons.blockbuster,
		form: ["http://blockbuster.com/search/PerformKeyWordSearchAction.action",
			{schannel: "Games", keyword: "*", searchType:"Games"}],
		scanURL:"blockbuster.com",
	},

	imdb: {
		name: "IMDb",
		link: "http://imdb.com/find?q={search};tt=on;nm=on;mx=20",
		icon: "http://imdb.com/favicon.ico",
		xpath: Defaults.xpath,
		scanURL:"imdb.com",
		validPage: function(pageTitle){return (pageTitle.indexOfAny(["(VG)"]) > -1);}
	},

	metacritic: {
		name: "Metacritic",
		xpath: "//td[@id='rightcolumn']/h1",
		icon: "http://www.metacritic.com/favicon.ico",
		form: ["http://www.metacritic.com/search/process", {
				sort: "relevance", termtype: "all", ts: "*", ty: "3" }],
		scanURL:"metacritic.com",
	},

	ebgames: {
		name: "EBgames",
		icon: "http://www.ebgames.com/gs/sites/eb/common/images/favicon.ico",
		link: "http://www.ebgames.com/search.asp?sortby=default&searchtype=quicksearch&searchcount=12&Keyword={search}",
		scanURL: "ebgames.com",
		xpath: "//td[@class='prodtitle']"
	},

	walmart: {
		name: "Wal*Mart",
		link: "http://www.walmart.com/catalog/search-ng.gsp?search_constraint=2636&search_query={search}",
		icon: "http://www.walmart.com/favicon.ico",
		scanURL: "walmart.com",
		xpath: Defaults.xpath,
	},

	wikipedia: {
		name: "Wikipedia",
		link: "http://en.wikipedia.org/wiki/Special:Search?search={search}&go=Go",
		icon: "http://en.wikipedia.org/favicon.ico",
		xpath: Defaults.xpath,
	},

	allgame: {
		name: "All Game",
		icon: icons.allmovie,
		form: ["http://www.allgame.com/cg/agg.dll", {P: "agg", SRCH: "*", TYPE:"1"}],
		scanURL:"allgame.com",

		getTitle: function(title){return title;}
	},

	lemon64: {
		name: "Lemon 64",
		icon: "http://www.lemon64.com/favicon.ico",
		scanURL: "lemon64.com",
		link: "http://www.lemon64.com/games/list.php?type=title&name={search}",
		xpath: "//td[@class='normalheadblank']//b[@class='mediumhead']",
	}
};

var UserSites = {};

/*
	The Game Dude

	1.6 (in development)
	11 Sep 2006, 11:00pm

	Copyright (c) 2005-6, Adam Vandenberg
	Released under the GPL license
	http://www.gnu.org/copyleft/gpl.html
*/
// #region Class support
// Adds items from props to obj.
function _extend(obj, props){
	for(var key in props){obj[key]=props[key];}
}
// #endregion

// #region String support
_extend(String.prototype,{
	important: function(){ return this.replace(";", " !important;");},
	trim: function() { return this.replace(/^\s+|\s+$/g, ""); },
	template: function(vars){
		return this.replace(
			/\{(\w*)\}/g,
			function(match,submatch,index){return vars[submatch];});
	 },
	endsWith: function(suffix){
		var lastIndex = this.lastIndexOf(suffix);
		return (-1 < lastIndex) && (lastIndex == (this.length-suffix.length));
	},
	removeSuffix: function(suffix){
		return (this.endsWith(suffix))? this.substring(0, this.length-suffix.length) : this;
	},
	after: function(s){
		var index = this.indexOf(s);
		var length = s.length || 1;
		return (-1<index) ? this.substring(index+length) : this;
	},
	indexOfAny: function(charsOrStringList){
		var index=-1;
		var s = this;
		foreach(charsOrStringList, function(token){
			index = s.indexOf(token);
			if (-1 < index) return true;
		});
		return index;
	},
	escapeHTML: function(){
		return this
			.replace(/&/g, "&amp;")
			.replace(/\"/g, "&quot;")
			.replace(/</g, "&lt;")
			.replace(/>/g, "&gt;");
	}
});
// #endregion

// #region Collection support
function foreach(stuff, f){ for(var i=0; i < stuff.length; i++) if (f(stuff[i])) return; }
function foreach_dict(stuff, f){ for(var name in stuff) if ( f(name, stuff[name]) ) return; }
function collect(f, stuff) {
	var list = [];
	foreach(stuff, function(item){list.push(f(item))});
	return list;
}
//#endregion

// #region DOM & Events support
function $(o) {
	if (typeof(o) == "string") return document.getElementById(o)
	else return o;
}

function hide(id){
	var e = $(id);
	if (e) e.style.display = "none";
}

function addEvent(elementID, eventName, handler, b){
	var e = $(elementID);
	if (e) e.addEventListener(eventName, handler, b);
}

// Extract the text from the given DOM node.
function $T(node) {
	var aNode = $(node);
	if (aNode == null) return "";

	function extract(n){
		var s = "";
		if (n.nodeType == 3){
			s+= n.nodeValue;
		}

		foreach(n.childNodes, function(child){
			s += extract(child);
		});
		return s;
	}

	return extract(aNode).trim();
}
// #endregion

//#region Xpath Support
function selectNode(selector, rootElement){ return xpath(selector, rootElement, null, true); }
function xpath(selector, rootElement, f_each, firstOnly){
	var results = document.evaluate(
		selector, rootElement || document, null,
		XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);

	// If we have a callback function, run it on the results
	if (f_each != null){
		while(result = results.iterateNext()) { f_each(result); }
		return;
	}

	// If we're only getting the first result, do that
	if (firstOnly){ return results.iterateNext(); }
}
//#endregion

function stringify(){
	var q = [], output = [];
	foreach(arguments, function(item){q.unshift(item);});

	var item = null;
	while (q.length > 0){
		item = q.pop();

		if (typeof(item) == "function")
			q.unshift(item());
		else if ((typeof(item) != "string") && (item.length != null))
			foreach(item, function(item){q.unshift(item);});
		else if (item != null)
			output.push(item.toString());
	}
	return output.join("");
}
function Maker(label){
	return function(){
		var s =  "<"+label+">"+stringify(arguments)+"</"+label+">";
		return s; } }

var Html = {};
foreach(['table','tr','td'], function(item){Html[item]=Maker(item);})

function rowify(row /* array of cells */){
	return Html.tr(collect(Html.td, row));  }

function Table(rows){ return Html.table(collect(rowify,rows)); }

Html.Select = function (name, selectedValue, options){
	var s = '<select id="{name}">'.template({name: name});
	foreach(options, function(option){
		var selected = (option[0] == selectedValue)?' selected="selected"':"";
		s += '<option value="{value}"{selected}>{caption}</option>'.template({value: option[0], caption: option[1], selected: selected});
	});
	s += "</select>";
	return s;
}

function addCSS(){
	for(var i=0;i<arguments.length;i++) GM_addStyle(arguments[i]);
}

function addImportantCSS(){
	for(var i=0;i<arguments.length;i++) GM_addStyle(arguments[i].important);
}

function NameValue(n,v){ return ' {n}="{v}"'.template({n:n,v:v}); }
function FormValue(n,v){ return '<input type="hidden" name="{n}" value="{v}" />'.template({n:n,v:v}); }

/// Creates new Site objects.
function Site(definition, key){
	_extend(this, definition);
	this.id = key;
}

_extend(Site.prototype, {
	icon: icons.noFavicon,
	link: "{form}",
	validPage: function(pageTitle){return true;},

	GetForm: function(movieTitle){
		if (this.form == null)
			return '';

		var s = '<form method="post" style="display:none;" action="{action}" id="_md_{id}_search">'.
			template ({
				action: this.form[0],
				id: this.id
			});

		foreach_dict(this.form[1], function(k,v){s += FormValue(k, v=="*" ? movieTitle : v);} );
		s += "</form>";
		return s;
	},

	GetLink: function(movieTitle){
		var link = this.link.template( {
				search: encodeURIComponent(movieTitle),
				form: "javascript:document.forms['_md_"+this.id+"_search'].submit()"
			} );

		var html = this.LinkTemplate.template( {
			href: link,
			name: this.name.replace(/\s/, "&nbsp;"),
			icon: this.icon
		});

		return html;
	},

	GetHTML: function(movieTitle){return this.GetLink(movieTitle) + this.GetForm(movieTitle);},

	processTitleNode: function(titleNode){return titleNode;},
});

function setPreference(event){GM_setValue(this.value, this.checked);}

function removeBrackets(movieName){
	do {
		var bracketIndex = movieName.indexOfAny("([");
		if (-1 < bracketIndex) movieName = movieName.substring(0,bracketIndex).trim();
	} while (bracketIndex != -1)

	return movieName;
}

function removeSuffix(movieName){
	foreach([" - Criterion Collection", " - Used"], function(suffix){
		if (movieName.endsWith(suffix)){
			movieName = movieName.removeSuffix(suffix).trim();
			return true;
		}
	});

	return movieName;
}


function ShowPreferences(){
	var prefs = $("_md_prefs");
	if(!prefs){
		prefs = document.createElement("div")
		prefs.id = "_md_prefs";

		var s = "<div>";
		s += "<b>The Game Dude</b><br />"
		s += "Version <font color='#336699'>1.6 (in development)</font> &bull; ";
		s += "<a href='1.6 (in development)'>Home Page</a> &bull; <a href='Movie.Dude.Script@gmail.com'>Contact</a><br />";
		s += "<br />";

		s += "<b>Display as:</b> ";
		s += Html.Select("_md_display_type", GM_getValue("linkStyle", "0"),
			[["0", "Text &amp; Icons"],["1", "Icons only"],["2","Text only"]]);

		s += '<br />';
		s += "<b>Show links to:</b><br />";
		var linkStyle = GM_getValue("linkStyle", "0");

		var table = [];
		foreach(SiteGroups, function(group){
			var row = [];
			row.push("<font color='#336699'>"+group[0] + ":</font>");
			foreach(group[1], function(siteID){
				var siteName = Sites[siteID].name;
				var checked = (GM_getValue(siteID, true)) ? " checked='checked'" : "";

				row.push( "&nbsp;<input type='checkbox' name='_md_pref' value='{siteID}'{checked} />{siteName} ".template({siteID: siteID, checked: checked, siteName: siteName}));
			});
			table.push(row);
		});

		s += Table(table);
		s += "<br /><button id='_md_close'>Close</button>"
		s += "</div>"
		prefs.innerHTML = s;
		document.body.appendChild(prefs);

		addEvent("_md_close", "click", function (e){ hide("_md_prefs");});
		addEvent("_md_display_type", "change", function(e){
			var select = $("_md_display_type");
			GM_setValue("linkStyle", select.options[select.selectedIndex].value);
		});
		xpath("//input[@name='_md_pref']", prefs, function(box){addEvent(box, "click", setPreference);} );
	}

	prefs.style.display="";
}

function getSiteBeingViewed(){
	var whichSite = null;
	foreach_dict(Sites, function(key, site){
		if ( -1 < location.host.indexOf(site.scanURL)){
			whichSite = site;
			return true;
		}
	});

	return (whichSite && whichSite.validPage(document.title)) ? whichSite : null;
}

// -- Main code
function LinkEmUp(){
	// Convert site definitions to site objects.
	foreach_dict(Sites, function(key, def){
		Sites[key] = new Site(def, key);
	});

	// Add any user supplied sites.
	foreach_dict(UserSites, function(key, def){
		Sites[key] = new Site(def, key);
	});

	var whichSite = getSiteBeingViewed();
	if (whichSite == null) return;

	var LinkTemplates = [
	'&bull;&nbsp;<a href="{href}" title="{name}">{name}</a>&nbsp;<a href="{href}" title="{name}"><img src="{icon}" width="16" height="16" border="0" /></a> ',
	'<a href="{href}" title="{name}"><img src="{icon}" width="16" height="16" border="0" /></a> ',
	'&bull;&nbsp;<a href="{href}" title="{name}">{name}</a> ',
	];

	Site.prototype.LinkTemplate = LinkTemplates[parseInt(GM_getValue("linkStyle", "0"))];

	var movieName = "";
	var titleNode = null;

	if (whichSite.xpath)
	{
		titleNode = selectNode(whichSite.xpath);
		if (titleNode != null) {
			titleNode = whichSite.processTitleNode(titleNode);
			movieName = $T(titleNode);
		}
		else return; // abort if the xpath gave us nothing
	}
	else
	{
		movieName = document.title;
		titleNode = document.createElement("div");
		document.body.insertBefore(titleNode, document.body.firstChild);
	}

	if (whichSite.getTitle)
		movieName = whichSite.getTitle(movieName);

	movieName = removeBrackets(movieName);
	movieName = removeSuffix(movieName);
	movieName = movieName.trim();

	var s = "<a id='_md_config' class='_md_config' title='Configure Game Dude'>The Game Dude</a>: ";

	foreach_dict(Sites, function(key, site){
		if ((site == whichSite) || !GM_getValue(site.id, true))
			return false;

		s += site.GetHTML(movieName);
	});

	titleNode.innerHTML += ( "<span id='_md_links'><br />" + s + "</span>");
	addCSS(
"#_md_links, #_md_prefs, #_md_links a {font-size:10pt;font-weight:normal;text-transform: none;}",
"#_md_prefs_link {cursor: pointer;}",
"a._md_config { font-size:10pt;font-weight:normal;text-transform: none; text-decoration: none; cursor: pointer; color: black;}",
"a._md_config:hover { background: #336699; color: white; cursor: pointer;}",
"#_md_prefs {position:fixed; bottom:auto; left:0; right:0; top:0; color:black; font: normal 11px sans-serif; background:#eee; border-bottom:2px #69c solid;}",
"#_md_prefs, #_md_prefs td {font-family: verdana, sans-serif; font-size: 10pt;}",
"#_md_prefs div {padding:5px 0 0.4em 0; margin: 0px auto;width: 700px;}",
"#_md_prefs button {font: normal 11px sans-serif; border: 1px solid #0080cc; color: #333; cursor: pointer; background: #FFF;}"
);

	addEvent("_md_prefs_link", "click", ShowPreferences);
	addEvent("_md_config", "click", ShowPreferences);
}

GM_registerMenuCommand("Game Dude Settings...", ShowPreferences);
LinkEmUp();