//alert('auto links has been included!');

var latinBig         = [65, 90];
var latinSmall       = [97, 122];
var cyrillicBig      = [1040, 1071];
var cyrillic         = [1040, 1103];

var digits           = [48, 57];

// word cannot be started with the digit, but digit canbe inside
//var digitChars       = String.fromCharCode(48, 49, 50, 51, 52, 53, 54, 55, 56, 57);

// word cannot be started with the punctuation char, but it canbe inside
// There is one exception " (34) and ' (39) is valid for word begin.
//var punctuationChars = String.fromCharCode(33, 34, 39, 44, 45, 46, 58, 59, 95, 96, 171, 180, 187);

var latinChars       = String.fromCharCode(65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90);
var cyrillicChars    = String.fromCharCode(1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071);

function codesToChars(startCode, endCode)
{
    var r = '';
	for ( var i=startCode; i<=endCode; i++ )
	{
	    if ( i>startCode )
	     r = r + "\r\n";
		r = r +i+" = "+String.fromCharCode(i);
	}
	return r;
}

function trim(s)
{
	var r = "";
	for ( var i=0; i<s.length; i++ )
	{
	    var ch = s.charAt(i);
		if ( ch!=' ' && ch!='\r' && ch!='\n' && ch!='\t' )
		{
			r = s.substr(i);
			break;
		}
	}

	if ( r.length==0 )
		return r;

    for ( var i=r.length-1; i>=0; i-- )
    {
    	
	    var ch = r.charAt(i);
		if ( ch!=' ' && ch!='\r' && ch!='\n' && ch!='\t' )
		{
			r = r.substring(0, i+1);
			break;
		}
    }	
	return r;
}

function isDigitCode(charCode)
{
	if ( charCode>=digits[0] && charCode<=digits[1] )	
		return true;
	return false;	
}

function isQuotationCode(charCode)
{
    // "(34), '(39), _(95), `(96), «(171), ´(180), »(187) 
	return 	charCode==34  || charCode==39  ||  
			charCode==95  || charCode==96  ||
			charCode==171 || charCode==180 || charCode==187			
			; 	
}

function isLetterCode(charCode)
{
	if ( charCode>=cyrillic[0] && charCode<=cyrillic[1] )
		return true; 
	if ( charCode>=latinBig[0] && charCode<=latinBig[1] )
		return true; 
	if ( charCode>=latinSmall[0] && charCode<=latinSmall[1] )
		return true; 
	if ( isDigitCode(charCode) )
		return true;	
    if ( isQuotationCode(charCode) )
    	return true;		
	return false;	
}

function splitPhrase(phrase)
{
	if ( phrase==null || phrase.length==0 )
		return r;

	var r = new Array();	
	var token = '';
	var isWord = false;	

	for( var i=0; i<phrase.length; i++ )
	{
		var ch = phrase.charAt(i);
		var isChLetter = isLetterCode(phrase.charCodeAt(i));
		
		if ( i==0 )
		{
			token = ch;
			isWord = isChLetter;
			continue;
		}
		
		if ( isWord==isChLetter )
		{
			token = token + ch;
		}	
		else
		{
		    if ( token.length>0 )
		    {
		    	//alert("Found new token: "+token);
				r[r.length] = token;
			}	
			token = ch;
			isWord = isChLetter;	
		}
	}	
	if ( token.length>0 )
		r[r.length] = token;
	return r;
}

function sortAutoLinks(aLink1, aLink2)
{
	return aLink2.words.length - aLink1.words.length;
}

function getLetterIndexFromCharCode(charCode)
{
	var latinOffset = 3; 
	var cyrillicOffset = latinOffset + latinChars.length;

	// cyrilic
	if ( charCode>=cyrillicBig[0] && charCode<=cyrillicBig[1] )
	    return  cyrillicOffset + charCode - cyrillicBig[0];
	    
	// quotation chars
	if ( charCode==34 )
		return 0;
	if ( charCode==39 )
	    return 1;	 
	if ( charCode==171 )
	    return 2;	 

	// latin
	if ( charCode>=latinBig[0] && charCode<=latinBig[1] )
		return latinOffset + charCode - latinBig[0];
	
	// word cannot start with the given char	
	return -1;    	
}

function extractTextNodes(textNodeArray, node)
{
	if ( node.nodeType==3 )
		return;
	if ( node.nodeType==1 && node.nodeName=='A' )
	  return;	
	for ( var i=0; i<node.childNodes.length; i++ )
	{
		var nn = node.childNodes[i];
		if ( nn.nodeType==3 )
		{
			textNodeArray[textNodeArray.length] = nn;
			continue;
		}
		else
		{
		  if ( nn.nodeType==1 && nn.nodeName!='A' )
		    extractTextNodes(textNodeArray, nn);
		}
	}
}

function AutoLink(phrase, link, tooltip, text)
{
	//var splitted = phrase.split(/\b/);
	var splitted = splitPhrase(phrase);
	if ( splitted.length==0 )
		return null;

    // setup words	
	this.words = new Array();
	for ( var i=0; i<splitted.length; i++ )
	{
		var wordCandidate = splitted[i].toUpperCase();
		if ( wordCandidate.length<1)
			continue;
		
		// first word must be searchable	
		if ( this.words.length==0 && getLetterIndexFromCharCode(wordCandidate.charCodeAt(0))==-1 )
			continue;	
		//if ( getLetterIndexFromCharCode(wordCandidate.charCodeAt(0))!=-1 )
		//	this.words[this.words.length] = wordCandidate;	
		
		if ( !isLetterCode(wordCandidate.charCodeAt(0)) )
			continue;
			
		this.words[this.words.length] = wordCandidate;	
	}
	
	if ( this.words.length==0 )
		return null;
	
	// setup firstWord
	this.firstWord = this.words[0]; 
	
	// setup link
	this.link = link;

	// setup tooltip
	this.tooltip = tooltip;

	// setup text
	if ( text==undefined )
		text = phrase;
	this.text = text;
	
    /**
     * toString
     */	
	this.toString = function()
	{
	  return this.words+' - '+this.link;
	}
	
	this.createLink = function()
	{
		var a = document.createElement('A');
		a.href = this.link;
		a.title = this.tooltip;
		
		a.appendChild(document.createTextNode(this.text));
		return a;
	}
}

function AutoLinkTable()
{
	this.table = new Array();
	
	for ( var i=0; i<30; i++ )
	{
	    var lengthTable = new Array();
		this.table[i] = lengthTable; 

		// quotation chars " (34) ' (39) (171)
		lengthTable[0] = new Array();
		lengthTable[1] = new Array();
		lengthTable[2] = new Array();
		
		for ( var j=0; j<latinChars.length; j++ )
		{
			var index = getLetterIndexFromCharCode(latinChars.charCodeAt(j));
			lengthTable[index] = new Array();
		}		

		for ( var k=0; k<cyrillicChars.length; k++ )
		{
			var index = getLetterIndexFromCharCode(cyrillicChars.charCodeAt(k));
			lengthTable[index] = new Array();
		}
	} 
	
	this.addAutoLink = function(phrase, link, tooltip, text, doCreateQuoted)
	{
	    phrase = trim(phrase);
	    if ( phrase.length<1 )
	    	return;
	    	
	    this.createAutoLink(phrase, link, tooltip, text);
	    if ( doCreateQuoted==undefined )
	    	doCreateQuoted = true;
	    
	    if ( doCreateQuoted==false )
	    	return;
	    
	    var firstCharCode = phrase.charCodeAt(0);	
	    var lastCharCode  = phrase.length>1 ? phrase.charCodeAt(phrase.length-1) : firstCharCode;
	    
	    // check if already quoted
	    
	    if 
	    ( 
	    	(firstCharCode==34 && lastCharCode==34) ||	
	    	(firstCharCode==39 && lastCharCode==39) || 
	    	(firstCharCode==171 && lastCharCode==187) 
	    )
	    {
	    	return;
	    }
	    
	    // quote with " (34)
	    this.createAutoLink("\""+phrase+"\"", link, tooltip, text);	
	    
	    // quote with " (39)
	    this.createAutoLink("'"+phrase+"'", link, tooltip, text);	

	    // quote with " (171) and (187)
	    this.createAutoLink("«"+phrase+"»", link, tooltip, text);	
	}

	this.createAutoLink = function(phrase, link, tooltip, text)
	{
	    var autoLink = new AutoLink(phrase, link, tooltip, text);
	    var firstWord = autoLink.firstWord;
		var lengthTable = this.table[firstWord.length];
		var index = getLetterIndexFromCharCode(firstWord.charCodeAt(0));
		var letterArray = lengthTable[index]; 
		letterArray[letterArray.length] = autoLink;
		return autoLink;
	}

    /**
     * returned array of AutoLink which start with the given word
     * the array is sorted by words count in AutoLink.
     * That is the first AutoLink is 'longer' than the second and so on.      
     */ 
	this.lookup = function(word)
	{
	    var w = word.toUpperCase();
		var lengthTable = this.table[w.length];
		
		var index = getLetterIndexFromCharCode(w.charCodeAt(0));
		if ( index==-1 )
			return null;
		var letterArray = lengthTable[index];
		var candidates = new Array(); 
		for ( var i=0; i<letterArray.length; i++ )
		{
		  var al = letterArray[i];
		  if ( al.firstWord == w )
		  	candidates[candidates.length] = al;
		}
		candidates.sort(sortAutoLinks);
		return candidates;
	}
}

