// ==UserScript==
// @name		   AutoPagerize [Modified by BladeMight]
// @namespace	  http://swdyh.yu.to/
// @description	loading next page and inserting into current page. Если на Яндекс не сработало, обновить страницу. Google листает до 4 страниц…
// @include		http://*
// @include		https://*
// @exclude		https://mail.google.com/*
// @exclude		http://b.hatena.ne.jp/*
// @exclude		http://www.facebook.com/plugins/like.php*
// @exclude		http://api.tweetmeme.com/button.js*
// @exclude	    http*.linux.org.ru*
// @exclude		http://osmaster.org.ua*
// @version		0.0.66 [0.0.15-BM]
// @icon		   http://autopagerize.net/img/icons/icon_032.png
// @grant		  GM_getValue
// @grant		  GM_setValue
// @grant		  GM_addStyle
// @grant		  GM_log
// @grant		  GM_xmlhttpRequest
// @grant		  GM_registerMenuCommand
// ==/UserScript==
//
// modified: BladeMight BladeMight@gmail.com
// version: 0.0.14-BM
//
// auther:  swdyh http://d.hatena.ne.jp/swdyh/
// version: 0.0.66 2012-08-31T18:23:34+09:00
//
// this script based on
// GoogleAutoPager(http://la.ma.la/blog/diary_200506231749.htm) and
// estseek autopager(http://la.ma.la/blog/diary_200601100209.htm).
// thanks to ma.la.

if (isGreasemonkey()) {
	var ep = getPref('exclude_patterns')
	if (ep && isExclude(ep)) {
		// FIXME
		// return
	}
}
else {
	gmCompatible()
}

var URL = 'http://autopagerize.net/'
var VERSION = '0.0.66 [0.0.14-BM]'
var DEBUG = true
var PAGE_SEPARATOR = true
var PAGE_SEPARATOR_SIZE = 18
var AUTO_START = true
var CACHE_EXPIRE = 24 * 60 * 60 * 1000
var BASE_REMAIN_HEIGHT = 400
var FORCE_TARGET_WINDOW = getPref('force_target_window', true)
var XHR_TIMEOUT = 30 * 1000
var TIMER_TRIES = 0
var SITEINFO_IMPORT_URLS = [
	'http://wedata.net/databases/AutoPagerize/items_all.json',
]
var COLOR = {
	on: 'rgba(0, 170, 0, 0.3)',
	off: '#ccc',
	loading: '#0ff',
	terminated: '#00f',
	error: '#f0f'
}
var SITEINFO = [
	/* sample
	{
		url:		  'https?://wall.alphacoders.com/.*',
		nextLink:	 '//ul[@class="pagination"]/li/a[@id="next_page"]',
		pageElement:  '//div[@class="center" and not(@style)]',
		exampleUrl:   'https://wall.alphacoders.com/by_category.php?id=3&name=Anime+Wallpapers',
	},
	*/
	/* template
	{
		url:		  '',
		nextLink:	 '',
		pageElement:  '',
		exampleUrl:   '',
	},
	*/
]
var MICROFORMAT = {
	url:		  '.*',
	nextLink:	 '//a[@rel="next"] | //link[@rel="next"] | //a[@class="next"] | //*[@class="next"]/a | //a[@class="next_page"]',
	insertBefore: '//*[contains(@class, "autopagerize_insert_before")]',
	pageElement:  '//*[contains(@class, "autopagerize_page_element")]',
}

