// ==UserScript==
// @name           Pagerization
// @namespace      http://userjs.0fk.org
// @description    Add autoloading for next page to 'ALLPAGE'.  DblClick to enable/disable it.
// @include        *
// @version        0.0.6.2
// ==/UserScript==
// Released under the GPL license
//  http://www.gnu.org/copyleft/gpl.html
// Thank you for
//  ma.la    ( http://userscripts.org/scripts/show/1392 )
//  FaziBear ( http://userscripts.org/scripts/show/6985 )
//  swdyh    ( http://d.hatena.ne.jp/swdyh/20070414/1176508836 )
// 2007-02-20
//   version 0.0.1 release
// 2007-02-24
//   version 0.0.2 release
// 2007-03-03
//   version 0.0.3 release
// 2007-04-13
//   version 0.0.4 release
// 2007-04-13
//   version 0.0.4.1 release
// 2007-04-14
//   version 0.0.5 release
// 2007-04-14
//   version 0.0.5.1 release
// 2007-04-14
//   version 0.0.5.2 release
// 2007-04-14
//   version 0.0.5.3 release
// 2007-04-16
//   version 0.0.6 release
// 2007-04-17
//   version 0.0.6.1 release
// 2007-04-17
//   version 0.0.6.2 release

(function () {

/*----------------------------------------------------------------------------
 * Option
 *--------------------------------------------------------------------------*/

// Auto starting
var ENABLE = true;

// cache limited (unit: day)
var EXPIRE = 1;

// your langage (ALL CHARCTER IS LOWERCASE)
var LANGUAGE = navigator.language.toLowerCase();

// list download site
// It's given priority above URL.
var SITEINFO_URL = [
	'http://userjs.oh.land.to/pagerization/siteinfo.v4.txt#function',
//	'http://swdyh.infogami.com/autopagerize',
];

// langage download site
// It's given priority above URL.
var LANGUAGE_URL = [
	'http://userjs.oh.land.to/pagerization/language.v3.txt',
];


// personal rules
// Let's write only your original data referring to http://userjs.oh.land.to/pagerization/siteinfo.v4.txt .

var PERSONAL_RULES = <><![CDATA[
[ Google Search ]
url:          http://www.google.*/search*
nextLink:     id("navbar")/table/tbody/tr/td[last()]/a
insertBefore: id("navbar")
pageElements: //div[2]/*[(self::div[@class="g"] or self::p)]
remainHeight: 800

[ Google Image Search ]
url:          http://images.google.*/images*
nextLink:     function (node) {
	var n = $S('id("nn")/..', node);
	return n ? n.href.replace(/start=\d*/,'start=' + ($X('count(//div[@id="ImgContent"]//td)') / 2)) : false;
}|id("nn")/..
insertBefore: id("navbar")
pageElement:  /<\/form><\/td><\/tr><\/table><br>((?:.|\n)*?)<br clear=all><div id=navbar/
remainHeight: 800
faceFunction: function (node, self) {
	$E('//A[contains(@href,"/imgres?imgurl=")][contains(@href,"&imgrefurl=")]', $S('//div[@id="ImgContent"]', node)).forEach(function (node) {
		var messageImage = self.lang.originImage || 'Origin image';
		var messagePage  = self.lang.originPage  || 'Origin page';
		var target = node.target || '';
		var originImage = node.href.match(/\/imgres\?imgurl\=(.*?)\&imgrefurl\=/) ? decodeURIComponent(RegExp.$1) : null;
		var originPage  = node.href.match(/\&imgrefurl\=(.*?)\&h=/) ? decodeURIComponent(RegExp.$1) : null;
		if (originImage) {
			node.href = originImage;
			var links = [
				'<font size="-2">[ ',
				'<a class="fl" href="', originImage, '" target="', target, '">', messageImage, '</a>',
			];
			if (originPage)
				links.push(' | ', '<a class="fl" href="', originPage, '" target="', target, '">', messagePage, '</a>');
			links.push(' ]</font>');
			node.parentNode.appendChild($C('div', {}, links.join('')));
		}
	});
}

[ Google News ]
url:          http://news.google.*/news*
nextLink:     id("navbar")/table/tbody/tr/td[last()]/a
insertBefore: id("navbar")
pageElement:  //div[@class="mainbody"]
remainHeight: 800

[ Google Groups ]
url:          http://groups.google.*/groups/dir*
nextLink:     //img[@src="/groups/img/nav_next.gif"]/..
insertBefore: id("bottom_marker")
pageElements: //div[2]/p
remainHeight: 800

[ Google Video ]
url:          http://video.google.*/videosearch*
nextLink:     id("nextpage")
insertBefore: id("pagenavigatortable")
pageElements: id('resultsdiv')/*[not(@id="pagenavigatortable")]
remainHeight: 800

[ Yahoo! ]
url:          http://search.yahoo.com/search*
nextLink:     id("yschnxtb")/big/a
insertBefore: id("yschpg")
pageElement:  id("yschweb")
remainHeight: 800

[ Wikipedia ]
url:          http://(.*).wikipedia.org/w/index.php*
nextLink:     id('bodyContent')/div/a[last()][child::span]
insertBefore: id('bodyContent')/ul/following-sibling::node()[1]
pageElement:  id('bodyContent')/ul
remainHeight: 800

[ digg.com ]
url:          http://(www.)?digg.com*
nextLink:     //div[@class="pages"]/a[last()][@class="nextprev"]
insertBefore: //div[@class="pages"]
pageElement:  //div[@class="main"]
remainHeight: 800

[ del.icio.us ]
url:          http://del.icio.us/*
nextLink:     //a[@accesskey="e"]
insertBefore: id("main")/p[2]
pageElement:  id("main")/ol
remainHeight: 1000

[ YouTube ]
url:          http://www.youtube.com/results*
nextLink:     id("mainContentWithNav")/div[1]/a[last()][text()="Next"]
insertBefore: id("mainContentWithNav")/div[3]
pageElement:  id("mainContentWithNav")/div[2]
remainHeight: 800

[ Twitter ]
url:          http://twitter.com/*
nextLink:     //div[@class="pagination"]/a[last()]
insertBefore: //div[@class="pagination"]
pageElement:  //table[@class="doing"]
remainHeight: 400

]]></>;


/*----------------------------------------------------------------------------
 * Pagerization
 *--------------------------------------------------------------------------*/

var Pagerization = function (info, lang, state) {
	var self = this;

	// setting
	if (!info.mineType && info.charset) {
		info.mineType = 'text/plain; charset=' + info.charset;
	}

	if (info.faceFunction && isFunction(info.faceFunction)) {
		var faceFunction = Pagerization.function(info.faceFunction);
		this.faceFunction = function (nodes) {
			for (var i = 0, j = nodes.length; i < j; ++i) {
				faceFunction(nodes[i], self);
			}
		};
	}
	info.remainHeight = info.remainHeight || 800;

	var html = document.getElementsByTagName('html')[0].innerHTML;
	if (info.insertBefore) {
		this.setPoint = isFunction(info.insertBefore)
			? info.insertBefore(document, html, this)
			: $S(info.insertBefore);
	}
	else {
		this.setPoint = isFunction(info.appendChild)
			? info.appendChild(document, html, this)
			: $S(info.appendChild);
	}

	this.page = 1;
	this.info = info;
	this.lang = lang;
	this.state = state;

	// check
	if (this.faceFunction) {
		var page = this.getPageElements(document, html, this);
		if (page.length == 0)
			page = [document];
		this.faceFunction(page, this);
	}
	this.requested = location.href.replace(/#.*$/, '');
	this.requestUrl = this.getNextLink(document, html, this);

	if (!this.setPoint || !this.requestUrl) {
		return false;
	}

	// init
	this.message = new StatusBalloon();

	if (this.state) {
		this.enable();
	}
	else {
		this.message.change(lang.supported, '#ffc514');
		this.message.element.style.visibility = 'visible';
	}

	// add event
	this.bindToggle = function () {
		if (self.state)
			self.disable.call(self);
		else
			self.enable.call(self);
	};
	document.body.addEventListener('dblclick', this.bindToggle, true);

	// alternative onscroll < very slow
	(function () {
		var result = self.scroll.call(self);
		if (!result) {
			self.remove.call(self);
			return;
		}
		setTimeout(arguments.callee, 150);
	})();
};

Pagerization.prototype = {
	enable: function () {
		this.message.change(this.lang.standing, '#00681c');
		this.state = true;
		this.message.element.style.visibility = 'visible';
	},
	disable: function () {
		this.state = false;
		this.message.element.style.visibility = 'hidden';
	},
	remove: function () {
		this.message.change(this.lang.terminate, '#0075be');
		document.body.removeEventListener('dblclick', this.bindToggle, true);
		this.message.remove();
	},
	scroll: function () {
		var remain = document.documentElement.scrollHeight - window.innerHeight - window.scrollY;
		return (this.state && remain < this.info.remainHeight) ? this.request() : true;
	},
	request: function () {
		if (!this.requestUrl || this.requested == this.requestUrl)
			return this.requestUrl;
		this.requested = this.requestUrl;
		this.message.change(this.lang.loading, '#790619');
		var self = this;
		GM_xmlhttpRequest({
			method: 'GET',
			url: self.requestUrl,
			overrideMimeType: self.info.mimeType ? self.info.mimeType : null,
			onload: function (res) {
				// DOM
				var responseHTML = parseHTML(res.responseText);
				var p = document.createElement('p');
				    p.innerHTML = '<a href="' + self.requestUrl + '" style="border:0;text-decoration:none">page: ' + (++self.page) + '<\/a>';
				self.setElements([document.createElement('hr'), p]);
				var elm = self.getPageElements(responseHTML, res.responseText, self);
				var state = self.setElements(elm);
				if (self.faceFunction && elm) {
					self.faceFunction(elm, self);
				}
				// serch next page
				self.requestUrl = state ? self.getNextLink(responseHTML, res.responseText, self) : false;
				// terminate
				self.message.change(self.lang.standing, '#00681c');
			}
		});
		return true;
	},
};

Pagerization.function = function (func) {
	var m = func.toString().match(/^function\s+(?:.*?)\s*\(((?:[\n\r]|.)*?)\)\s*{((?:[\n\r]|.)*?)}$/);
	return new Function(m[1], $C.toString() + $E.toString() + $S.toString() + $X.toString() + m[2]);
}

Pagerization.starter = function (info, lang, state) {
	var P = Pagerization.prototype;

	if (info.nextLink) {
		P.getNextLink =
			isFunction(info.nextLink) ?
				Pagerization.function(info.nextLink)
			: isRegExp(info.nextLink) ?
				function (node, text) {
					return text.match(this.info.nextLink) ? parseRelativeURL(this.requestUrl || location.href, RegExp.$1) : false;
				}
			: // isString
				function (node, text) {
					node = $S(this.info.nextLink, node);
					return (
						!node       ? false :
						node.href   ? node.href :
						node.action ? node.action : false
					);
				};
	}
	else {
		return false;
	}

	if (info.pageElements) {
		P.getPageElements =
			isFunction(info.pageElements) ?
				Pagerization.function(info.pageElements)
			: isRegExp(info.pageElements) ?
				function (node, text) {
					var m = text.match(this.info.pageElements), res = [];
					if (m) {
						for (var i = 1, j = m.length; i < j; ++i) {
							var o = document.createElement('div');
							    o.innerHTML = m[i];
							res.push(o);
						}
					}
					return res;
				}
			: // isString
				function (node, text) {
					return $E(this.info.pageElements, node);
				};
	}
	else if (info.pageElement) {
		P.getPageElements =
			isFunction(info.pageElement) ?
				function (node, text, self) {
					var res = Pagerization.function(this.info.pageElement)(node, text, self);
					return res ? [res] : [];
				}
			: isRegExp(info.pageElement) ?
				function (node, text) {
					var m = text.match(this.info.pageElement);
					if (m) {
						var o = document.createElement('div');
						    o.innerHTML = m[1];
						return [o];
					}
					return [];
				}
			: // isString
				function (node, text) {
					var res = $S(this.info.pageElement, node);
					return res ? [res] : [];
				};
	}
	else {
		return false;
	}

	if (info.insertBefore) {
		P.setElements = function (nodes) {
			if (nodes.length > 0) {
				var point = this.setPoint;
				for (var i = 0, j = nodes.length; i < j; ++i)
					point.parentNode.insertBefore(nodes[i], point);
				return true;
			}
			return false;
		};
	}
	else if (info.appendChild) {
		P.setElements = function (nodes) {
			if (nodes.length > 0) {
				for (var i = 0, j = nodes.length; i < j; ++i)
					this.setPoint.appendChild(nodes[i]);
				return true;
			}
			return false;
		};
	}
	else {
		return false;
	}

	return new Pagerization(info, lang, state);
};


/*----------------------------------------------------------------------------
 * StatusBalloon
 *--------------------------------------------------------------------------*/

var StatusBalloon = function () {
	var div = $C('div', {
		style: 'margin:0;padding:0.2em;border:0;position:fixed;right:1px;bottom:1px;z-index:999999;visibility:hidden;color:#fff;line-height:1.2em;font-weight:bold;font-size:12px;'
	});
	document.body.appendChild(div);
	this.element = div;
};

StatusBalloon.prototype = {
	change: function (text, color) {
		this.element.textContent = text;
		this.element.style.background = color;
	},
	remove: function () {
		var element = this.element;
		setTimeout(function () {
			element.parentNode.removeChild(element);
		}, 1500);
	}
};


/*----------------------------------------------------------------------------
 * Cache
 *--------------------------------------------------------------------------*/

var Cache = function (url) {
	this.url = url;
	this.crc16 = {
		
	};
	for (var i = 0, j = url.length; i < j; ++i) {
		this.crc16[url[i]] = crc16(url[i].replace(/#.*$/, ''));
	}
	this.deadline = GM_getObject('cache-deadline');
};

Cache.prototype = {
	get: function (url) {
		return GM_getObject('cache-' + this.crc16[url]);
	},
	update: function (clear) {
		var url = [].concat(this.url), crc16 = this.crc16, deadline = this.deadline;
		var update = function () {
			if (url.length == 0) {
				GM_setObject('cache-deadline', deadline);
				return;
			}
			var requestUrl = url.shift();
			var crc16url = crc16[requestUrl];
			var opt = !!requestUrl.match(/#function$/);
			if (!clear && deadline[crc16url] && deadline[crc16url] > (new Date()).getTime())
				update();
			else
				GM_xmlhttpRequest({
					method: 'GET',
					url: requestUrl.replace(/#.*$/, '') + '?' + (new Date()).getTime(),
					overrideMimeType: 'text/plain; charset=UTF-8',
					onload: function (res) {
						GM_setObject('cache-' + crc16url, parseOption(supportAutopagerize(res.responseText), opt));
						deadline[crc16url] = (new Date()).getTime() + EXPIRE * 24 * 60 * 60 * 1000;
						update();
					},
					onerror: update
				});
		};
		update();
	}
};


/*----------------------------------------------------------------------------
 * Initialize
 *--------------------------------------------------------------------------*/

var autopage = false;
var database = new Cache(SITEINFO_URL.concat(LANGUAGE_URL));

/*
 * Language
 */

var message = {
	standing:  "Standing",
	loading:   "Loading",
	terminate: "Terminate",
	supported: "Supported",
	update:    "Cache Update"
};

var overwriteLanguage = function (lang) {
	for (var i = 0, j = LANGUAGE_URL.length; i < j; ++i) {
		var data = database.get(LANGUAGE_URL[i]);
		if (data) {
			if (data.hasOwnProperty(lang)) {
				for (var k in data[lang])
					message[k] = data[lang][k];
				return true;
			}
			else if (data.hasOwnProperty('en')) {
				for (var k in data['en'])
					message[k] = data['en'][k];
			}
		}
	}
	return false;
};

while (!overwriteLanguage(LANGUAGE.toLowerCase()) && LANGUAGE.indexOf('-') > 0)
	LANGUAGE = LANGUAGE.replace(/\-[^\-]*$/, '');


/*
 * Siteinfo
 */

var startpager = function (rules, addRules) {
	for (var i in rules) {
		if (location.href.match(i)) {
			if (addRules[i]) {
				for (var j in addRules[i])
					rules[i][j] = addRules[i][j];
			}
			autopage = Pagerization.starter(rules[i], message, ENABLE);
			if (autopage)
				return;
		}
	}
};

if (!PERSONAL_RULES)
	PERSONAL_RULES = {};
else if (typeof PERSONAL_RULES != 'object')
	PERSONAL_RULES = parseOption(PERSONAL_RULES, true);

startpager(PERSONAL_RULES, {});
for (var i = 0, j = SITEINFO_URL.length; i < j && !autopage; ++i) {
	startpager(database.get(SITEINFO_URL[i]), PERSONAL_RULES);
}

if (!autopage) {
	database.update(false);
}

GM_registerMenuCommand('Pagerization - ' + message.update, function () {
	database.update(true);
});


/*----------------------------------------------------------------------------
 * Functions
 *--------------------------------------------------------------------------*/

function parseRelativeURL(aUrl, rUrl) {
	if (rUrl.match(/^https?:\/\//))
		return rUrl;
	aUrl = aUrl.replace(/\/[^\/]*$/, '');
	rUrl = rUrl.replace(/\/\//g, '/');
	if (rUrl.charAt(0) == '/')
		return aUrl.match(/^https?:\/\/[^\/]*/)[0] + rUrl;
	while (rUrl.charAt(0) == '.') {
		if (rUrl.substring(0, 3) == '../')
			aUrl = aUrl.replace(/\/[^\/]*$/, '');
		rUrl = rUrl.replace(/^\.*\//, '');
	}
	return aUrl + '/' + rUrl;
}

function parseHTML(str) {
	var res = document.implementation.createDocument(null, 'html', null),
	    range = document.createRange();
	    range.setStartAfter(document.body);
	res.documentElement.appendChild(range.createContextualFragment(
		str.replace(/^([\n\r]|.)*?<html.*?>|<\/html>([\n\r]|.)*$/ig, '')
	));
	return res;
}

function parseOption(str, opt) {
	var res = {}, data = trim(str).split(/\n{2,}/).map(function (str) {
		return str.split(/\s*\n\s*([a-zA-Z0-9]+)\s*:\s*/);
	});
	for (var i = 0, j = data.length; i < j; ++i) {
		var name = data[i].shift(), tmp = {}, m;
		if (m = name.match(/\[\s*(.+?)\s*\]/)) {
			name = escape(m[1]);
			for (var k = 0, l = data[i].length; k < l; k += 2)
				tmp[trim(data[i][k])] = toObject(trim(data[i][k + 1]), opt);
			if (tmp['url']) {
				name = tmp['url'];
				delete tmp['url'];
			}
			if (!res[name])
				res[name] = tmp;
		}
	}
	return res;
}

function supportAutopagerize(str) {
	if (str.charAt(0) == '[')
		return str;
	var res = [];
	while(/<h[1-6]>([^>]*)<\/h[1-6]>(?:[\n\r]|.)*?<textarea[^>]*>((?:[\n\r]|.)*?)<\/textarea>/ig.exec(str))
		res.push('[' + trim(RegExp.$1) + ']\n' + trim(RegExp.$2).replace(/<\/?em>/g, '_'));
	return res.join('\n\n');
}

function trim(str) {
	return str.replace(/^[\s\t\n\r]+|[\s\t\n\r]+$/g, '');
}

function crc16(str) {
	var table = new Array(256);
	for (var i = 0; i < 256; ++i) {
		var c = i;
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		c = (c & 1) ? (0x8408 ^ (c >>> 1)) : (c >>> 1);
		table[i] = c;
	}
	var crc = 0xFFFF;
	for (var i = 0, j = str.length; i < j; ++i) {
		var n = str.charCodeAt(i);
		var a = new Array;
		do {
			a[a.length] = n & 0xFF;
		} while (n >>>= 8);
		for (var k = 0, l = a.length; k < l; ++k)
			crc = table[(crc ^ a[k]) & 0xFF] ^ (crc >>> 8);
	}
	return crc;
}

function GM_setObject(name, obj) {
	GM_setValue(name, obj.toSource());
}

function GM_getObject(name, value) {
	return eval.call(window, GM_getValue(name, (value || {}).toSource()));
}

function toObject(v, o) {
	var m;
	if (v.match(/^[0-9]+$/))
		return parseInt(v);
	if (m = v.match(/^\/(.+?)\/$/))
		return new RegExp(m[1], 'i');
	if (m = v.match(/^function\s*\(((?:[\n\r]|.)*?)\)\s*{((?:[\n\r]|.)*?)}(?:\|(.*))?$/))
		return o ? new Function(m[1], m[2]) : m[3] ? toObject(m[3], false) : '';
	return v;
}

function isFunction(v) {
	return typeof v == 'function' && typeof v.call == 'function';
}

function isRegExp(v) {
	return typeof v == 'function' && typeof v.call == 'undefined';
}

function isString(v) {
	return typeof v == 'string';
}

function $C(name, attr, childs) {
	var res = document.createElement(name);
	for (var k in attr)
		res.setAttribute(k, attr[k]);
	if (childs) {
		if (typeof childs == 'string')
			res.innerHTML = childs;
		else
			for (var i = 0, j = childs.length; i < j; ++i)
				res.appendChild(typeof childs[i] == 'string' ? document.createTextNode(childs[i]) : childs[i]);
	}
	return res;
}

function $E(xpath, context) {
	context = context || document;
	var iterator = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null), res = [];
	for (var node = iterator.iterateNext(); node; node = iterator.iterateNext())
		res.push(node);
	return res;
}

function $S(xpath, context) {
	context = context || document;
	return document.evaluate(xpath, context, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function $X(xpath, context) {
	context = context || document;
	var result = document.evaluate(xpath, context, null, XPathResult.ANY_TYPE, null);
	switch (result.resultType) {
		case XPathResult.STRING_TYPE : return result.stringValue;
		case XPathResult.NUMBER_TYPE : return result.numberValue;
		case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
		case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: return $E(xpath, context);
	}
	return null;
}

})();
