/* searchhighlight.js - Highlight search terms
   Copyright (C) 2007 Jaakko Väyrynen <jjv at iki dot fi>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   Handles quoted phrases ("hello world").
   Ignores +/- signs in front of search terms.
   Character encoding is a problem (search terms and document may not
   always match). 
   Some (non-ascii) letters at the end of the word fail to highlight.
*/


// Create span node for search word with given color scheme
function create_highlight_node(child, color) {
    var node = document.createElement('span');
    node.className = "ssearchword"; // Not used here
    node.style.backgroundColor = color;
    node.style.color = '#000000';
    node.appendChild(child);
    return node;
}

// Find recursively all terms starting from node and highlight with color
function highlightTerm(node, term, color) {
    var termlc = term.toLowerCase(); // Lowercased text for matching
    // Go trough all child nodes
    for (var i = 0; i < node.childNodes.length; i++) {
	var child = node.childNodes[i];
	if (child.nodeType == 3) { // Text node
	    var textRight = child.nodeValue; // Unsearched text
	    var spanNode = null; // Create only if needed
	    var index; // Search index in unsearched text
	    var re = new RegExp('\\b' + termlc + '\\b', 'i');
	    // Repeat until search term is not found
	    while((index = textRight.search(re)) != -1) {
		// Partition text into three sections
		textLeft = textRight.substring(0, index);
		textTerm = textRight.substr(index, term.length);
		textRight = textRight.substr(index + term.length);

		// Create extra nodes for text before and term
		leftNode = document.createTextNode(textLeft);
		termNode = create_highlight_node(document.createTextNode(textTerm), color);
		// Add created nodes, create extra node here if split
		if (spanNode == null) {
		    spanNode = document.createElement('span');
		    child.parentNode.replaceChild(spanNode,child);
		}
		spanNode.appendChild(leftNode);
		spanNode.appendChild(termNode);
	    }
	    // Add text that is left
	    if (spanNode != null) {
		rightNode = document.createTextNode(textRight);
		spanNode.appendChild(rightNode);
	    }
	} else { // Recurse, assuming text nodes have no children 
	    highlightTerm(child, term, color);
	}
    }
}

// Return an array of search terms
function parseQuery(query) {
    words = Array();
    while (query.length > 0) {
	if (query.match(/^ /)) {             // white space 
	    query = query.substr(1);
	} else if (query.match(/^\+/)) {     // +term 
	    query = query.substr(1);
	} else if (query.match(/^-/)) {      // -term 
	    query = query.substr(1);
	} else if (query.match(/^\"/)) {     // "quoted term" 
	    i = query.indexOf('"', 1); // Find closing quotes
	    if (i == -1) { // Error if not found
		words.push(query);
		return words;
	    } else {
		words.push(query.substring(1, i));
		query = query.substr(i+1);
	    }
	} else {                             // term 
	    i = query.indexOf(' ');
	    if (i == -1) { // Last term 
		words.push(query);
		return words;
	    } else {
		words.push(query.substring(0, i));
		query = query.substr(i);
	    }
	}
    }
    return words;
}

// If referrer is from google etc, highlight search terms
function searchHighlight() {
    if (!document.createElement) return;

    // Get query if it exists
    var ref = document.referrer;
    var query = null;
    if (ref.match(/http:\/\/[^/]*google\./i) || // google
	ref.match(/http:\/\/[^/]alltheweb\.com/i) || // alltheweb
	ref.match(/http:\/\/[^/]search\.live\.com/i)) { // live (msn)
	query = (ref.match(/(\?|&)q=(.*?)(&|$)/i))[2];
    } else if (ref.match(/http:\/\/[^/]search\.yahoo\.com/i)) { // yahoo
	query = (ref.match(/(\?|&)p=(.*?)(&|$)/i))[2];
    } else if (ref.match(/http:\/\/[^/]looksmart\.com/i)) { // looksmart
	query = (ref.match(/(\?|&)qt=(.*?)(&|$)/i))[2];
    } 
    // Otherwise do nothing
    if (query == null || query.match(/^\s*$/)) return;

    // Parse query terms
    try { // for unicode 
	query = decodeURIComponent(query.replace(/\+/g,' '));
    } catch (err) { 
	query = unescape(query.replace(/\+/g,' '));
    }
    var terms = parseQuery(query);

    // Create text info for document
    var spanNode = document.createElement("span");
    var infoNode = document.createTextNode("The following terms have been highlighted: ");
    spanNode.appendChild(infoNode);


    // Highlight terms and update info
    var colors = new Array('#ffff66', '#a0ffff', '#99ff99', '#ff9999', '#ff66ff', '#00aaaa', '#00aa00', '#886800', '#9999ff', '#990099');
    for (var j = 0; j < terms.length; j++) {
	var term = terms[j];
	var color = colors[j % colors.length];
	if (term && term.length > 0) {
	    highlightTerm(document.getElementsByTagName("body")[0], term, color);
	}
	// Update info text
	termNode = create_highlight_node(document.createTextNode(term), color);
	spanNode.appendChild(termNode);
	spanNode.appendChild(document.createTextNode(" "));
    }

    // Add link to show page without higlighting
    var linkNode = document.createElement('a');
    linkNode.setAttribute('href', document.location);
    linkNode.appendChild(document.createTextNode("[Remove highlighting]"));
    spanNode.appendChild(linkNode);

    // Add info text to the beginning of page body
    var body = document.getElementsByTagName("body")[0];
    body.insertBefore(spanNode, body.childNodes[0]);

}

function trySearchHighlight() {
    try {
	searchHighlight();
    } catch(err) {
	return;
    }
}

// Highlight terms when window is loaded
window.onload = trySearchHighlight;
