﻿
var Highlight = {

		__constructor : function( init ){

			//this.debug_pack = document.open('about:blank' , '_blank' , 'toolbar=yes,location=no,status=yes,menubar=yes,scrollbars=yes,resizable=yes,width=600,height=400,top=100,left=100');

			this.error = new Array();
			this.doc = document;
			this.body = this.doc.documentElement;
			this.str = { 'a' : 'a' , 'span' : 'span' , 'br' : 'br' , 'convertBR' : 'igear_br' };
			this.variable = new Object();
			this.foundIt( init , this );
			this.appending( this.nodeList );
		},

		appending : function( element ){

			for ( var i = 0 , stop = element.length; i < stop; i++ ){
			
				if ( this.type in this.langs ) this.begin( element[i] );
				else 
					this.error.push( 1000 );
					//this.error.push( this.defaultLang + 'but no such language exists' );
			}
		},

		begin : function ( element ){

				this.variable.element = element;
				this.variable.documentFragment = this.doc.createDocumentFragment();
				this.variable.currentParent = this.variable.documentFragment;

				this.variable.currentText = null;
				this.variable.stack = new Array();

				this.variable.span = this.doc.createElement( this.str.span );
				this.variable.a = this.doc.createElement( this.str.a );
				this.variable.br = this.doc.createElement( this.str.br );

				if ( this.variable.element.childNodes.length === 0 ) return this.error.push( 1001 );
				else 
					this.variable.inputString = this.getText( this.variable.element );

				var i = this.variable.i - 1;

				if( i >= 0 )
				do { this.variable.element.removeChild( this.variable.element.firstChild ); }
				while ( i-- );

				this.play();
				this.close();

			return;
		},

		play : function(){
				
				this.variable.pos = 0;
				this.variable.currentStyle = undefined;
			
				var inputStringLength = this.variable.inputString.length;
				var endOfLinePattern = null;
				var endOfLinePattern = new RegExp( '\r\n|\r|\n' , 'g' );
				
				//endOfLinePattern.lastIndex = 0;

				while ( this.variable.pos  < inputStringLength ){

					var start = this.variable.pos;
					var startOfNextLine;
					var end;
					var endOfLineMatch = endOfLinePattern.exec( this.variable.inputString );

					if ( endOfLineMatch === null ){

						end = inputStringLength;
						startOfNextLine = inputStringLength;
					}
					else {

						end = endOfLineMatch.index;
						startOfNextLine = endOfLinePattern.lastIndex;
					}

					var line = this.variable.inputString.substring( start , end );
					var matchCache = null;
					var matchCacheState = -1;

					while ( true ){

						//this.debug_pack.document.writeln(  this.variable.pos + ', ' + start  + '<br/>' );

						var posWithinLine = this.variable.pos - start;
						var pattern = this.getTop( this.variable.stack );
						var stateIndex = typeof pattern === this.ty.not ? 0: pattern.next;
						var state = this.langs[this.type][stateIndex];
						var numPatterns = state.length;
						var bestMatch = null;
						var bestMatchIndex = -1;

						//this.debug_pack.document.writeln( ' pos : ' + posWithinLine + ' , pattern : ' + pattern + ' , stateIndex : ' + stateIndex + ' , state : ' + state +  ' , numPatterns : ' + numPatterns + ' , bestMatch : ' + bestMatch + ' , bestMatchIndex : ' + bestMatchIndex +   '<br/>');

						if ( stateIndex !== matchCacheState )
							matchCache = new Array();

						for ( var i = 0; i < numPatterns; i++ ){

							var match = null;

							if ( stateIndex === matchCacheState && ( matchCache[i] === null || posWithinLine <= matchCache[i].index )) 
								match = matchCache[i];
							else {

								var regex = state[i].regex;
								regex.lastIndex = posWithinLine;
								match = regex.exec( line );
								matchCache[i] = match;
							}

							if ( match !== null && ( bestMatch === null || match.index < bestMatch.index )){

								//if( typeof this.debug_pack != this.ty.not ) this.debug_pack.document.writeln( ' No ~ ' + matchCache.length + '  ::  ' + match.index + ' , ' + bestMatchIndex + '   <br/>' );

								bestMatch = match;
								bestMatchIndex = i;
							}
						}

						matchCacheState = stateIndex;

						if ( bestMatch === null ){

							this.output( line.substring( posWithinLine ) , null );
							break;
						}
						else {

							if ( bestMatch.index > posWithinLine )
								this.output( line.substring( posWithinLine , bestMatch.index ) , null );

							pattern = state[bestMatchIndex];

							var newStyle = pattern.style;
							var matchedString;

							// style matching
							if ( newStyle instanceof Array ){
								for ( var subexpression = 0 , stop = newStyle.length; subexpression < stop ; subexpression++ ){

									matchedString = bestMatch[subexpression + 1];
									this.output( matchedString, newStyle[subexpression] );
								}
							}
							else {

								matchedString = bestMatch[0];
								this.output( matchedString, newStyle );
							}

							if ( 'next' in pattern ) this.variable.stack.push( pattern );
							else {

								if ( 'exit' in pattern ) this.variable.stack.pop();
								if ( 'exitall' in pattern )
									this.variable.stack = new Array();
							}
						}

					}

					if ( this.variable.currentStyle ) this.endElement();
					if ( endOfLineMatch ) this.appendText( endOfLineMatch[0] );

					this.variable.currentStyle = undefined;
					this.variable.pos = startOfNextLine;
				}
		},

		output : function( str , style ){

			var length = str.length;

			if ( length === 0 ) return;
			if ( !style ){

				var pattern = this.getTop( this.variable.stack );
				if( pattern !== undefined && !( 'state' in pattern ))
					style = pattern.style;
			}

			if (this.variable.currentStyle !== style){

				if ( this.variable.currentStyle ) this.endElement();
				if ( style )
					this.startElement( style );
			}

			if( style == this.str.convertBR ) this.variable.currentParent.parentNode.appendChild( this.variable.br.cloneNode(false));
			else this.appendText( str );

			this.variable.pos += length;
			this.variable.currentStyle = style;
		},

		startElement : function( style ) {

				// only Text
				if (this.variable.currentText !== null){

					this.variable.currentParent.appendChild( this.doc.createTextNode( this.variable.currentText ));
					this.variable.currentText = null;
				}

				var span = this.variable.span.cloneNode( true );
				this.variable.currentParent.appendChild( span );
				this.variable.currentParent = span;
				span.className = style;
		},

		endElement : function(){

				if ( this.variable.currentText !== null ){

						if ( this.variable.currentParent.className === 'igear_url' ){

							var a = this.variable.a.cloneNode( true );
							var url = this.variable.currentText;

							this.variable.currentParent.appendChild( a );

							a.appendChild( this.doc.createTextNode( this.variable.currentText ));
							a.className = 'igear_url';

							if (url.length > 0 && url.charAt(0) === '<' && url.charAt(url.length - 1) === '>')
								url = url.substr(1, url.length - 2);

							if ( /^mailto:/.test( url ) && url.indexOf( '@' ) !== -1 )
								url = 'mailto:' + url;

							a.setAttribute( 'href' , url );
						}
						else this.variable.currentParent.appendChild( this.doc.createTextNode( this.variable.currentText ));

					this.variable.currentText = null;
				}

			this.variable.currentParent = this.variable.currentParent.parentNode;
		},

		appendText : function( str ){

			if ( this.variable.currentText === null )
				this.variable.currentText = str;
			else
				this.variable.currentText += str;
		},

		close : function() {

			if (this.variable.currentText !== null){

				this.variable.currentParent.appendChild( this.doc.createTextNode( this.variable.currentText ));
				this.variable.currentText = null;
			}

			this.variable.element.appendChild( this.variable.documentFragment );
			this.variable = new Object();
		},

		getText : function( element ){

				switch( element.nodeType ){
					
					case 3 :
					case 4 : {
						
						return element.data;
						break;
					};

					default : {

						if( element.nodeName.toLowerCase() === this.str.br ) return '\\n';

							for (var i = 0 , result = new String() , stop = element.childNodes.length; i < stop; i++)
								result += this.getText( element.childNodes.item(i));
					};
				}

			return this.variable.i = i , result;
		},

		getTop : function( stack ){

				var length = stack.length;
				if ( length === 0 ) return undefined;

			return stack[length - 1];
		},

		pop: function( stack ) {

				if ( stack.length === 0 ) this.error.push( 1002 );
			return stack.pop();
		}

};