var AutoPager = function(info) {
	this.pageNum = 1
	this.info = info
	this.state = AUTO_START ? 'enable' : 'disable'
	this.loadConfigs()
	var self = this
	var url = this.getNextURL(info.nextLink, document, location.href)

	if ( !url ) {
		debug("getNextURL returns null.", info.nextLink)
		return
	}
	if (info.insertBefore) {
		this.insertPoint = getFirstElementByXPath(info.insertBefore)
	}

	if (!this.insertPoint) {
		var lastPageElement = getElementsByXPath(info.pageElement).pop()
		if (lastPageElement) {
			this.insertPoint = lastPageElement.nextSibling ||
				lastPageElement.parentNode.appendChild(document.createTextNode(' '))
		}
	}

	if (!this.insertPoint) {
		debug("insertPoint not found.", lastPageElement, info.pageElement)
		return
	}

	this.requestURL = url
	this.loadedURLs = {}
	this.loadedURLs[location.href] = true
	var toggle = function() {self.stateToggle()}
	this.toggle = toggle
	GM_registerMenuCommand('AutoPagerize - on/off', toggle)
	this.scroll= function() { self.onScroll() }
	window.addEventListener("scroll", this.scroll, false)

	if (isFirefoxExtension()) {
		var div = document.createElement("div")
		div.setAttribute('id', 'autopagerize_icon')
		div.style.display = 'none'
		document.body.appendChild(div)
		this.icon = div
	}
	else if (isChromeExtension() || isSafariExtension() || isJetpack()) {
		var frame = document.createElement('iframe')
		frame.style.display = 'none'
		frame.style.position = 'fixed'
		frame.style.bottom = '0px'
		frame.style.left = '0px'
		frame.style.height = '25px'
		frame.style.border = '0px'
		frame.style.opacity = '0.8'
		frame.style.zIndex = '1000'
		frame.width = '100%'
		frame.scrolling = 'no'
		this.messageFrame = frame
		var u = settings['extension_path'] ?
			settings['extension_path'] + 'loading.html' :
			'http://autopagerize.net/files/loading.html'
		this.messageFrame.src = u
		document.body.appendChild(frame)
		if (isSafariExtension()) {
			safari.self.tab.dispatchMessage('aed', {url: location.href })
		}
		else if (isChromeExtension()) {
			chrome.extension.connect({name: "launched"}).postMessage()
		}
		if (isJetpack()) {
			postMessage({name: 'launched', data: location.href })
		}
   }
	else {
		this.initIcon()
		this.initHelp()
		GM_addStyle('@media print{#autopagerize_icon, #autopagerize_help {display: none !important;}}')
		GM_addStyle('hr.autopagerize_page_separator {clear: both;}')
		this.icon.addEventListener("mouseover", function() {
			self.viewHelp()
		}, true)
	}
	this.onScroll()

	var that = this
	document.addEventListener('AutoPagerizeToggleRequest', function() {
		that.toggle()
	}, false)
	document.addEventListener('AutoPagerizeUpdateIconRequest', function() {
		that.updateIcon()
	}, false)
	that.updateIcon()
}
// Settings
AutoPager.prototype.saveConfigs = function() {
	GM_setValue('PAGE_SEPARATOR', PAGE_SEPARATOR)
	GM_setValue('PAGE_SEPARATOR_SIZE', PAGE_SEPARATOR_SIZE)
	GM_setValue('BASE_REMAIN_HEIGHT', BASE_REMAIN_HEIGHT)
	debug("Saved as: " + PAGE_SEPARATOR + " " + PAGE_SEPARATOR_SIZE + " " + BASE_REMAIN_HEIGHT)
	this.loadConfigs();
}
AutoPager.prototype.loadConfigs = function() {
	var val = GM_getValue('PAGE_SEPARATOR')
	if (val != null)
		PAGE_SEPARATOR = val
	val = GM_getValue('PAGE_SEPARATOR_SIZE')
	if (val != null)
		PAGE_SEPARATOR_SIZE = val
	val = GM_getValue('BASE_REMAIN_HEIGHT')
	if (val != null)
		BASE_REMAIN_HEIGHT = val
	debug("Load as: " + PAGE_SEPARATOR + " " + PAGE_SEPARATOR_SIZE + " " + BASE_REMAIN_HEIGHT)
}

AutoPager.prototype.getPageElementsBottom = function() {
	try {
		var elem = getElementsByXPath(this.info.pageElement).pop()
		return getElementBottom(elem)
	}
	catch(e) {}
}

AutoPager.prototype.initHelp = function() {
	var helpDiv = document.createElement('div')
	helpDiv.setAttribute('id', 'autopagerize_help')
	helpDiv.setAttribute('style', 'padding:5px;position:fixed;' +
					 'top:-200px;left:3px;font-size:10px;' +
					 'background:#248;color:#fff;border:1px solid #ccc;' +
					 'z-index:99999;text-align:left;font-weight:normal;' +
					 'line-height:120%;font-family:verdana;')

	var toggleDiv = document.createElement('div')
	toggleDiv.setAttribute('style', 'margin:0 0 0 50px;')
	var a = document.createElement('a')
	a.setAttribute('class', 'autopagerize_link')
	a.innerHTML = 'on/off'
	a.href = 'javascript:void(0)'
	var self = this
	var toggle = function() {
		self.stateToggle()
		helpDiv.style.top = '-200px'
	}
	a.addEventListener('click', toggle, false)
	toggleDiv.appendChild(a)
	var s = '<div style="width:100px; float:left;">'
	for (var i in COLOR) {
		s += '<div style="float:left;width:1em;height:1em;' +
			'margin:0 3px;background-color:' + COLOR[i] + ';' +
			'"></div><div style="margin:0 3px">' + i + '</div>'
	}
	s += '</div>'
	var colorDiv = document.createElement('div')
	colorDiv.innerHTML = s
	var raw = document.createElement('div')
	raw.innerHTML = `<div style="clear: both;">
	<label>Insert page separators:</label>
	<input class="pagesep" style="vertical-align: middle; bottom: -1px;" type="checkbox"` + (PAGE_SEPARATOR ? 'checked=""' : "") + `/><br/>
	<label>Page separator size:</label>
	<input class="sepsiz" style="vertical-align: middle; width: 50px;" type="number" min="0" value="` + PAGE_SEPARATOR_SIZE + `"/><br/>
	<label>Base remain height:</label>
	<input class="baserem" style="vertical-align: middle; width: 50px;" type="number" min="0" value="` + BASE_REMAIN_HEIGHT + `"/>
</div>`
	helpDiv.appendChild(colorDiv)
	helpDiv.appendChild(toggleDiv)
	helpDiv.appendChild(raw)

	var versionDiv = document.createElement('div')
	versionDiv.setAttribute('style', 'clear:both;')
	versionDiv.innerHTML = '<a href="' + URL +
		'">AutoPagerize</a> ver ' + VERSION
	helpDiv.appendChild(versionDiv)
	document.body.appendChild(helpDiv)

	var proc = function(e) {
		var pagesep = raw.getElementsByClassName('pagesep')[0]
		var sepsiz = raw.getElementsByClassName('sepsiz')[0]
		var baserem = raw.getElementsByClassName('baserem')[0]
		PAGE_SEPARATOR = pagesep.checked
		PAGE_SEPARATOR_SIZE = sepsiz.value
		BASE_REMAIN_HEIGHT = baserem.value
		self.saveConfigs()
		var c_style = document.defaultView.getComputedStyle(helpDiv, '')
		var s = ['top', 'left', 'height', 'width'].map(function(i) {
			return parseInt(c_style.getPropertyValue(i)) })
		if (e.clientX < s[1] || e.clientX > (s[1] + s[3] + 11) ||
			e.clientY < s[0] || e.clientY > (s[0] + s[2] + 11)) {
				helpDiv.style.top = '-200px'
		}
	}
	helpDiv.addEventListener('mouseout', proc, false)
	this.helpLayer = helpDiv
	GM_addStyle('#autopagerize_help a { color: #0f0; text-decoration: underline;}')
}

