function Debugger ( parent ) {
	parent = parent || document.body;
	this.w3c = window.navigator.appName != 'Microsoft Internet Explorer';
	this.history = [];
	this.historyPtr = 0;
	this.historyLen = 0;
	this.disableHistory = false;
	this.h = document.createElement( 'div' );
	this.h.style.position = 'absolute';
	this.h.style.zIndex = 10000;
	this.h.style.top = '5px';
	this.h.style.left = '5px';
	this.h.style.border = '1px';
	this.h.style.borderStyle = 'solid';
	this.h.style.borderColor = '#000';
	this.h.style.backgroundColor = '#ebebeb';
	/*this.h.style.maxWidth = '500px';*/
	this.h.style.width = 'auto';
	this.h.style.padding = '3px';
	this.command = '';
	this.selectedNode = null;
	this.oldBorder = null;
	parent.appendChild( this.h );
	var d = document.createElement( 'div' );
	d.innerHTML = 'Evaluate:';
	d.style.fontSize = '10px';
	this.h.appendChild( d );
	var inp = document.createElement( 'input' );
	inp.style.width = '230px';
	var ank = this.createAnker( 'Go!', function() {	
		if (this._debugger.input.value != '') {
			this._debugger.watch( this._debugger.input.value );
			this._debugger.input.value = '';
		}
	}, '12px', '10px' );
	var pre = this.createAnker( '&lt;&lt;', function() {this._debugger.goBack(); }, '12px', '10px' ); 
	var nex = this.createAnker( '&gt;&gt;', function() {this._debugger.goForvard();}, '12px', '10px', '10px' );  
	this.input = inp;
	var frm = document.createElement( 'div' );
	this.h.appendChild( frm );
	frm.appendChild( inp );
	frm.appendChild( ank );
	frm.appendChild( pre );
	frm.appendChild( nex );
	var frm2 = document.createElement( 'div' );
	frm2.style.padding = '1px';
	frm2.style.marginTop = '3px';
	var cattr = this.createAnker( 'Attr', function() {this._debugger.exeCommand( 'Attr', '' ); }, '10px', '0' );
	frm2.appendChild( cattr );
	var cstl = this.createAnker( 'Style', function() {this._debugger.exeCommand( 'Style', '' ); }, '10px', '10px' );
	frm2.appendChild( cstl );
	var cpar = this.createAnker( 'Chain', function() {this._debugger.exeCommand( 'Chain', '' ); }, '10px', '10px' );
	frm2.appendChild( cpar );
	var ccss = this.createAnker( 'CSS', function() {this._debugger.exeCommand( 'CSS', '' ); }, '10px', '10px' );
	frm2.appendChild( ccss );
	this.h.appendChild( frm2 );
	var sel = document.createElement( 'div' );
	sel.style.padding = '1px';
	sel.style.marginTop = '3px';
	sel.style.fontSize = '10px';
	sel.style.display = 'none';
	this.h.appendChild( sel );
	this.selector = sel;
	this.menu = frm2;
	var w = document.createElement( 'div' );
	w.style.padding = '1px';
	w.style.marginTop = '3px';
	this.watchWin = w;
	this.h.appendChild( w );
}

Debugger.prototype.createAnker = function( text, action, size, lm, rm ) {
	size = size || '10px';
	lm = lm || '10px';
	rm = rm || '0';
	var ank = document.createElement( 'a' );
	ank.innerHTML = text;
	ank.style.marginLeft = lm;
	ank.style.marginRight = rm;
	ank.style.fontSize = size;
	ank.setAttribute( 'href', 'javascript:;' );
	ank._debugger = this;
	ank.onclick = action;
	return ank;
}

Debugger.prototype.goBack = function() {
	if ( this.historyPtr > 1 ) {
		this.disableHistory = true;
		this.watch( this.history[--this.historyPtr] );
	}
}

Debugger.prototype.goForvard = function() {
	if ( this.historyPtr < this.historyLen ) {
		this.disableHistory = true;
		this.watch( this.history[++this.historyPtr] );
	}
}

Debugger.prototype.watch = function( expr, clear, eval ) {
	if ( clear !== false ) {
		this.clear();
	}
	if ( ( eval !== false ) && typeof expr == 'string' ) {
		expr = this.eval( expr );
		if ( !expr ) return;
	}
	this.output( expr );
}

