// PLUGIN_INFO//{{{
var PLUGIN_INFO =
    nextlink
    mapping "[[", "]]" by AutoPagerize XPath.
    AutoPagerize 用の XPath より "[[", "]]" をマッピングします。
    suVene
    0.2.4
    1.2
    2.0pre
    ||
    let g:nextlink_followlink = "true"
||<
と設定することにより、"[[", "]]" の動作は、カレントのタブに新しくページを読み込むようになります。
== Command ==
:nextlink:
    autocmd によって呼び出されます。
  ]]>
;
//}}}
liberator.plugins.nextlink = (function() {
    // initialize //{{{
    if (!liberator.plugins.libly) {
        liberator.log('nextlink: needs _libly.js');
        return;
    }
    var libly = liberator.plugins.libly;
    var $U = libly.$U;
    var logger = $U.getLogger('nextlink');
    var isFollowLink = typeof liberator.globalVariables.nextlink_followlink == 'undefined' ?
                       false : $U.eval(liberator.globalVariables.nextlink_followlink);
    var pageNaviCss =
            ;
    //}}}
    var NextLink = function() {//{{{
        this.initialize.apply(this, arguments);
    };
    NextLink.prototype = {
        initialize: function(pager) {
            this.WEDATA_AUTOPAGERIZE = 'http://wedata.net/databases/AutoPagerize/items.json';
            this.initialized = false;
            this.isCurOriginalMap = true;
            this.siteinfo = [];
            this.cache = {}; // {url: {xpath: xpath, next: element, prev: url}} or null
            this.pager = pager;
            this.browserModes = config.browserModes || [modes.NORMAL, modes.VISUAL];
            this.is2_0later = config.autocommands.some(function ([k, v]) k == 'DOMLoad'); // toriaezu
            var req = new libly.Request(this.WEDATA_AUTOPAGERIZE);
            req.addEventListener('onSuccess', $U.bind(this,
                function(res) {
                    var json = $U.evalJson(res.responseText);
                    if (!json) return;
                    this.siteinfo = json.map(function(item) item.data)
                                    .sort(function(a, b) b.url.length - a.url.length); // sort url.length desc
                    this.initialized = true;
                }
            ));
            req.get();
            // for debug
            /*
            this.initialized = true;
            this.siteinfo = [
                {
                    url: '^https?://(?:192\\.168(?:\\.\\d+){2}|localhost)(?::\\d+)?/',
                    nextLink: 'id("next")',
                    pageElement: '//*'
                }
            ];
            */
            commands.addUserCommand(['nextlink'], 'map ]] by AutoPagerize XPath.',
                $U.bind(this, function(args) { this.handler(args); }), null, true
            );
            var loadEvent = autocommands['DOMLoad'] || 'PageLoad'; // for 1.2
            liberator.execute(':autocmd! ' + (this.is2_0later ? 'DOMLoad' : 'PageLoad') + ' .* :nextlink onLoad');
            liberator.execute(':autocmd! LocationChange .* :nextlink onLocationChange');
        },
        handler: function(args) {
            event = args.string || args;
            this[event](buffer.URL);
            commandline.echo('');
        },
        onLoad: function(url) {
            if (!this.initialized) return;
            if (this.cache[url] &&
                this.cache[url].hasOwnProperty('xpath')) {
                this.cache[url].doc = window.content.document;
                this.onLocationChange(url, true);
                return;
            }
            for (let i = 0, len = this.siteinfo.length; i < len; i++) {
                if (url.match(this.siteinfo[i].url) && this.siteinfo[i].url != '^https?://.') {
                    window.content.addEventListener('unload', $U.bind(this,
                                function() { this.cache[url] = null; }), false);
                    this.setCache(url,
                        ['doc', 'xpath', 'siteinfo'],
                        [window.content.document, this.siteinfo[i].nextLink, this.siteinfo[i]]
                    );
                    this.onLocationChange(url, true);
                    return;
                }
            }
            this.setCache(url, ['doc', 'xpath', 'prev', 'next'], [null, null, null, null]);
        },
        onLocationChange: function(url, isCallerLoaded) {
            if (!this.initialized ||
                !this.cache[url] ||
                !this.cache[url].hasOwnProperty('xpath')) return;
            if (this.cache[url]['xpath'] == null) {
                this.restorOrginalMap();
                return;
            }
            this.pager.onLocationChange(this, url, isCallerLoaded);
            this.customizeMap(this, url);
            this.isCurOriginalMap = false;
        },
        customizeMap: function(context, url) {
            var cache = this.cache[url];
            var prev = cache.prev;
            var next = cache.next;
            if (!prev)
                this.removeMap('[[');
            if (!next)
                this.removeMap(']]');
            this.pager.customizeMap(context, url, prev, next);
        },
        restorOrginalMap: function() {
            if (this.isCurOriginalMap) return;
            this.removeMap('[[');
            this.removeMap(']]');
            this.isCurOriginalMap = true;
        },
        setCache: function(key, subKeys, values) {
            if (!this.cache[key]) this.cache[key] = {};
            values = [].concat(values);
            [].concat(subKeys).forEach($U.bind(this, function(subKey, i) {
                this.cache[key][subKey] = values[i];
            }));
        },
        removeMap: function(cmd) {
            try {
                if (mappings.hasMap(this.browserModes, cmd)) {
                    mappings.remove(this.browserModes, cmd);
                }
                return true;
            } catch (e) {
                return false;
            }
        }
    };//}}}
    var Autopager = function() {};//{{{
    Autopager.prototype = {
        onLocationChange: function(context, url, isCallerLoaded) {
            var cache = context.cache[url];
            var doc = cache.doc;
            var elems = cache.next;
            var elem;
            if (isCallerLoaded) {
                let insertPoint, lastPageElement;
                if (cache.insertBefore)
                    insertPoint = $U.getNodesFromXPath(cache.siteinfo.insertBefore, doc);
                if (!insertPoint)
                    lastPageElement = $U.getNodesFromXPath(cache.siteinfo.pageElement, doc).pop();
                    if (lastPageElement)
                        insertPoint = lastPageElement.nextSibling ||
                                      lastPageElement.parentNode.appendChild(doc.createTextNode(' '));
                if (context.is2_0later) {
                    let css = $U.xmlToDom(pageNaviCss, doc);
                    let node = doc.importNode(css, true);
                    doc.body.insertBefore(node, doc.body.firstChild);
                    //doc.body.appendChild(css);
                }
                $U.getNodesFromXPath(cache.xpath, doc, function(item) elem = item, this);
                context.setCache(url,
                    ['prev', 'next', 'curPage', 'insertPoint', 'terminate', 'lastReqUrl', 'loadedURLs', 'mark'],
                    [[], [elem], 1, insertPoint, 0, null, {}, []]
                );
            }
        },
        customizeMap: function(context, url, prev, next) {
            var cache = context.cache[url];
            var doc = cache.doc;
            mappings.addUserMap(context.browserModes, ['[['], 'customize by nextlink.js',
                $U.bind(this, function(count) {
                    if (cache.curPage == 1) {
                        return;
                    } else if (--cache.curPage == 1) {
                        window.content.scrollTo(0, 0);
                    } else {
                        this.focusPagenavi(context, url, cache.curPage);
                    }
                }),
                { flags: Mappings.flags.COUNT });
            mappings.addUserMap(context.browserModes, [']]'], 'customize by nextlink.js',
                $U.bind(this, function(count) {
                    var reqUrl, lastReqUrl;
                    reqUrl = $U.pathToURL(cache.next[cache.curPage - 1], doc);
                    lastReqUrl = cache.lastReqUrl;
                    if (cache.isLoading) {
                        logger.echo('loading now...');
                        return;
                    }
                    if (!reqUrl || cache.curPage == cache.terminate) {
                        logger.echo('end of pages.');
                        return;
                    }
                    if (cache.loadedURLs[reqUrl]) {
                        this.focusPagenavi(context, url, ++cache.curPage);
                        return;
                    }
                    context.setCache(url, ['lastReqUrl', 'isLoading'], [reqUrl, true]);
                    var req = new libly.Request(
                                    reqUrl, null,
                                    { asynchronous: true, encoding: doc.characterSet,
                                      context: context, url: url }
                                  );
                    req.addEventListener('onSuccess', $U.bind(this, this.onSuccess));
                    req.addEventListener('onFailure', $U.bind(this, this.onFailure));
                    req.addEventListener('onException', $U.bind(this, this.onFailure));
                    req.get();
                }),
                { flags: Mappings.flags.COUNT }
            );
        },
        onSuccess: function(res) {
            var context = res.req.options.context;
            var url = res.req.options.url;
            var cache = context.cache[url];
            var doc = cache.doc;
            var page = res.getHTMLDocument(cache.siteinfo.pageElement);
            var htmlDoc = res.doc;
            var prev = cache.next[cache.curPage];
            var next = $U.getNodesFromXPath(cache.xpath, htmlDoc);
            cache.isLoading = false;
            cache.loadedURLs[res.req.url] = true;
            if (!page || page.length < 1) {
                context.setCache(url, 'terminate', cache.curPage);
                return;
            }
            cache.curPage++;
            if (next && next.length) {
                cache.prev.push(prev);
                cache.next.push(next[0]);
            } else {
                context.setCache(url, 'terminate', cache.curPage);
            }
            this.addPage(context, htmlDoc, url, page, res.req.url);
            this.focusPagenavi(context, url, cache.curPage);
        },
        addPage: function(context, doc, url, page, reqUrl) {
            var cache = context.cache[url];
            var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
            //var hr = doc.createElementNS(HTML_NAMESPACE, 'hr');
            var p = doc.createElementNS(HTML_NAMESPACE, 'p');
            var tagName;
            if (page[0] && page[0].tagName)
                tagName = page[0].tagName.toLowerCase();
            if (tagName == 'tr') {
                let insertParent = cache.insertPoint.parentNode;
                let colNodes = getElementsByXPath('child::tr[1]/child::*[self::td or self::th]', insertParent);
                let colums = 0;
                for (let i = 0, l = colNodes.length; i < l; i++) {
                    let col = colNodes[i].getAttribute('colspan');
                    colums += parseInt(col, 10) || 1;
                }
                let td = doc.createElement('td');
                // td.appendChild(hr);
                td.appendChild(p);
                let tr = doc.createElement('tr');
                td.setAttribute('colspan', colums);
                tr.appendChild(td);
                insertParent.insertBefore(tr, cache.insertPoint);
            } else if (tagName == 'li') {
                let li = doc.createElementNS(HTML_NAMESPACE, 'li');
                cache.insertPoint.parentNode.insertBefore(li, cache.insertPoint);
                li.appendChild(p);
            } else {
                //cache.insertPoint.parentNode.insertBefore(hr, cache.insertPoint);
                cache.insertPoint.parentNode.insertBefore(p, cache.insertPoint);
            }
            p.id = 'vimperator-nextlink-' + cache.curPage;
            p.innerHTML = 'page: ' + cache.curPage + '';
            p.className = 'vimperator-nextlink-page';
            cache.mark.push(p);
            return page.map(function(elem) {
                var pe = doc.importNode(elem, true);
                cache.insertPoint.parentNode.insertBefore(pe, cache.insertPoint);
                return pe;
            });
        },
        onFailure: function(res) {
            logger.log('onFailure');
            var context = res.req.options.context;
            var url = res.req.options.url;
            var cache = context.cache[url];
            cache.isLoading = false;
            logger.echoerr('nextlink: loading failed. ' + '[' + res.status + ']' + res.statusText + ' > ' + res.req.url);
            res.req.options.context.setCache(res.req.options.url, 'terminate', cache.curPage);
        },
        focusPagenavi: function(context, url, page) {
           var elem, p;
           try {
               elem = context.cache[url].mark[page - 2];
               p = $U.getElementPosition(elem);
               window.content.scrollTo(0, p.top);
           } catch (e) {
               logger.log('focusPagenavi: err ' + page + ' ' + e);
           }
       }
    };//}}}
    var FollowLink = function() {};//{{{
    FollowLink.prototype = {
        onLocationChange: function(context, url, isCallerLoaded) {
            var cache = context.cache[url];
            var doc = cache.doc;
            var elem;
            //var matches = buffer.evaluateXPath(this.cache[url]);
            //for each (let match in matches) elem = match;
            $U.getNodesFromXPath(cache.xpath, doc, function(item) elem = item, this);
            var nextURL = $U.pathToURL(elem, doc);
            var xpath = ['a', 'link'].map(function(e) 
                                     '//' + e + '[translate(normalize-space(@rel), "PREV", "prev")="prev"]')
                                     .join(' | ');
            var prev = $U.getNodesFromXPath(xpath, doc);
            if (prev.length)
                context.setCache(url, 'prev', prev[0]);
            context.setCache(nextURL, 'prev', url);
            context.setCache(url, 'next', elem);
        },
        customizeMap: function(context, url, prev, next) {
            var cache = context.cache[url];
            var doc = cache.doc;
            if (prev)
                mappings.addUserMap(context.browserModes, ['[['], 'customize by nextlink.js',
                    function(count) {
                        if (prev.href) {
                            buffer.followLink(prev, liberator.CURRENT_TAB);
                        } else {
                            liberator.open(prev, liberator.CURRENT_TAB);
                        }
                    },
                    { flags: Mappings.flags.COUNT });
            if (next)
                mappings.addUserMap(context.browserModes, [']]'], 'customize by nextlink.js',
                    function(count) { buffer.followLink(next, liberator.CURRENT_TAB); },
                    { flags: Mappings.flags.COUNT });
        }
    };//}}}
    var instance = new NextLink((isFollowLink ? new FollowLink() : new Autopager()));
    return instance;
})();
// vim: set fdm=marker sw=4 ts=4 sts=0 et: