/**** Utilities *****/

// Dummy console.log if not available
if (window.console === undefined) {
    window.console = {log: function(){}}
} else if (!window.console.log) {
    window.console.log = function(){}
}

/**
 * Useful if the string is a url.
 * There are 3 possible return values:
 * 
 * 1) returns undefined if the parameter is not defined in the url
 * 2) returns null if the parameter is present but has not a specified value (es: url?param&otherParams)
 * 3) returns the value of the parameter if present and correctly specified
 * 
 * If the second argument is specified, then it will be returned in 1) and 2)
 * 
 * @author Andrea Zilio
 */
String.prototype.urlParam = function(name, default_value) {
  var regex = new RegExp("(?:&amp;|&|\\?)" /* & o ? */ +
    RegExp.escape(name) /* the parameter name */ +
    "(?:=([^&]*))?" /* (=...)? */ + 
    "(?:&|$)" /* & o fine */
  , "g");
  
  var matches = null;
  while((last = regex.exec(this)) != null) {
    matches = last;
  }
  
  // now matches has the last match in the string
  if (matches === null) {
    // not found
    return default_value === undefined ? undefined : default_value;
  } else  {
    // Param present
    if( matches[1] === undefined ) {
      // Parameter without a value (ex: page.html?param)
      return default_value === undefined ? null : default_value;
    } else {
      // Parameter present with a value
      return decodeURIComponent(matches[1]);
    }
  }
  
}

RegExp.escape = (function() {
  var specials = [
    '/', '.', '*', '+', '?', '|', '$',
    '(', ')', '[', ']', '{', '}', '\\'
  ];

  sRE = new RegExp(
    '(\\' + specials.join('|\\') + ')', 'g'
  );
  
  return function(text) {
    return text.replace(sRE, '\\$1');
  }
})();

/**** UI Handling ***/

///var resultsCache = new QueryResultsCache(10);
var curQuery = '';
var curPmids = [];
var curData = {};

var data;
var busy = false;
var pending = false;
var page = 0;
var fuzzy = 1;
var m = 20;
var sid = Math.random();

function check(value, page) {

    if (value === undefined) value = '';

    $.address.parameter('q', typeof value == 'string' ? value : '');

	if (page === undefined) {
	    window.page=0;
	} else {
	    window.page = page;
    }
    
	$.address.parameter('page', window.page > 0 ? window.page + 1 : null);
	    
    var regex=/\+|\s/gi;
	if (value.length==0)
	{
		busy = false;
		document.getElementById('result-bar').innerHTML="";
		document.getElementById('top').innerHTML="";
		document.getElementById('bottom').innerHTML="";
		document.getElementById('instructions').style.display = 'block';
		document.getElementById('left').innerHTML="";
	}
	else if(value.replace(regex,"").length >= 3){
		document.getElementById('instructions').style.display = 'none';
		if(busy)pending = true;
		else query(value);
	}
}
function query(value) {
	
    var regex=/\+/gi;
	value = value.replace(regex,"");
	var values = value.split(' ');
    var url = "/cgi-bin/broker.py?sid="+sid+"&max="+m+"&page="+page+"&fuzzy="+fuzzy+"&q=";
    var first = 1;
	var q = "";
	for(var i = 0; i < values.length; i++) {
		if(values[i]!=''){
    		if(!first)q += "+";
    		q += values[i].toLowerCase();
			first = 0;
		}
    }
    
	url += q;
	curQuery = q;
	
    busy = true;
    self.scrollTo(0, 0);
    sendAjaxRequest(function(responseText) {
        var response = eval("(" + responseText + ")");
        display(response);
    }, url);
    
}

function display(response) {
	busy = false;                                                                                                                                                                                                                 
	
	time = response.t;
	data = response.d;

	var html = "<table class='legend_table'><tr><td><span class='legend_exact'>&nbsp;&nbsp;&nbsp;</span> exact match&nbsp;&nbsp;&nbsp;<span class='legend_fuzzy'>&nbsp;&nbsp;&nbsp;</span> fuzzy match</td><td align='right'>";

	if(data.length)
		html += "(<b>"+(time/1000)+"</b> seconds) Results <b>"
			+(page*m+1)+"</b> - <b>"+(page*m+data.length)+"</b>.";
	else
		html += "(<b>"+(time/1000)+"</b> seconds) <b>No results found</b>.";

    html += "</td></tr></table>"
	document.getElementById('result-bar').innerHTML = html;

    curPmids = [];
    curData = {};

	html = "<div id=\"abstract\"></div><div id=\"abstract-border-cover\"></div>";
	for(var i = 0; i < data.length; i++) {
	    curPmids.push(data[i].d[0]);
	    curData[data[i].d[0]] = data[i];
		var regex = prepareRegex(data[i].k, data[i].ed);
		html += "<div class='rprt' pmid=\"" + data[i].d[0] + "\">";
		html += "<p class='title'><a href='/cgi-bin/url.py?pmid=" + data[i].d[0] + "'>"
			+ highlight(data[i].d[2], regex, "highlight1", "highlight2") + "</a></p>";
		html += "<p class='authors'>" + authorsLinks(highlight(data[i].d[3], regex, "highlight1", "highlight2"), data[i].d[0]) + "</p>";
		if (data[i].d[8] != '') {
		    html += "<p class='preview'><em>Abstract preview: </em>" + highlight(textPreview(data[i].d[8], regex, 30, 600, 300), regex, "highlight1", "highlight2") + "</p>";
		} else {
		    html += "<p class='preview'><em>Abstract not available</em></p>";
		}
		html += "<p class='affiliation'>" + highlight(data[i].d[1], regex, "highlight1", "highlight2") + "</p>";
		html += "<p class='source'>"+ sourceLink(data[i].d[0], highlight(data[i].d[5], regex, "highlight1", "highlight2")) + ". " 
			+ highlight(data[i].d[4], null, "highlight1", "highlight2") + ".</p>";
		html += "<p class='keywords'>";
		var keyword = data[i].d[7];
		var keywords = keyword.split(',');
		for(var j = 0; j < keywords.length; j++) {
		    if(j)html += "&nbsp;|&nbsp;";
		    var str = keywords[j].replace(/^\s+/, '');
		    html += "<a href=\"/#/?q=" + encodeURIComponent(str) + "\">"
				+ highlight(str, regex, "highlight1", "highlight2") + "</a>";
		}
		html += "</p>";
		html += '<p class="additional">';
                html += '<a href="/cgi-bin/fulltext.py?title=' + encodeURIComponent(data[i].d[2]) + '&amp;pmid=' + data[i].d[0] + '">Full text</a> - ';
                html += '<a href="http://scholar.google.com/scholar?q=' + encodeURIComponent(data[i].d[2]) + '">Google Scholar</a> - ';
                html += '<a class="bibtexLink" href="#bibtex?pmid=' + data[i].d[0] + '">BibTex</a> - ';
				html += '<a target="_blank" href="/cgi-bin/endnote.py?title=' + encodeURIComponent(data[i].d[2]) + '&amp;pmid=' + data[i].d[0] + '">Endnote</a></p>'
		html += "</div>";
	}

	document.getElementById('left').innerHTML=html;


	html = "";

	if(page) 
		html += "<a style='text-decoration: none;' href='#firstPage'>&#171;&#160;First</a> "
		+ "&nbsp;&nbsp;<a style='text-decoration: none;' href='#previousPage'>Previous</a> "
		+ "&nbsp;&nbsp;<a href='#previousPage'>"+page+"</a> &nbsp;|&nbsp;";

	if(page || data.length >= m)
		html += (page+1);

	if(data.length >= m)
		html += "&nbsp;|&nbsp;<a href='#nextPage'>"+(page+2)+"</a>&nbsp;&nbsp;"
		+ "<a style='text-decoration: none;' href='#nextPage'>Next</a>";

       
	document.getElementById('top').innerHTML = html;
	document.getElementById('bottom').innerHTML = html;


    if(pending) {
        pending = false;
        query(document.getElementById('input').value);
    }

}

function prepareRegex(keys, ed) {
    var regex1 = "([^A-Za-z0-9_&])(";
    var regex2 = "([^A-Za-z0-9_&])(";
    var j1 = 0;
    var j2 = 0;
	
	// Sort keys and ed (into keywords) from the longest keyword to the shortest... This way we highlight both even if a keyword is the prefix of another one
	
	var keywords = [];
	
	for(var i = 0; i < keys.length; i++) {
		keywords[i] = {'key': keys[i], 'ed': ed[i]};
	}
	
	keywords.sort(function(a,b) {
		return b.key.length - a.key.length;
	});
	
    for(var k = 0; k < keywords.length; k++)
        if(keywords[k].key.length)
			if(keywords[k].ed == 0) {
		        if(j1)regex1 += "|";
		        regex1 += keywords[k].key.replace(/\W/g, 
		            function(sub){return "\\" + sub;});
		        j1++;
			}
			else {
		        if(j2)regex2 += "|";
		        regex2 += keywords[k].key.replace(/\W/g, 
		            function(sub){return "\\" + sub;});
		        j2++;
			}

    regex1 += ")";
	regex2 += ")";
    return [ (j1)?new RegExp(regex1, "gi"):null, (j2)?new RegExp(regex2, "gi"):null ];
}

function authorsLinks(authorsList, pmid) {

    var ret = '';   // Initialize to the empty string (we will construct the string step by step)

    authors = authorsList.split(/\s*,\s*/); // Get the authors (tokenizing on the comma)
    
    // We iterate over the authors and...
    var i;
    for(i=0;i<authors.length; i++) {
        
        // We create the link for this author
        ret += (ret == '' ? '' : ', ') + '<a href="/cgi-bin/author_link.py?pmid=' + 
            pmid + '&amp;num=' + i + '" target="_blank">' + 
            authors[i] + '</a>';
        
    }
    
    return ret;

}

function sourceLink(pmid, linkBody) {

    return '<a href="/cgi-bin/journal_link.py?pmid=' + pmid + '">' + linkBody + '</a>';

}

function highlight(string, regexp, stylec1, stylec2) {
    var str = " " + string.replace(/</g, "&lt;");
	if(regexp == null)
		return str;
    if(regexp[0] != null)
		str = str.replace(regexp[0], function(sub, m1, m2) {
		        return m1 + "<span class='" + stylec1 + "'>" + m2 + "</span>";
		    });
	if(regexp[1] != null)
	    str = str.replace(regexp[1], function(sub, m1, m2) {
            return m1 + "<span class='" + stylec2 + "'>" + m2 + "</span>";
        });
    return str.replace(/^\s+/, '');
}

function textPreview(string, regexp, padding, maxLength, noMatchesLength) {
    var str = " " + string.replace(/</g, "&lt;");
	if(regexp == null)
		return str;
		
    var matches = [];
		
    if(regexp[0] != null)
		str = str.replace(regexp[0], function(sub, m1, m2, offset) {
		        matches.push({start: offset, length: m2.length});
		        return sub;
		    });
	if(regexp[1] != null)
	    str = str.replace(regexp[1], function(sub, m1, m2, offset) {
            matches.push({start: offset, length: m2.length});
		    return sub;
        });
        
    
    matches.sort(function(a,b) {
    
        return a.start - b.start;
    
    });
    
    
    var snippets = [];
    var curLength = 0;
    
    var i;
    for(i=0;i<matches.length;i++) {
        
        if (i > 0 && matches[i].start - padding <= snippets[snippets.length-1].end + 1) {
            // Just extend previous snippet
            curLength -= snippets[snippets.length-1].end - snippets[snippets.length-1].start + 1;
            snippets[snippets.length-1].end = Math.min(string.length-1, matches[i].start + matches[i].length + padding - 1);
            curLength += snippets[snippets.length-1].end - snippets[snippets.length-1].start + 1;
        } else {
            snippets.push({
                start: Math.max(0, matches[i].start - padding),
                end: Math.min(string.length-1, matches[i].start + matches[i].length + padding - 1)
            });
            // Don't truncate the word at the beginning of the snippet
            var added = snippets[snippets.length-1];
            if (!/\s/.test(string.charAt(added.start))) {
                while(added.start > 1 && !/\s/.test(string.charAt(added.start-1))) {
                    added.start--;
                }
            }
            curLength += snippets[snippets.length-1].end - snippets[snippets.length-1].start + 1;
        }
        
        if (curLength >= maxLength) {
            break;
        }
        
    }
    
    // join snippets and add '...'
    var ret = '';
    if (snippets.length > 0 && snippets[0].start > 0) {
        ret += '&hellip;';
    }
    var lastEnd = 0;
    for(i=0;i<snippets.length;i++) {
        ret += (ret == '' || ret == '&hellip;' ? '' : '&hellip;') + string.substring(snippets[i].start,snippets[i].end+1)
        lastEnd = snippets[i].end;
    }
    
    // If we have not reached the end of the string
    if (ret != '' && lastEnd < string.length-1) {
        // if we have still space... expand the last snippet
        if (ret.length < maxLength) {
            ret += string.substr(lastEnd+1, maxLength - ret.length);
            if (lastEnd + (maxLength - ret.length) < string.length - 1) {
                ret += '&hellip;';
            }
        } else {
            // Truncate to the curLength (which is close to maxLength since we stopped immediately after curLength was bigger then maxLength
            ret = ret.substr(0, curLength) + '&hellip;';
        }
    }
    
    // If there were no matches in the string just truncate it at noMatchesLength from the beginning
    if (ret == '') {
        ret = string.substr(0, noMatchesLength) + (noMatchesLength < string.length ? '&hellip;' : '');
    }
    
    return ret;
    
}

function enableLink(linkid,textid) {
	var link=document.getElementById(linkid);
	var text=document.getElementById(textid);
	text.style.display="none";
	link.style.display="";
}
function disableLink(linkid, textid) {
	var link=document.getElementById(linkid);
	var text=document.getElementById(textid);
	link.style.display="none";
	text.style.display="";

}

function sendAjaxRequest(callback, url) {
  // Provide the XMLHttpRequest class for stupid IE 5.x-6.x:
  if( typeof XMLHttpRequest == "undefined" ) XMLHttpRequest = function() {
    try { return new ActiveXObject("Msxml2.XMLHTTP.6.0") } catch(e) {}
    try { return new ActiveXObject("Msxml2.XMLHTTP.3.0") } catch(e) {}
    try { return new ActiveXObject("Msxml2.XMLHTTP") } catch(e) {}
    try { return new ActiveXObject("Microsoft.XMLHTTP") } catch(e) {}
    throw new Error( "This browser does not support XMLHttpRequest." )
  };

  var request =  new XMLHttpRequest();
 
  request.onreadystatechange = function() {
    if (request.readyState == 4 && request.status == 200) {
      if (request.responseText) {
        callback(request.responseText);
      }
    }
  };
  
  request.open("get", url);
  request.send(null);
}

$(function() {

    document.getElementById('input').focus();
    
    // Bibtex
    $('a.bibtexLink').live('click', function(e) {
        
        var $this = $(this);
        
        var $popup = $('<div id="bibtex"></div>');
        
        $popup.appendTo($(document.body));
        
        $popup.html('<textarea readonly="readonly"></textarea><a href="#bibtexClose">Close</a>');
        $popup.find('a').click(function(e) {
            
            $popup.remove();
            
            e.preventDefault();
        });
        
        $popup.offset({'left': $this.offset().left, 'top': $this.offset().top + $this.outerHeight()});
        
        var pmid = this.href.urlParam('pmid');
        
        $popup.find('textarea').val('Loading...');
		
		$.getJSON('cgi-bin/bibtex.py', {
			'pmid': pmid,
			'title': curData[pmid].d[2]
		}, function(data) {
			$popup.find('textarea').val(data.bibtex);
		}).error(function(jqXHR, desc, error) {
			$popup.find('textarea').val("Sorry, we're unable to generate the BibTex for the specified document.\nPlease try again later.");
		});
        
        $popup.show();
        
        e.preventDefault();
    });
    
    // Fuzzy On/Off
    $('a[href$=#fuzzyOn]').live('click', function (e) {
        fuzzy=1;
        
        $.address.parameter('fuzzy', '1');
        check($.address.parameter('q'), page);
        
        disableLink('on','onText');
        enableLink('off','offText');
        
        e.preventDefault();
    });
    $('a[href$=#fuzzyOff]').live('click', function (e) {
        fuzzy=0;
        
        $.address.parameter('fuzzy', '0');
        check($.address.parameter('q'), page);
        
        disableLink('off','offText');
        enableLink('on','onText');
        
        e.preventDefault();
    });
    
    // Pagination
    $('a[href$=#previousPage]').live('click', function (e) {
    
        page--;
        $.address.parameter('page', page+1);
        check($.address.parameter('q'), page);
    
        e.preventDefault();
    });
    $('a[href$=#nextPage]').live('click', function (e) {
    
        page++;
        
        $.address.parameter('page', page+1);
        check($.address.parameter('q'), page);
    
        e.preventDefault();
    });
    $('a[href$=#firstPage]').live('click', function (e) {
    
        page = 0;
        $.address.parameter('page', page+1);
        check($.address.parameter('q'), page);
    
        e.preventDefault();
    });
    
    // Abstracts
    function showAbstract(pmid) {
    
        var $rprt = $('.rprt[pmid=' + pmid + ']');
        
        var abstractText = curData[$rprt.attr('pmid')].d[8];
        
        var $abstract = $('#abstract');
        var $abstractBorderCover = $('#abstract-border-cover');
        $abstract.attr('pmid', pmid).show();
        $abstract.css('min-height', 0);
        $abstractBorderCover.attr('pmid', pmid).show();
        
        var highlightRegex = prepareRegex(curData[pmid].k, curData[pmid].ed);
        
        if (abstractText != '') {
            $abstract.html('<h3>Abstract:</h3><p>' + highlight(abstractText, highlightRegex, 'highlight1', 'highlight2') + '</p>');
        } else {
            $abstract.html('<h3>Abstract:</h3><p><em>Abstract is not available</em></p>');
        }
        
        var top = $rprt.offset().top;
        var left = $rprt.offset().left + $rprt.outerWidth() - 1;
        
        var abstractTop = top;
        var abstractLeft = left;
        if(abstractTop + $abstract.height() > $(document).scrollTop() + $(window).height()) {
            abstractTop =  $(document).scrollTop() + $(window).height() - $abstract.height();
        }
        
        $abstract.offset({left: abstractLeft, top: abstractTop});
        $abstract.css('min-height', $rprt.outerHeight() - 2 + (top - abstractTop));
        $abstractBorderCover.offset({left: left, top: top + 1});
        $abstractBorderCover.height($rprt.outerHeight() - 2);
        
    }
    function hideAbstract(pmid) {
        $('#abstract[pmid=' + pmid + ']').hide();
        $('#abstract-border-cover[pmid=' + pmid + ']').hide();
    }
    
    $('.rprt').live('mouseenter', function(e) {
        $('.active-rprt').removeClass('active-rprt');
        var $rprt = $(this);
        
        $rprt.addClass('active-rprt');
    
        showAbstract($rprt.attr('pmid'));
        
    });
    var overAbstractForPmid = null;
    $('.rprt').live('mouseleave', function(e) {
        var $this = $(this);
        window.setTimeout( function() {
            if (overAbstractForPmid == $this.attr('pmid')) return;
            $this.removeClass('active-rprt');
            hideAbstract($this.attr('pmid'));
        }, 100);
    });
    $('#abstract').live('mouseenter', function(e) {
        var pmid = $('.active-rprt').attr('pmid');
        overAbstractForPmid = pmid;
    });
    $('#abstract').live('mouseleave', function(e) {
        hideAbstract($('.active-rprt').attr('pmid'));
        $('.active-rprt').removeClass('active-rprt');
        overAbstractForPmid = null;
    });

});

// Deep linking

$.address.externalChange(function(e) {

    $('#input').val(e.parameters['q']);
    
    if ($.address.parameter('page') !== undefined) {
        page = parseInt($.address.parameter('page')) - 1;
    } else {
        page = 0;
    }
    
    if ($.address.parameter('fuzzy') !== undefined) {
        fuzzy = parseInt($.address.parameter('fuzzy'));
    }
    
    if (fuzzy == 0) {
        disableLink('off','offText');
        enableLink('on','onText');
    } else {
        disableLink('on','onText');
        enableLink('off','offText');
    }
    
    check($.address.parameter('q'), page);

});