Debugger.prototype.clear = function( expr, eval ) {	
	while( this.watchWin.hasChildNodes() ) {
		this.watchWin.removeChild( this.watchWin.firstChild );
	}	
}

Debugger.prototype.captureNode = function() {
	if ( this.oldBorder ) {
		this.selectedNode.style.border = this.oldBorder.w;
		this.selectedNode.style.borderColor = this.oldBorder.c;
		this.selectedNode.style.borderStyle = this.oldBorder.s;
	}
	document.onmousemove = this.winEventMove;
	document.onmousedown = this.winEventPress;
	this.menu.style.display = 'block';
	this.selector.style.display = 'none';
	this.exeCommand( this.command, this.selectedNode );
}

Debugger.prototype.highLightNode = function(e) {
	if (this.w3c) {
		var node = e.target;
	} else {		
		var node = event.srcElement;
	}
	if ( node == this.selectedNode ) {
		return;
	} else {
		if ( this.oldBorder ) {
			this.selectedNode.style.border = this.oldBorder.w;
			this.selectedNode.style.borderColor = this.oldBorder.c;
			this.selectedNode.style.borderStyle = this.oldBorder.s;
		}
	}
	this.selector.innerHTML = this.command + ' for: ' + nodeView( node );
	this.selectedNode = node;
	if ( !this.oldBorder ) {
		this.oldBorder = { w:0, c:0, s:0 };
	}
	this.oldBorder.w = this.selectedNode.style.border;
	this.oldBorder.c = this.selectedNode.style.borderColor;
	this.oldBorder.s = this.selectedNode.style.borderStyle;
	this.selectedNode.style.border = '1px';
	this.selectedNode.style.borderColor = '#f00';
	this.selectedNode.style.borderStyle = 'solid';
}

Debugger.prototype.exeCommand = function( cmd, arg ) {
	this.clear();
	if ( typeof arg == 'string') {
		if ( arg == '' ) {
			this.command = cmd;
			this.menu.style.display = 'none';
			this.selector.style.display = 'block';
			this.winEventMove = document.onmousemove;
			this.winEventPress = document.onmousedown;
			document.onmousemove = function(e) { this.__activeDebugger.highLightNode(e); }
			document.onmousedown = function(e) { this.__activeDebugger.captureNode(); }
			document.__activeDebugger = this;
			return false;
		} else {
			this.putToHistory( ':' + cmd + ' ' + arg );	
			try {
				arg = eval( '(' + arg + ')' );
			} catch ( e ) {
				return [{ name:':'+cmd+' '+arg, value:e.message }];
			}
		}
	}
	switch ( cmd ) {
		case 'Attr':
			this.output( [this.showAttributes( arg )], false );
			break;
		case 'Style':
			this.output( [this.showStyles( arg )], false );
			break;
		case 'Chain':
			this.output( [this.nodeParentChain( arg )], false );
			break;
		case 'CSS':
			this.output( this.showCss( arg ), false, true );
			break;
	}
	return false;
}

Debugger.prototype.showAttributes = function( node ) {
	var attr = node.attributes;
	var i = 0;
	var res = {name: 'Attributes for: ' + nodeView( node ), value:{} };
	while ( i < attr.length ) {
		if ( this.w3c || attr[i].specified ) {
			if ( !this.w3c && attr[i].nodeName == 'style' ) {
				res.value[attr[i].nodeName] = node.style.cssText;
			} else {
				res.value[attr[i].nodeName] = attr[i].nodeValue;
			}
		}
		i++;
	}
	return res;
}

Debugger.prototype.showStyles = function( node ) {	
	return {name: 'Styles for: ' + nodeView( node ), value: this.getStyles( node.style ) };
}

Debugger.prototype.getStyles = function( styleSet ) {
	var i, res = {};
	for ( i in styleSet ) {
		if ( typeof i == 'string' && typeof styleSet[i] == 'string' && i.match(/^[^0-9]/) ) {
			if ( i != 'cssText' && i != 'length' && styleSet[i] != '' ) {
				res[normalizeStyleName( i )] = styleSet[i];
			}
		}
	}
	return res;
}

