/**************************************\
  PSST - Practical Server-Side Titling
  
  JavaScript portion
\**************************************/

if (! PSST) { var PSST = new Object; };
if (! PSST.ElementSelection ) { PSST.ElementSelection = new Object(); };

PSST.BRTagsRegEx = /<br *\/?>/gi;
PSST.SUPTagsRegEx = /<\/?sup( [^>]*)?>/gi;
PSST.SUBTagsRegEx = /<\/?sub( [^>]*)?>/gi;
PSST.BRTagsRegEx = /<br *\/?>/gi;
PSST.AllTagsRegEx = /<\/?[^>]+>/gi;
PSST.AllWhitespaceRegEx = /\s+/gi;
PSST.ForbiddenAltRegEx = /[\u0001-\u0019\u8000-\uFF7F]/g;
PSST.BreakCharacter = String.fromCharCode( 0xE48B );
PSST.SuperscriptCharacter = String.fromCharCode( 0xE800 );
PSST.SubscriptCharacter = String.fromCharCode( 0xE801 );

PSST.TargetURL = "You must call PSST.init before using this";
PSST.ReplacementSelectors = {};
PSST.PreloadedImages = [];
PSST.escape = encodeURIComponent || escape;

PSST.init = function( url ) {
	PSST.TargetURL = url;
	var sl, oldsl, slspec, oldslspec;
	for (var i = 1; i<arguments.length; i++) {
		sl = PSST.ElementSelection.parseSelectString(arguments[i]);
		oldsl = PSST.ReplacementSelectors[ PSST.ElementSelection.normalizedSelector(sl) ];
		if (oldsl) {
			slspec = PSST.specificity(sl);
			oldslspec = PSST.specificity(oldsl);
			if ( oldslspec > slspec ) { continue; }
		}
		PSST.ReplacementSelectors[ PSST.ElementSelection.normalizedSelector(sl) ] = sl;
	}
};

PSST.withDOM = function() {
	var selectorlist, selectorurl, replacedelements, elementtext, targetelement;
	var i, j, hovers, img;
	
	for( i in PSST.ReplacementSelectors ) {
		selectorlist = PSST.ReplacementSelectors[i];
		selectorurl = PSST.escape(PSST.ElementSelection.normalizedSelector(selectorlist));
		
		replacedelements = PSST.ElementSelection.getReplaced(selectorlist);
		hovers = selectorlist[selectorlist.length-1].hover;
		
		for( j=0; j<replacedelements.length; j++ ) {
			targetelement = replacedelements[j];
			elementtext = PSST.normalizeText(targetelement.innerHTML);
			
			targetelement.style.visibility = "visible";
			targetelement.innerHTML = '<img src="' + PSST.TargetURL + "?sel=" + selectorurl + "&text=" + PSST.escape(elementtext) + '" alt="' + elementtext.replace(PSST.ForbiddenAltRegEx,'').replace('"','&quot;') + '" class="PSST" style="border:none;" />';
			if ( hovers ) {
				targetelement._psst = {};
				targetelement._psst.onmouseout = targetelement.onmouseout;
				targetelement._psst.onmouseover = targetelement.onmouseover;
				targetelement.onmouseover = function () {					
					targetelement.getElementsByTagName("img")[0].src = PSST.TargetURL + "?sel=" + selectorurl + "%3Ahover&text=" + PSST.escape(elementtext);
					if ( targetelement._psst.onmouseover ) { targetelement._psst.onmouseover(); }
				}
				targetelement.onmouseout = function () {					
					targetelement.getElementsByTagName("img")[0].src = PSST.TargetURL + "?sel=" + selectorurl + "&text=" + PSST.escape(elementtext);
					if ( targetelement._psst.onmouseout ) { targetelement._psst.onmouseout(); }
				}
				img = new Image();
				img.src = PSST.TargetURL + "?sel=" + selectorurl + "%3Ahover&text=" + PSST.escape(elementtext);
				PSST.PreloadedImages.push(img);
			}
		}
	}
};

PSST.normalizeText = function( text ) {
		return text.replace(PSST.SUPTagsRegEx, PSST.SuperscriptCharacter).replace(PSST.SUBTagsRegEx, PSST.SubscriptCharacter).replace(PSST.BRTagsRegEx, PSST.BreakCharacter).replace(PSST.AllTagsRegEx, '').replace(PSST.AllWhitespaceRegEx, ' ').replace(PSST.BreakCharacter, "\n");
}

PSST.ElementSelection.normalizedSelector = function ( selectorlist ) {
	// Ignores all pseudo-elements.
	var result = "";
	for( var i=0; i<selectorlist.length; i++ ) {
		result += " " + selectorlist[i].normalized;
	}
	return result.substring(1); // trim leading space
}

