(function($, global) {

var queue = {},
    pmidre = /\d+/,
    trimre = /(^\s+|\s+$)/g,
    trim = function(str) { return str.replace(trimre, str) },
    proto = "prototype",
    entrezajax = {},
    identity = function() { },
    pmidExtractor = function(n) { return ($(n).text().match(pmidre) || [ null ])[0] },
    defaultFormat = "<span class='authors'>%AuthorList</span>. <a href='http://www.ncbi.nlm.nih.gov/pubmed/%Id'><em class='title'>%Title</em></a> <span class='source'>%Source</span>. <span class='issue'>%SO</span>. PubMed PMID: <span class='pmid'>%Id</span>.",
    tokenizer = /(%\w*|%\{\w+\}|[^%]*)/g,
    methods = [ "esearch", "elink", "espell", "einfo", "esummary", "efetch", "esearch+esummary", "esearch+efetch", "esearch+elink", "elink+esummary", "elink+efetch" ],
    copy = function(from, to) {
        to = to || {};
        for (var p in from) {
            if (!from.hasOwnProperty || from.hasOwnProperty(p))
                to[p] = from[p];
        }
        return to;
    };


function EntrezAjax(opts) {
    var baseUrl = opts.baseUrl || "http://entrezajax.appspot.com/",
        apiKey = opts.apiKey,
        db = opts.db || "pubmed",
        makeRequest = function(method, params, success, error) {
            params = copy(params);
            error = error || identity;

            return $.getJSON(baseUrl + method + "?callback=?",
                copy({
                    db: db,
                    apikey: apiKey
                }, params),
                function(data) {
                    if (!data.entrezajax || data.entrezajax.error) {
                        error(data);
                    } else {
                        success(data.result);
                    }
                }
            ).error(function() {
                error({});
            });
        },
        entrez = {};

    $.each(methods, function() {
        entrez[this] = (function(method) {
                return function(params, success, error) {
                    return makeRequest(method, params, success, error);
                };
            })(this);
    });

    return entrez;
}


var pubmed = EntrezAjax({ apiKey: "6b19beeaedade171ecc320ddd87f7ae6", db: "pubmed" }),
    pubMedSummary = function(doc, format) {
        var tokens = format.match(tokenizer),
            formatted = $.map(tokens, function(token) {
                if (token[0] == "%") {
                    var prop = token.substring(1),
                        val = doc[prop];
                    if (val != undefined)
                        token = val.join ? val.join(", ") : val;
                }
                return token;
            }).join("");

        return $("<span class='pretty-pmid' />").html(formatted);
    },
    waitAndCall = function(cb, wait, max) {
        var waitTimeoutId = null,
            startedAt,
            wrapped = function() {
                var now = (new Date()).getTime(),
                    cbthis = this,
                    cbargs = arguments,
                    tid = setTimeout(function() {
                        if (tid == waitTimeoutId)
                            waitTimeoutId = null;
                        cb.apply(cbthis, cbargs);
                    }, wait);

                if (waitTimeoutId !== null && now - startedAt < max) {
                    clearTimeoutId(waitTimeoutId);
                } else {
                    startedAt = now;
                }

                waitTimeoutId = tid;
            };
        wrapped.unwrap = function() { return cb };
        return wrapped;
    },
    makeAllRequests = waitAndCall(function(format) {
        var ids = $.map(queue, function(nodes, id) { return id }),
            queued = queue;

        pubmed.esummary({ id: ids.join(",") }, function(docs) {
            for (var i = docs.length; i--;)
                $.each(queued[docs[i].Id] || [], function() {
                    $(this.node)
                        .removeClass("pmid pretty-please")
                        .empty()
                        .append(pubMedSummary(docs[i], this.format));
                });
        });

        queue = {};
    }, 500, 2000);



/**
 * Turns a set of PubMed IDs (PMIDs) into a prettier version. This can be
 * called with no arguments, or it can be passed an option argument. This
 * option argument can have 2 properties format and pmid.
 *
 * The pmid option let's you specify an "extractor" function for a PubMed
 * ID. For each context node, the PubMed ID is extracted using this function
 * by calling it with the node as a single argument.
 *
 * The format option let's you specify the format of the output. This is done
 * using a string (with intermixed HTML) with various "keywords", prepended
 * with a '%', as placeholders for a document's attributes. Allowed keywords
 * simply map to the values returned by Entrez's esummary web service.
 *
 * The default format string is:
 *
 * "<span class='authors'>%AuthorList</span>.
 * <em class='title'>%Title</em>
 * <span class='source'>%Source</span>.
 * <span class='issue'>%SO</span>.
 * PubMed PMID: <span class='pmid'>%Id</span>."
 *
 * @param opts An optional options argument.
 */
$.fn.prettyPubMedId = function(opts) {
    opts = copy(opts, { format: defaultFormat, pmid: pmidExtractor });
    this.each(function() {
        var pmid = opts.pmid(this);
        if (pmid)
            (queue[pmid] = queue[pmid] || []).push({ node: this, format: opts.format });
    });
    if (this.length)
        makeAllRequests(opts.format);
    return this;
};


$(document).ready(function() {
    $(".pmid.pretty-please").prettyPubMedId();
});

})(jQuery, window);