AutoPager.prototype.viewHelp = function() {
	this.helpLayer.style.top = '3px'
}

AutoPager.prototype.onScroll = function() {
	var scrollHeight = Math.max(document.documentElement.scrollHeight,
								document.body.scrollHeight)
	var remain = scrollHeight - window.innerHeight - window.scrollY
	debug("Remain: " + parseInt(remain) + " Point: " + parseInt(BASE_REMAIN_HEIGHT))
	if (this.state == 'enable' && remain < parseInt(BASE_REMAIN_HEIGHT)) {
		  this.request()
	}
}

AutoPager.prototype.stateToggle = function() {
	if (this.state == 'enable') {
		this.disable()
	}
	else {
		this.enable()
	}
}

AutoPager.prototype.enable = function() {
	this.state = 'enable'
	this.updateIcon()
}

AutoPager.prototype.disable = function() {
	this.state = 'disable'
	this.updateIcon()
}

AutoPager.prototype.updateIcon = function(state) {
	var st = state || this.state
	var rename = {'enable': 'on', 'disable': 'off' }
	if (rename[st]) {
		st = rename[st]
	}
	var color = COLOR[st]
	if (color) {
		if (isFirefoxExtension()) {
			chlorine.pageAction.update(color, location.href)
		}
		else if (isChromeExtension()) {
			chrome.extension.connect({name: "pageActionChannel"}).postMessage(color)
		}
		else if (isSafariExtension() || isJetpack()) {
		}
		else {
			this.icon.style.background = color
		}
	}
}

AutoPager.prototype.request = function() {
	if (!this.requestURL || this.lastRequestURL == this.requestURL) {
		return
	}
	this.lastRequestURL = this.requestURL
	var self = this
	var mime = 'text/html; charset=' + document.characterSet
	var headers = {}

	if (isSameDomain(this.requestURL)) {
		headers.Cookie = document.cookie
	}
	else {
		this.error()
		return
	}
	var opt = {
		method: 'get',
		url: this.requestURL,
		headers: headers,
		overrideMimeType: mime,
		onerror: function(res) {
			self.error()
		},
		onload: function(res) {
			if (res.finalUrl) {
				var url_s = res.finalUrl.split(/[\/\?]/)
				if (url_s[0] == location.protocol && location.host == url_s[2]) {
					self.requestLoad.apply(self, [res])
					return
				}
			}
			self.error()
		}
	}
	AutoPager.requestFilters.forEach(function(i) { i(opt) }, this)
	if (opt.stop) {
		this.requestURL = opt.url
	}
	else {
		this.showLoading(true)
		GM_xmlhttpRequest(opt)
	}
}

AutoPager.prototype.showLoading = function(sw) {
	if (sw) {
		this.updateIcon('loading')
		if (this.messageFrame && settings['display_message_bar']) {
			this.messageFrame.style.display = 'block'
		}
	}
	else {
		this.updateIcon('enable')
		if (this.messageFrame) {
			this.messageFrame.style.display = 'none'
		}
	}
}

AutoPager.prototype.requestLoad = function(res) {
	AutoPager.responseFilters.forEach(function(i) {
		i(res, this.requestURL)
	}, this)
	var htmlDoc = createHTMLDocumentByString(res.responseText)
	AutoPager.documentFilters.forEach(function(i) {
		i(htmlDoc, this.requestURL, this.info)
	}, this)
	var url = ""
	var page = ""
	try {
		page = getElementsByXPath(this.info.pageElement, htmlDoc)
		url = this.getNextURL(this.info.nextLink, htmlDoc, this.requestURL)
		debug("URL: " + url, "XPath: "  + this.info.nextLink)
		// Retry from MICROFORMAT
		if (!url) {
			url = this.getNextURL(MICROFORMAT.nextLink, htmlDoc, this.requestURL)
	   }
	   if (!page) {
		   page = getElementsByXPath(MICROFORMAT.pageElement, htmlDoc)
	   }
	   // Retry from SITEINFO
		if ((!page || !url) && SITEINFO.lenght > 0) {
		   for (var t=0; t != SITEINFO.lenght; t++) {
			   if (!url) {
				 url = this.getNextURL(SITEINFO[t].nextLink, htmlDoc, this.requestURL)
			   }
			   if (!page) {
				 page = getElementsByXPath(SITEINFO[t].pageElement, htmlDoc)
			   }
		   }
	   }
	}
	catch(e){
		log(e)
		this.error()
		return
	}

	if (!page || page.length < 1 ) {
		debug('pageElement not found.' , this.info.pageElement)
		this.terminate()
		return
	}

	if (this.loadedURLs[this.requestURL]) {
		debug('page is already loaded.', this.requestURL, this.info.nextLink)
		this.terminate()
		return
	}

	this.loadedURLs[this.requestURL] = true
	page = this.addPage(htmlDoc, page)
	AutoPager.filters.forEach(function(i) {
		i(page)
	})
	this.requestURL = url
	this.showLoading(false)
	this.onScroll()
	if (!url) {
		debug('<<nextLink not found.', this.info.nextLink, htmlDoc)
		this.terminate()
	}
	var ev = document.createEvent('Event')
	ev.initEvent('GM_AutoPagerizeNextPageLoaded', true, false)
	document.dispatchEvent(ev)
}