PSST.specificity = function ( selectorlist ) {
	var result = 0;
	for( var i=0; i<selectorlist.length; i++ ) {
		result += selectorlist[i].specificity;
	}
	return result;
}

PSST.ElementSelection.getReplaced = function( selectors ) {
	var i, j;
	
	var parents, results;
	
	parents = [ document.documentElement ];
	results = [];
	for( i=0; i<selectors.length; i++ ) {
		for( j=0; j<parents.length; j++ ) {
			results = results.concat( PSST.ElementSelection.getWithinElementOneClause( selectors[i], parents[j] ) );
		}
		parents = results;
		results = [];
	}
	
	return parents;
}

PSST.ElementSelection.getWithinElementOneClause = function( clause, element ) {
	var result = [];
	if ((! element) || (! clause)) { return result; }

	var possibles;
	if ( clause.tag ) {
		possibles = element.getElementsByTagName( clause.tag );
	} else {
		possibles = element.getElementsByTagName( "*" );
	}
	
	var curr, currclasses, i, j, classok;
	for(i=0; i<possibles.length; i++) {
		curr = possibles[i];
		classok = true;
		if ((clause.id) && (curr.id != clause.id)) { continue; };
		if (clause.classes.length > 0) {
			currclasses = " " + curr.className + " ";
			for( j=0; j<clause.classes.length; j++) {
				if( currclasses.indexOf( clause.classes[j] ) == -1 ) {
					classok = false;
				}
			}
		}
		if (classok) { result.push(curr); };
	}
	
	return result;
}

PSST.ElementSelection.parseSelectString = function( selectstring ) {
	var res = [];
	var rawselects = selectstring.split(' ');
	var i, s;
	
	for(i=0; i<rawselects.length; i++) {
		s = PSST.ElementSelection._parseSelectClause( rawselects[i] );
		if ( s ) { res.push(s); };
	}
	
	return res;
}

PSST.ElementSelection._parseSelectClause = function ( clausestring ) {
	var selector = { tag:null, id:null, classes:[], hover:false, normalized:"", innermostnormalized:"", specificity:0 };
    var modifier, name, rest;
	var cleanclasses = [];
	
	if (clausestring == '') { return null; }

    while (match = clausestring.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) {
      modifier = match[1], name = match[2], rest = match[3];
      switch (modifier) {
        case '#':       selector.id = name; selector.specificity += 100; break;
        case '.':       selector.classes.push( " " + name + " " ); cleanclasses.push(name); selector.specificity += 10; break;
        case ':':       if(name=='hover') { selector.hover = true; selector.specificity += 10; }; break;
        case '':
        case undefined: selector.tag = name.toLowerCase(); selector.specificity += 1; break;
      }
      clausestring = rest;
    }
	
	cleanclasses.sort();
	
	selector.normalized = (selector.tag?selector.tag:"") + (selector.id?"#"+selector.id:"");
	for( var i = 0; i<cleanclasses.length; i++ ) {
		selector.normalized += "." + cleanclasses[i];
	}
	selector.innermostnormalized = selector.normalized + ( selector.hover ? ":hover" : "" );
	
	return selector;
}


do {
	/* Internet Explorer */
	/*@cc_on @*/
	/*@if (@_win32)
		if ( window.location.protocol != "https:" ) {
			document.write("<sc" + "ript id='__ie_psst_observeDOMReady' defer='defer' src='javascript:void(0);'><\/sc" + "ript>");
			PSST.DOMReadyBrowserDevice = document.getElementById("__ie_psst_observeDOMReady");
			PSST.DOMReadyBrowserDevice.onreadystatechange = function() {
				if (this.readyState == "complete") {
					PSST.withDOM(); // call the onload handler
				}
			};
			break;
		}
	/*@end @*/
	
	/* Safari/Konqueror */
	/* WebKit must be tested for before DOM2 standard because WebKit supports document.addEventListener but NOT the 
	   DOMContentLoaded event. */
	if (/WebKit/i.test(navigator.userAgent)) { 
		PSST.DOMReadyBrowserDevice = setInterval(function() {
			if (/loaded|complete/.test(document.readyState)) {
				clearInterval(PSST.DOMReadyBrowserDevice);
				PSST.withDOM(); 
			}
		}, 10);
		break;
	}
	
	/* Mozilla/Opera9/DOM2 Compliant browsers */
	if (document.addEventListener) {
		document.addEventListener("DOMContentLoaded", PSST.withDOM, false);
		break;
	}

	/* Anything else */
	if (window.addEventListener) {
		window.addEventListener("load", PSST.withDOM, false);
	} else if(window.attachEvent) {
		window.attachEvent("onload", PSST.withDOM);
	}

} while(0);