Debugger.prototype.showCss = function( node ) {
	var c = this.getParentChain( node );	
	var css = document.styleSheets;
	var cssFiles = [];
	var i, j, k, rules, added;
	var c1 = [];
	for ( i = 0; i < c.length; i++ ) {
		c1.push( { n:c[i].nodeName.toLowerCase(), i:c[i].id.toLowerCase(), c:c[i].className.toLowerCase() } );
	}
	for ( i = 0; i < css.length; i++ ) {
		if ( this.w3c ) {
			rules = css[i].cssRules;
		} else {
			rules = css[i].rules;
		}
		var actSel = [];
		for ( j = 0; j < rules.length; j++ ) {
			var cset = rules[j].selectorText.split(',');
			for ( k = 0; k < cset.length; k++ ) {
				if ( this.checkSelector( cset[k], c1 ) ) {
					if ( this.w3c ) {
						actSel.push( formatStyle( rules[j].cssText ) );
					} else {
						actSel.push( rules[j].selectorText + "{\r\n\t" + formatStyle( rules[j].style.cssText ) + "\r\n\t\}" );
					}
					break;
				}
			}
		}
		if ( actSel.length > 0 ) {
			cssFiles.push( { name:css[i].href, value: actSel } );
		}
	}
	return cssFiles;
}

Debugger.prototype.getParentChain = function( node ) {
	var chain = [];
	while ( node && node.nodeType == 1 ) {
		chain.push( node );
		node = node.parentNode;
	}
	return chain;
}

Debugger.prototype.nodeParentChain = function( node ) {
	var c = this.getParentChain( node );
	var i = c.length - 1;
	var chv = [];
	while ( i >= 0 ) {
		chv.push( nodeView( c[i] ) );
		i--;
	}
	return {name: 'Chain for: ' + nodeView( node ), value: chv };
}