AutoPager.prototype.addPage = function(htmlDoc, page) {
	var HTML_NS  = 'http://www.w3.org/1999/xhtml'
	var hr = document.createElementNS(HTML_NS, 'hr')
	var p  = document.createElementNS(HTML_NS, 'p')
	hr.setAttribute('class', 'autopagerize_page_separator')
	p.setAttribute('class', 'autopagerize_page_info')
	p.setAttribute('style', 'height: ' + PAGE_SEPARATOR_SIZE + 'px; ' + (PAGE_SEPARATOR_SIZE < 15 ? 'display:none;' : ''));
	var self = this


	if (PAGE_SEPARATOR) {
		if (page[0] && /tr/i.test(page[0].tagName)) {
			var insertParent = this.insertPoint.parentNode
			var colNodes = getElementsByXPath('child::tr[1]/child::*[self::td or self::th]', insertParent)

			var colums = 0
			for (var i = 0, l = colNodes.length; i < l; i++) {
				var col = colNodes[i].getAttribute('colspan')
				colums += parseInt(col, 10) || 1
			}
			var td = document.createElement('td')
			// td.appendChild(hr)
			td.appendChild(p)
			var tr = document.createElement('tr')
			td.setAttribute('colspan', colums)
			tr.appendChild(td)
			insertParent.insertBefore(tr, this.insertPoint)
		}
		else {
// 	this.insertPoint.parentNode.insertBefore(hr, this.insertPoint)
			this.insertPoint.parentNode.insertBefore(p, this.insertPoint)
		}
	}
	p.innerHTML = '▶ страница: <a class="autopagerize_link" href="' +
	this.requestURL.replace(/&/g, '&amp;') + '">' + (++this.pageNum) + '</a><div style="margin-top: -4pt;"><hr></div>'
	return page.map(function(i) {
		var pe = document.importNode(i, true)
		self.insertPoint.parentNode.insertBefore(pe, self.insertPoint)
		var ev = document.createEvent('MutationEvent')
		ev.initMutationEvent('AutoPagerize_DOMNodeInserted', true, false,
							 self.insertPoint.parentNode, null,
							 self.requestURL, null, null)
		pe.dispatchEvent(ev)
		return pe
	})
}

AutoPager.prototype.initIcon = function() {
	var div = document.createElement("div")
	div.setAttribute('id', 'autopagerize_icon')
	with (div.style) {
		fontSize   = '12px'
		position   = 'fixed'
		top		= '0px'
		left	  = '0px'
		background = COLOR['on']
		color	  = '#fff'
		width = '2px'
		height = '2px'
		zIndex = '99999'
		if (this.state != 'enable') {
			background = COLOR['off']
		}
	}
	document.body.appendChild(div)
	this.icon = div
}

AutoPager.prototype.getNextURL = function(xpath, doc, url) {
	var nextLink = getFirstElementByXPath(xpath, doc)
	if (nextLink) {
		var nextValue = nextLink.getAttribute('href') ||
			nextLink.getAttribute('action') || nextLink.value
		if (nextValue.match(/^http(s)?:/)) {
			debug("nextLink="+nextValue)
			return nextValue
		}
		if (nextValue.match(/^\/.*/)) {
			var next = window.location.protocol + '//' + document.domain + nextValue
			debug("nextLink(/)=" + next)
			return next
		} else {
			var base = getFirstElementByXPath('//base[@href]', doc)
			debug("nextLink(ELSE)=" + resolvePath(nextValue, (base ? base.href : url)))
			debug('base ' + (base ? base.href : url) + ' nV' + nextValue)
			return resolvePath(nextValue, (base ? base.href : url))
		}
	}
}

AutoPager.prototype.terminate = function() {
	window.removeEventListener('scroll', this.scroll, false)
	this.updateIcon('terminated')
	var self = this
	setTimeout(function() {
		if (self.icon) {
			self.icon.parentNode.removeChild(self.icon)
		}
		if (isSafariExtension()) {
			var mf = self.messageFrame
			mf.parentNode.removeChild(mf)
		}
	}, 1500)
}

AutoPager.prototype.error = function() {
	this.updateIcon('error')
	window.removeEventListener('scroll', this.scroll, false)
	if (isSafariExtension() || isChromeExtension() || isJetpack()) {
		var mf = this.messageFrame
		var u = settings['extension_path'] ?
			settings['extension_path'] + 'error.html' :
			'http://autopagerize.net/files/error.html'
		mf.src = u
		mf.style.display = 'block'
		setTimeout(function() {
			mf.parentNode.removeChild(mf)
		}, 3000)
	}
}

AutoPager.documentFilters = []
AutoPager.requestFilters = []
AutoPager.responseFilters = []
AutoPager.filters = []

var fixLinkRel = function() {
	var anotherElement = document.querySelector('*[class~=h-entry],*[class~=hentry]')
	var shouldDelete = anotherElement && anotherElement.className.match(/(^|\s)h-?entry(\s|$)/)

	if (shouldDelete) {
	  var links = document.querySelectorAll('link[rel*="next"], link[rel*="Next"], link[rel*="NEXT"]')
	  for(var link of links)
		  link.removeAttribute('rel')
	}

	var anotherElement2 = document.querySelector('*[class~=h-entry],*[class~=hentry]')
	var shouldDelete2 = anotherElement2 && anotherElement2.className.match(/(^|\s)post-.*h-?entry(\s|$)/)

	if (shouldDelete2) {
	  var links2 = document.querySelectorAll('a[rel*="next"], a[rel*="Next"], a[rel*="NEXT"]')
	  for(var a of links2)
		  a.removeAttribute('rel')
	}
}
var fixImgThumb = function() {
	function handle(node) {
		var items = document.evaluate('.//img', node, null, 7, null);
		for(var i = 0; i < items.snapshotLength; i++) {
			var item  = items.snapshotItem(i);
			var attrs = item.attributes;
			for(var j = attrs.length-1; j >= 0; j--) {
	//	  for(var j = 0; j < attrs.length; j++) {
				if (/^data-/.test(attrs[j].name) && /jpe?g/i.test(attrs[j].value)) {
					item.src = attrs[j].value;
					break;
				}
			}
		}
	}
	document.addEventListener('AutoPagerize_DOMNodeInserted', function(e) {
		handle(e.target);
	}, false);
}

var parseInfo = function(str) {
	var lines = str.split(/\r\n|\r|\n/)
	var re = /(^[^:]*?):(.*)$/
	var strip = function(str) {
		return str.replace(/^\s*/, '').replace(/\s*$/, '')
	}
	var info = {}
	for (var i = 0; i < lines.length; i++) {
		if (lines[i].match(re)) {
			info[RegExp.$1] = strip(RegExp.$2)
		}
	}
	var isValid = function(info) {
		var infoProp = ['url', 'nextLink', 'pageElement']
		for (var i = 0; i < infoProp.length; i++) {
			if (!info[infoProp[i]]) {
				return false
			}
		}
		return true
	}
	return isValid(info) ? info : null
}

var launchAutoPager = function(list) {
	fixLinkRel()
	fixImgThumb()
	if (list.length == 0) {
		return
	}
	for (var i = 0; i < list.length; i++) {
		try {
			if (ap) {
				return
			}
			else if (!location.href.match(list[i].url)) {
			}
			else {
				var nextLink = getFirstElementByXPath(list[i].nextLink)
				if (!nextLink) {
					debug(">>nextLink not found => " + nextLink + ", X: " + list[i].nextLink)
					if (TIMER_TRIES < 1) {
						debug("Wait for load page elements from js, retry in 1200ms.")
						setTimeout(function() { launchAutoPager(list) }, 1200);
						TIMER_TRIES++
					}
				} else if (!getFirstElementByXPath(list[i].pageElement)) {
					debug(">>pageElement not found.", list[i].pageElement)
					if (TIMER_TRIES < 1) {
						debug("Wait for load page elements from js, retry in 1200ms.")
						setTimeout(function() { launchAutoPager(list) }, 1200);
						TIMER_TRIES++
					}
				}
				else {
					ap = new AutoPager(list[i])
					return
				}
			}
		}
		catch(e) {
			log(e)
			continue
		}
	}
}
var clearCache = function() {
	GM_setValue('cacheInfo', '')
}
var getCache = function() {
	try {
		return JSON.parse(GM_getValue('cacheInfo')) || {}
	}
	catch(e) {
		return {}
	}
}
var getCacheCallback = function(res, url) {
	if (res.status != 200) {
		return getCacheErrorCallback(url)
	}

	var info
	try {
		info = JSON.parse(res.responseText).map(function(i) { return i.data })
	}
	catch(e) {
		info = []
	}
	if (info.length > 0) {
		info = info.filter(function(i) { return ('url' in i) })
		info.sort(function(a, b) { return (b.url.length - a.url.length) })

		var r_keys = ['url', 'nextLink', 'insertBefore', 'pageElement']
		info = info.map(function(i) {
			var item = {}
			r_keys.forEach(function(key) {
				if (i[key]) {
					item[key] = i[key]
				}
			})
			return item
		})

		cacheInfo[url] = {
			url: url,
			expire: new Date(new Date().getTime() + CACHE_EXPIRE),
			info: info
		}
		GM_setValue('cacheInfo', JSON.stringify(cacheInfo))
		launchAutoPager(info)
	}
	else {
		getCacheErrorCallback(url)
	}
}
var getCacheErrorCallback = function(url) {
	var expire = new Date(new Date().getTime() + CACHE_EXPIRE)
	if (cacheInfo[url]) {
		cacheInfo[url].expire = expire
		launchAutoPager(cacheInfo[url].info)
	}
	else {
		cacheInfo[url] = {
			url: url,
			expire: expire,
			info: []
		}
	}
	GM_setValue('cacheInfo', cacheInfo.toSource())
}