function Token(token, index)
{
	this.token = token;
	this.index = index;
	
	this.isWord = function()
	{
		return this.token.length>=1 && this.token.length<=29 && isLetterCode(this.token.charCodeAt(0));		
	}
/*	
	this.isBlank = function()
	{
	    if ( this.token.length==0 )
	    	return false;
		for ( var i=0; i<this.token.length; i++ )
		{
			var ch = this.token.charAt(i);
			if ( ch!=' ' && ch!='\r' && ch!='\n' && ch!='\t' && ch!='-')
				return false;
		}
		return true;
	}
*/	
	this.toString = function()
	{
		return '['+this.index+'] - @'+this.token+'@';
	}
}

function PhraseIterator(phrase)
{
	//this.splitted = phrase.split(/\b/);
    this.splitted = splitPhrase(phrase);
    
	this.pos = 0;

	/**
	 * returns true if there are more token available.
	 */
	this.hasNext = function()
	{
		return this.pos<this.splitted.length;
	}
	
	/**
	 * returns next token or null if no token is available. 
	 * and increment current position.
	 */ 
	this.next = function()
	{
	    if ( this.hasNext() )
	    {
		    var p = this.pos; 
		    this.pos = this.pos + 1;
			return new Token(this.splitted[p], p);
		}
		return null;	
	}
	
	this.findWordAfterToken = function(token)
	{
		if ( token==null )
			return null;
		
		if ( token.index>=(this.splitted.length-2) )	
			return null;

		for ( var i=token.index+1; i<this.splitted.length; i++ )
		{
			var nextToken = new Token(this.splitted[i], i);
			var isBlank = !nextToken.isWord();
			if ( i==(token.index+1) && !isBlank ) 	
			  return null;
			
			if ( isBlank )
				continue;
			if ( nextToken.isWord() )
				return nextToken;
			return null;		  
		}	
		return null;		
	}
	
	this.moveAfterToken = function(token)
	{
		if ( token==null )
			return;
		this.pos = token.index+1;	
	}
}

function bypathTextNode(textNode)
{
	var phrase = new PhraseIterator(textNode.nodeValue);
    var resultFragment = document.createDocumentFragment();
    
	var s = '';
	
	while(  phrase.hasNext() )
	{
		var token = phrase.next();
		//alert('bypathTextNode: '+token);
		
		if ( !token.isWord() )
		{
			s = s + token.token;
			continue;
		}	

		var candidates = alTable.lookup(token.token);
		if ( candidates==null || candidates.length==0 )
		{
			s = s + token.token;
			continue;
		}
		
		//alert('bypathTextNode: '+token+' found '+candidates.length+' candidates.');
		var approved  = new Array();
		var wordCount = 1;
		var wordToken = token;
		var lastApprovedToken = token;
		
		while( candidates.length>0 )
		{
			wordToken = phrase.findWordAfterToken(wordToken); 
			//alert("wordToken = "+wordToken);
			
			for ( var i=candidates.length-1; i>=0; i-- )
			{
				var candidate = candidates[i];
				//alert("candidate.words = "+candidate.words);
				
				if ( candidate.words.length<=wordCount)
				{
				    //alert('bypathTextNode:  move to approved (words.length<=wordCount) candidate = '+candidate.words+' wordCount = '+wordCount+' wordToken = '+wordToken);									
					candidates.splice(i, 1);
					approved[approved.length] = candidate;
					continue;
				}
				
				if ( wordToken==null || wordToken.token.toUpperCase()!=candidate.words[wordCount] )
				{
					//alert('bypathTextNode:  remove candidate = '+candidate.words+' wordCount = '+wordCount+' wordToken = '+wordToken);									
					candidates.splice(i, 1);
				}	
				else
				{
					//alert('bypathTextNode:  nothing happens candidate = '+candidate.words+' wordCount = '+wordCount+' wordToken = '+wordToken);									
					lastApprovedToken = wordToken;
				}
			}
			
			wordCount = wordCount + 1;
		}
		
		if ( approved.length>0 )
		{
			//alert(approved[approved.length-1]);
			resultFragment.appendChild(document.createTextNode(s));
			resultFragment.appendChild(approved[approved.length-1].createLink());
			s = '';
			phrase.moveAfterToken(lastApprovedToken);
		}	
		else
		{
			s = s + token.token
		}
	}
	
	if ( s.length>0 )
		resultFragment.appendChild(document.createTextNode(s));
	textNode.parentNode.insertBefore(resultFragment, textNode);	
	
//	textNode.parentNode.insertBefore(document.createElement('BR'), textNode);
//	textNode.parentNode.insertBefore(document.createElement('BR'), textNode);
	
	textNode.parentNode.removeChild(textNode);
}

function bypathElement(elementId)
{
	var parentNode = document.getElementById(elementId);
	if ( parentNode==null )
		return;
	var textNodes = new Array();
	extractTextNodes(textNodes, parentNode);
	
	for ( var i=0; i<textNodes.length; i++ )
	{
		bypathTextNode(textNodes[i]);
	}
} 
		
var alTable = new AutoLinkTable();