// ==UserScript==
// @name          google with wikipedia
// @namespace     http://c--v.net/
// @include       http://www.google.*/search?*
// ==/UserScript==
//
// Mail: ryosuke a sekido dot info
// Home: http://c--v.net/
// License: Creative Commons by 2.1 Japan
//
(function() {
	// Google の検索キーワードを取得する
	var keyword = getSearchKeyword();

	// 検索対象ではない
	if(keyword == null) return;

	// Wikipedia の記事を検索する
	document.GM_wikiExp = 'searching now...';
	document.GM_search  = keyword;
	searchWikipedia(keyword);

	// Google 検索結果の表示窓を作る
	window.addEventListener('load', makeWikipediaFrame, true);

	// -------------------------------------------
	// Wikipedia の記事を検索する
	function searchWikipedia(keyword) {
		// 検索語と Wikipedia の URL を控える
		changeKeyword(keyword);

		// Wikipedia 解説を取得する
		GM_xmlhttpRequest({ method: 'GET', url: document.GM_wikiUrl, onload: wikipediaOnLoad });
	}

	function changeKeyword(keyword) {
		document.GM_keyword = keyword;
		document.GM_wikiUrl = 'http://ja.wikipedia.org/wiki/' + keyword.replace(/\s/g, '_');
	}

	// ------------------------------------------
	// Google の検索キーワードを取得する
	function getSearchKeyword() {
		// ?...&q=keyword&...
		document.location.toString().match(/\Wq=([^&]+)/);

		// デコード
		var keyword = unescape(decodeURI(RegExp.$1));

		// : か " が含まれていたら Wikipedia から検索しない
		if(keyword.match(/:/) || keyword.match(/\"/)) return null;

		// スペース区切りを戻す
		keyword = keyword.replace(/\+/g, ' ').replace(/\s+$/g, '');

		return keyword;
	}

	// ------------------------------------------
	// Wikipedia 解説取得の onLoad イベントリスナ
	function wikipediaOnLoad(e) {
		// 解説を抽出する
		var explanation = parseWikipedia(e.responseText);

		// 解説が見つかった
		if(explanation != null) {
			addExplanation(explanation);
		}

		// 解説なんて無いよ＞＜
		else {
			// キーワードをスペース区切りで短くしてみる
			if(document.GM_keyword.indexOf(' ') > -1) {
				var nextKeyword = document.GM_keyword.substring(0, document.GM_keyword.lastIndexOf(' '));
				addExplanation("searching " + nextKeyword + " now...");
				searchWikipedia(nextKeyword);
			}

			// ほんとに無いよ＞＜
			else {
				changeKeyword(document.GM_search);
				addExplanation("Not Found");
			}
		}
	}

	// ------------------------------------------
	// 解説を抽出する
	function parseWikipedia(text) {
		// Wikipedia 文書内のさいだい検索範囲
		// 大きいと処理が重くなる
		var maxSearchLine = 100;
		text = text.split("\n");

		// 全文探す
		var tmpTitle = "";
		for(var i=0; i<text.length; i++) {
			if(text[i].match(/<h1[^>]*>(.*)<\/h1>/)) {
				tmpTitle = RegExp.$1;
				continue;
			}

			// @id = bodyContent 以下、さいしょの p タグが必要な解説
			if(text[i].match(/id="bodyContent"/)) {
				// 適当に連結
				var bodyContent = "";
				for(var j=i+1; j<i+maxSearchLine; j++) {
					bodyContent += text[j];
				}

				// p タグ内を抜き出す
				if(bodyContent.match(/<p>(.*?)<\/p>(<(ol|ul)>(.*?)<\/(ol|ul)>)?/m)) {
					var expl = RegExp.$1 + RegExp.$4;
					changeKeyword(tmpTitle);
					return expl;
				}

				// p タグが無くても探さない
				break;
			}
		}

		// 解説なんて無いよ＞＜
		return null;
	}

	// ------------------------------------------
	// Wikipedia 解説用の要素を追加する
	function makeWikipediaFrame() {
		var wrapper = document.createElement('li');
		wrapper.setAttribute('id', 'wp_result');
		wrapper.setAttribute('class', 'g');
		wrapper.setAttribute('style', 'background-color: #EEEEEE');
		wrapper.innerHTML = '<h3 class="r"><a class="l" href="' + document.GM_wikiUrl + '">Definition in Wikipedia: <em id="wp_result_word">' + document.GM_search + '</em></a></h3>' + 
							'<div class="s"><span id="wp_result_text">' + document.GM_wikiExp + '</span><br />' +
							'<cite id="wp_result_url">' + document.GM_wikiUrl.substr(7) + '</cite><span class="gl"> - google with wikipedia</span></div>';

		// 検索結果に追加する
		if(getNodeSnapshot('//li[@id = "wp_result"]').snapshotLength == 0) {
			var googleResult = getNodeSnapshotFirst('//body//li[@class = "g"]');
			googleResult.parentNode.insertBefore(wrapper, googleResult);
		}
	}

	// ------------------------------------------
	// Google の検索結果に追加する
	function addExplanation(text) {
		// URL を絶対 URL に変換する
		text = text.replace(/\/wiki/g, 'http://ja.wikipedia.org/wiki');

		// 検索結果に追加する
		if(changeMessage(text)) {
			getNodeSnapshotFirst('//li[@id = "wp_result"]//a').href = document.GM_wikiUrl;
			getNodeSnapshotFirst('//em[@id = "wp_result_word"]').innerHTML = document.GM_keyword;
			getNodeSnapshotFirst('//cite[@id = "wp_result_url"]').innerHTML = document.GM_wikiUrl.substr(7);
		}
	}

	function changeMessage(text) {
		var wpResultText = getNodeSnapshot('//span[@id = "wp_result_text"]');
		if(wpResultText.snapshotLength == 1) {
			wpResultText.snapshotItem(0).innerHTML = text;
			document.GM_wikiExp = text;
			return true;
		} else {
			document.GM_wikiExp = text;
			return false;
		}
	}

	// ------------------------------------------
	// XPath でノードを取得する
	function getNodeSnapshot(xpath) {
		return document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	}

	function getNodeSnapshotFirst(xpath) {
		return getNodeSnapshot(xpath).snapshotItem(0);
	}
})();