var linkFilter = function(doc, url) {
	var base = getFirstElementByXPath('//base[@href]', doc)
	var baseUrl = base ? base.href : url
	var isSameBase = isSameBaseUrl(location.href, baseUrl)
	if (!FORCE_TARGET_WINDOW && isSameBase) {
		return
	}

	var anchors = getElementsByXPath('descendant-or-self::a[@href]', doc)
	anchors.forEach(function(i) {
		var attrHref = i.getAttribute('href')
		if (FORCE_TARGET_WINDOW && !attrHref.match(/^#|^javascript:/) &&
			i.className.indexOf('autopagerize_link') < 0) {
			i.target = '_blank'
		}
		if (!isSameBase && !attrHref.match(/^#|^\w+:/)) {
			i.href = resolvePath(i.getAttribute('href'), baseUrl)
		}
	})

	if (!isSameBase) {
		var images = getElementsByXPath('descendant-or-self::img', doc)
		images.forEach(function(i) {
			i.src = resolvePath(i.getAttribute('src'), baseUrl)
		})
	}
}
AutoPager.documentFilters.push(linkFilter)

fixResolvePath()

if (typeof(window.AutoPagerize) == 'undefined') {
	window.AutoPagerize = {}
	window.AutoPagerize.addFilter = function(f) {
		AutoPager.filters.push(f)
	}
	window.AutoPagerize.addDocumentFilter = function(f) {
		AutoPager.documentFilters.push(f)
	}
	window.AutoPagerize.addResponseFilter = function(f) {
		AutoPager.responseFilters.push(f)
	}
	window.AutoPagerize.addRequestFilter = function(f) {
		AutoPager.requestFilters.push(f)
	}
	window.AutoPagerize.launchAutoPager = launchAutoPager

	var ev = document.createEvent('Event')
	ev.initEvent('GM_AutoPagerizeLoaded', true, false)
	document.dispatchEvent(ev)
}

var settings = {}
var ap = null
if (isChromeExtension()) {
	var port = chrome.extension.connect({name: "settingsChannel"})
	port.postMessage()
	port.onMessage.addListener(function(res) {
		settings = res
		if (res['exclude_patterns'] && isExclude(res['exclude_patterns'])) {
			return
		}
		debug('Running as chrome extension.')
		launchAutoPager(SITEINFO)
		var port_ = chrome.extension.connect({name: "siteinfoChannel"})
		port_.postMessage({ url: location.href })
		port_.onMessage.addListener(function(res) {
			launchAutoPager(res)
			chrome.extension.onConnect.addListener(function(port) {
				if (port.name == "toggleRequestChannel") {
					port.onMessage.addListener(function(msg) {
						if (ap) {
							ap.toggle()
						}
					})
				}
			})
		})
	})
}
else if (isSafariExtension()) {
	var re_exclude = /^(about:|safari-extension:)/
	if (!location.href.match(re_exclude)) {
		safari.self.addEventListener('message', function(event) {
			if (event.name === 'settings') {
				settings = event.message
				safari.self.tab.dispatchMessage('siteinfoChannel', {url: location.href })
			}
			else if (event.name === 'siteinfoChannel') {
				if (!settings['exclude_patterns'] || !isExclude(settings['exclude_patterns'])) {
					debug('Running as safari extension.')
					launchAutoPager(SITEINFO)
					launchAutoPager([MICROFORMAT])
					launchAutoPager(event.message)
				}
			}
			else if (event.name === 'toggleRequestChannel') {
				if (ap) {
					ap.toggle()
				}
			}
			else if (event.name === 'updateSettings') {
				settings = event.message
			}
		}, false)
		safari.self.tab.dispatchMessage('settings')
	}
}
else if (isJetpack()) {
	postMessage({ name: 'settings' })
	onMessage = function(message) {
		if (message.name == 'siteinfo') {
			debug('Running as firefox jetpack extension.')
			// launchAutoPager(SITEINFO)
			launchAutoPager(message.data)
		}
		else if (message.name == 'settings') {
			settings = message.data
			if (settings['exclude_patterns'] && isExclude(settings['exclude_patterns'])) {
				// return
			}
			else  {
				postMessage({ name: 'siteinfo', url: location.href })
				launchAutoPager([MICROFORMAT])
			}
		}
	}
}
else {
	debug('Running as user.js extension.')
	debug(location.href)
	launchAutoPager(SITEINFO)
	GM_registerMenuCommand('AutoPagerize - clear cache', clearCache)
	var cacheInfo = getCache()
	var xhrStates = {}
	SITEINFO_IMPORT_URLS.forEach(function(i) {
		if (!cacheInfo[i] || new Date(cacheInfo[i].expire) < new Date()) {
			var opt = {
				method: 'get',
				url: i,
				onload: function(res) {
					xhrStates[i] = 'loaded'
					getCacheCallback(res, i)
				},
				onerror: function(res){
					xhrStates[i] = 'error'
					getCacheErrorCallback(i)
				},
			}
			xhrStates[i] = 'start'
			GM_xmlhttpRequest(opt)
			setTimeout(function() {
				if (xhrStates[i] == 'start') {
					getCacheErrorCallback(i)
				}
			}, XHR_TIMEOUT)
		}
		else {
			launchAutoPager(cacheInfo[i].info)
		}
	})
	launchAutoPager([MICROFORMAT])
}


// new google search sucks!
if (location.href.match('^http://[^.]+\.google\.(?:[^.]{2,3}\.)?[^./]{2,3}/.*(&fp=)')) {
	var to = location.href.replace(/&fp=.*/, '')
	// console.log([location.href, to])
	location.href = to
}




// utility functions.
function createHTMLDocumentByString(str) {
	if (document.documentElement.nodeName != 'HTML') {
		return new DOMParser().parseFromString(str, 'application/xhtml+xml')
	}
	var html = strip_html_tag(str)
	var htmlDoc
	try {
		// We have to handle exceptions since Opera 9.6 throws
		// a NOT_SUPPORTED_ERR exception for |document.cloneNode(false)|
		// against the DOM 3 Core spec.
		htmlDoc = document.cloneNode(false)
		htmlDoc.appendChild(htmlDoc.importNode(document.documentElement, false))
	}
	catch(e) {
		htmlDoc = document.implementation.createDocument(null, 'html', null)
	}
	var fragment = createDocumentFragmentByString(html)
	try {
		fragment = htmlDoc.adoptNode(fragment)
	}
	catch(e) {
		fragment = htmlDoc.importNode(fragment, true)
	}
	htmlDoc.documentElement.appendChild(fragment)
	return htmlDoc
}

function getElementsByXPath(xpath, node) {
	var nodesSnapshot = getXPathResult(xpath, node,
		XPathResult.ORDERED_NODE_SNAPSHOT_TYPE)
	var data = []
	for (var i = 0; i < nodesSnapshot.snapshotLength; i++) {
		data.push(nodesSnapshot.snapshotItem(i))
	}
	return data
}

function getFirstElementByXPath(xpath, node) {
	var result = getXPathResult(xpath, node,
		XPathResult.FIRST_ORDERED_NODE_TYPE)
	return result.singleNodeValue
}

function getXPathResult(xpath, node, resultType) {
	var node = node || document
	var doc = node.ownerDocument || node
	var resolver = doc.createNSResolver(node.documentElement || node)
//	 debug("XPath: " + xpath + "\nresulType: " + resultType );
	// Use |node.lookupNamespaceURI('')| for Opera 9.5
	// A workaround for bugs of Node.lookupNamespaceURI(null)
	// https://bugzilla.mozilla.org/show_bug.cgi?id=693615
	// https://bugzilla.mozilla.org/show_bug.cgi?id=694754
	var defaultNS = null
	try {
		// This follows the spec: http://www.w3.org/TR/DOM-Level-3-Core/namespaces-algorithms.html#lookupNamespaceURIAlgo
		if (node.nodeType == node.DOCUMENT_NODE) {
			defaultNS = node.documentElement.lookupNamespaceURI(null)
		}
		else {
			defaultNS = node.lookupNamespaceURI(null)
		}
	}
	catch(e) {}

	if (defaultNS) {
		const defaultPrefix = '__default__'
		xpath = addDefaultPrefix(xpath, defaultPrefix)
		var defaultResolver = resolver
		resolver = function (prefix) {
			return (prefix == defaultPrefix)
				? defaultNS : defaultResolver.lookupNamespaceURI(prefix)
		}
	}
	return doc.evaluate(xpath, node, resolver, resultType, null)
}

function addDefaultPrefix(xpath, prefix) {
	const tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g
	const TERM = 1, OPERATOR = 2, MODIFIER = 3
	var tokenType = OPERATOR
	prefix += ':'
	function replacer(token, identifier, suffix, term, operator, modifier) {
		if (suffix) {
			tokenType =
				(suffix == ':' || (suffix == '::' &&
				 (identifier == 'attribute' || identifier == 'namespace')))
				? MODIFIER : OPERATOR
		}
		else if (identifier) {
			if (tokenType == OPERATOR && identifier != '*') {
				token = prefix + token
			}
			tokenType = (tokenType == TERM) ? OPERATOR : TERM
		}
		else {
			tokenType = term ? TERM : operator ? OPERATOR : MODIFIER
		}
		return token
	}
	return xpath.replace(tokenPattern, replacer)
}

function createDocumentFragmentByString(str) {
	var range = document.createRange()
	range.setStartAfter(document.body)
	return range.createContextualFragment(str)
}

function log(message) {
	if (typeof console == 'object') {
		console.log(message)
	}
	else {
		GM_log(message)
	}
}

function debug() {
	if ( typeof DEBUG != 'undefined' && DEBUG ) {
		if (console.log.apply) {
			console.log.apply(console, arguments)
		}
		else {
			Function.prototype.apply.apply(console.log, [console, arguments])
		}
	}
}

function getElementPosition(elem) {
	var offsetTrail = elem
	var offsetLeft  = 0
	var offsetTop   = 0
	while (offsetTrail) {
		offsetLeft += offsetTrail.offsetLeft
		offsetTop  += offsetTrail.offsetTop
		offsetTrail = offsetTrail.offsetParent
	}
	offsetTop = offsetTop || null
	offsetLeft = offsetLeft || null
	return {left: offsetLeft, top: offsetTop}
}

function getElementBottom(elem) {
	var c_style = document.defaultView.getComputedStyle(elem, '')
	var height  = 0
	var prop	= ['height', 'borderTopWidth', 'borderBottomWidth',
				   'paddingTop', 'paddingBottom',
				   'marginTop', 'marginBottom']
	prop.forEach(function(i) {
		var h = parseInt(c_style[i])
		if (typeof h == 'number') {
			height += h
		}
	})
	var top = getElementPosition(elem).top
	return top ? (top + height) : null
}

function getScrollHeight() {
	return Math.max(document.documentElement.scrollHeight,
								document.body.scrollHeight)
}

function isSameDomain(url) {
	if (url.match(/^\w+:/)) {
		var url_s = url.split(/[\/\?]/)
		return url_s[0] == location.protocol && location.host == url_s[2]
	}
	else {
		return true
	}
}

function isSameBaseUrl(urlA, urlB) {
	return (urlA.replace(/[^/]+$/, '') == urlB.replace(/[^/]+$/, ''))
}

function resolvePath(path, base) {
	if (path.match(/^https?:\/\//)) {
		return path
	}
	if (path.match(/^\?/)) {
		return base.replace(/\?.+$/, '') + path;
	}
	if (path.match(/^[^\/]/)){
		return base.replace(/[^/]+$/, '') + path
	}
	else {
		return base.replace(/([^/]+:\/\/[^/]+)\/.*/, '\$1') + path
	}
}

function fixResolvePath() {
	if (resolvePath('', 'http://resolve.test/') == 'http://resolve.test/') {
		return
	}
	// A workaround for WebKit and Mozilla 1.9.2a1pre,
	// which don't support XML Base in HTML.
	// https://bugs.webkit.org/show_bug.cgi?id=17423
	// https://bugzilla.mozilla.org/show_bug.cgi?id=505783
	var XML_NS = 'http://www.w3.org/XML/1998/namespace'
	var baseElement = document.createElementNS(null, 'base')
	var pathElement = document.createElementNS(null, 'path')
	baseElement.appendChild(pathElement)
	resolvePath = function resolvePath_workaround(path, base) {
		baseElement.setAttributeNS(XML_NS, 'xml:base', base)
		pathElement.setAttributeNS(XML_NS, 'xml:base', path)
		return pathElement.baseURI
	}
}

function strip_html_tag(str) {
	var chunks = str.split(/(<html(?:[ \t\r\n][^>]*)?>)/)
	if (chunks.length >= 3) {
		chunks.splice(0, 2)
	}
	str = chunks.join('')
	chunks = str.split(/(<\/html[ \t\r\n]*>)/)
	if (chunks.length >= 3) {
		chunks.splice(chunks.length - 2)
	}
	return chunks.join('')
}

function getPref(key, defaultValue) {
	var value = GM_getValue(key)
	return (typeof value == 'undefined') ? defaultValue : value
}

function wildcard2regep(str) {
	return '^' + str.replace(/([-()\[\]{}+?.$\^|,:#<!\\])/g, '\\$1').replace(/\x08/g, '\\x08').replace(/\*/g, '.*')
}

function isExclude(patterns) {
	var rr = /^\/(.+)\/$/
	var eps = (patterns || '').split(/[\r\n ]+/)
	for (var i = 0; i < eps.length; i++) {
		var reg = null
		if (rr.test(eps[i])) {
			reg = eps[i].match(rr)[1]
		}
		else {
			reg = wildcard2regep(eps[i])
		}
		if (location.href.match(reg)) {
			return true
		}
	}
	return false
}
// obsolete
function isFirefoxExtension() {
	return (typeof chlorine == 'object')
}

function isChromeExtension() {
	return (typeof chrome == 'object') &&
		(typeof chrome.extension == 'object')
}

function isSafariExtension() {
	return (typeof safari == 'object') &&
		(typeof safari.extension == 'object')
}

function isGreasemonkey() {
	return (typeof GM_log == 'function')
}

function isJetpack() {
	// isFirefoxExtension is obsolete
	return (!isGreasemonkey() && !isSafariExtension() &&
			!isChromeExtension() && !isFirefoxExtension())
}

function gmCompatible() {
	GM_registerMenuCommand = function() {}
	GM_setValue = function() {}
	GM_getValue = function() {}
	GM_addStyle = function() {}
	uneval = function() {}
	fixResolvePath = function() {}
	resolvePath = function (path, base) { return path }

	if (isChromeExtension() || isSafariExtension()) {
		createHTMLDocumentByString = function(str) {
			if (document.documentElement.nodeName != 'HTML') {
				return new DOMParser().parseFromString(str, 'application/xhtml+xml')
			}
			// FIXME
			var html = str.replace(/<script(?:[ \t\r\n][^>]*)?>[\S\s]*?<\/script[ \t\r\n]*>|<\/?(?:i?frame|html|script|object)(?:[ \t\r\n][^<>]*)?>/gi, ' ')
			var htmlDoc = document.implementation.createHTMLDocument ?
				document.implementation.createHTMLDocument('apfc') :
				document.implementation.createDocument(null, 'html', null)
			var range = document.createRange()
			range.selectNodeContents(document.documentElement)
			htmlDoc.documentElement.appendChild(range.createContextualFragment(html))
			return htmlDoc
		}
	}
	return true
}