Debugger.prototype.checkSelector = function( sel, chain ) {
	var sel = sel.split(' ');
	var selch = [];
	var i = 0, j;
	while ( i < sel.length ) {
		if ( !sel[i].match(/^\s*$/) ) {
			var m = sel[i].match(/([\w\d_-]+)?(#([\w\d_-]+))?(\.([\w\d_-]+))?(:[\w\d_-]+)?/);
			if ( m ) {
				selch.push( {
					n:m[1]?m[1].toLowerCase():undefined,
					i:m[3]?m[3].toLowerCase():undefined,
					c:m[5]?m[5].toLowerCase():undefined
				} );
			}
		}
		i++;
	}
	if ( selch.length == 0 ) return false;
	j = selch.length - 1;
	if (!( 
			( selch[j].n == chain[0].n || selch[j].n == undefined ) && 
			( selch[j].i == chain[0].i || selch[j].i == undefined ) && 
			( selch[j].c == chain[0].c || selch[j].c == undefined )
		)) return false;
	j = chain.length - 1;
	for ( i = 0; i < selch.length - 1; i++ ) {
		var equ = false;
		while ( j > 0 ) {
			if ( 
				( selch[i].n == chain[j].n || selch[i].n == undefined ) && 
				( selch[i].i == chain[j].i || selch[i].i == undefined ) && 
				( selch[i].c == chain[j].c || selch[i].c == undefined )
			) {	
				j--;
				equ = true;
				break;
			}
			j--;
		}
		if ( !equ ) return false;
	}
	return true;
}

Debugger.prototype.putToHistory = function( expr ) {
	if ( !this.disableHistory ) {
		this.history[++this.historyPtr] = expr;
		this.historyLen = this.historyPtr;
	} else {
		this.disableHistory = false;
	}
}

Debugger.prototype.eval = function( expr ) {
	var m = expr.match(/^:(\w+)(.*)$/);
	if (m) {	
		return this.exeCommand( m[1], m[2] );
	} else {
		var vv = '';
		try {
			vv = eval( '(' + expr + ')' );
			this.putToHistory( expr );
		} catch ( e ) {
			vv = e.message;
		}
		return [{ name:expr, value:vv }];
	}
}

Debugger.prototype.makeBlock = function( val, name, fulstr ) {
	var full = name == undefined;
	var vv = 'undefined';
	try {
		switch ( typeof val ) {
			case 'string':
				if ( full || fulstr === true ) {
					vv = '<pre>' + htmlentities( val ) + '</pre>';
				} else {
					if ( val.length > 200 ) {
						vv = htmlentities( val.substring( 0, 200 ) ) + '...';
					} else {
						vv = htmlentities( val );
					}
				}
			break;
			case 'function':
				if ( full ) {
					vv = '<pre>' + val.toString() + '</pre>';
				} else {
					vv = 'function() { ... }';
				}
			break;
			case 'object':
				if ( val.toString ) {
					vv = val.toString();
				} else {
					vv = 'object';
				}
			break;
			default:
				if ( val.toString ) {
					vv = val.toString();
				}
		}
	} catch ( e ) {
		vv = 'evaluating failed';
	}
	var bdiv = document.createElement( 'div' );
	bdiv.style.padding = '3px';
	bdiv.style.border = '1px';
	bdiv.style.borderStyle = 'solid';
	bdiv.style.borderColor = '#000';
	bdiv.innerHTML = !full ? '<b>'+name + '</b>: ' + vv : vv;
	return bdiv;
}

Debugger.prototype.output = function( expr, bind, fulstr ) {
	var _this = this;
	var i = 0;
	while (i < expr.length) {
		var div = document.createElement( 'div' );
		div.style.border = '1px';
		div.style.borderStyle = 'solid';
		div.style.borderColor = '#000';
		var tit = document.createElement( 'div' );
		tit.style.color = '#fff';
		tit.style.padding = '3px';
		tit.style.backgroundColor = '#000';
		tit.style.overflow = 'hidden';
		tit.style.whiteSpace = 'nowrap';
		tit.innerHTML = expr[i].name;
		if ( bind !== false ) {
			tit.onclick = this.setExpression;
			tit.__debugger = this;
		}
		div.appendChild( tit );
		this.watchWin.appendChild( div );
		if ( typeof expr[i].value == 'object' ) {
			var p;
			for ( p in expr[i].value ) {
				var vv = this.makeBlock( expr[i].value[p], p, fulstr );
				if ( bind !== false ) {
					vv.onclick = this.setExpression;
					vv.ondblclick = this.evalExpression;				
					vv.__debugger = this;
					vv.__expr = expr[i].name + "['" + p + "']";
				}
				div.appendChild( vv );
			}
		} else {
			div.appendChild( this.makeBlock( expr[i].value ) );
		}
		i++;
	}
}

Debugger.prototype.setExpression = function() {
	this.__debugger.input.value = this.__expr || this.innerHTML;
}

Debugger.prototype.evalExpression = function() {
	this.__debugger.input.value = this.__expr || this.innerHTML;
	this.__debugger.watch( this.__debugger.input.value );	
	this.__debugger.input.focus();
}

function htmlentities( str ) {
    var i, out = '', len, c;
    len = str.length;
    for( i = 0; i < len; i++ ){
        c = str.charAt( i );
		if ( c == '<' ) out += '&lt;';
		else if ( c == '>' ) out += '&gt;';
        else if ( c == '"' ) out += '&quot;';
		else out += c;
    }
    return out;
}

function normalizeStyleName( name ) {
	var i, out = '', len, c;
    for( i = 0; i < name.length; i++ ){
        c = name.charAt( i );
		if ( c >= 'A' && c <= 'Z' ) out += '-' + c.toLowerCase();
		else out += c;
    }
    return out;
}

function formatStyle( text ) {
	var i, out = '', len, c;
    for( i = 0; i < text.length; i++ ){
        c = text.charAt( i );
		if ( c == ';' ) out += ";\r\n\t";
		else if ( c == '{' ) out += "{\r\n\t";
		else out += c;
    }
    return out;
}

function nodeView( node ) {
	var v = node.nodeName;
	if ( node.id != '' ) v = v + '#' + node.id;
	if ( node.className != '' ) v = v + '.' + node.className;
	return v;
}

window.onload = function(){
	addDebugger();
}

function addDebugger() {
	if ( window.d ) return;
	window.d = new Debugger();
	window.d.h.style.display = 'none';
	document._onkeypress = document.onkeypress;
	var w3c = window.navigator.appName != 'Microsoft Internet Explorer';
	document.onkeypress = function( e ) {
		e = e || window.event;
		if ( e.ctrlKey && ( e.shiftKey || e.altKey ) ) {
			if ( ( w3c && e.charCode == e.DOM_VK_D ) ||
				 ( !w3c && e.keyCode == 4 ) ) {			
				if ( window.d.h.style.display == 'none' ) {
					window.d.h.style.display = 'block';
				} else {
					window.d.h.style.display = 'none';
				}
			}
		}
		if ( document._onkeypress ) {
			document._onkeypress( e );
		}
	}
}
