function Tracker() {

	/* vars */
	var Config = new Object();	
	Config.domainName = "happytrees.ru";				// Имя домена
	Config.step = 0;				// Порог чувствительности к движению px 
	Config.delay = 1;		 		// Порог чувствительности к задержке в мс
	Config.idle = 60;				// Лимит простоя мышки в секундах
	Config.packetSize = 15000;			// Лимит передаваемых данных (в байтах)
	Config.recordStep = 1;				// Шаг записи
	Config.compStep = 500;  			// Частота компрессии

	var str = "";
	var x,y, oldx, oldy;
	var window_w=0,window_h=0,window_x=0,window_y=0;
	var counter=0, lastcounter=0;
	var elFocus = null;
	var prevElFocus = null;
	var traps = [];
	var coordsInitiated = 0;
	var counterID;
	var __eventListeners = [];
	var keyDownFlag = 0;
	var keyDownCode = 0;
	/* vars end */

	/* private methods */
	/* record methods */

	function getBodyScrollTop()
	{
	  return self.pageYOffset || (document.documentElement && document.documentElement.scrollTop) || (document.body && document.body.scrollTop);
	}

	function getBodyScrollLeft()
	{
	  return self.pageXOffset || (document.documentElement && document.documentElement.scrollLeft) || (document.body && document.body.scrollLeft);
	}

	function runCounter() {
		counter++;
		if (counter*Config.recordStep>Config.idle*1000) counter=0;
		var window_resized = false;
		if (str.length>Config.packetSize) { str = ''; }

		// capturing scroll

		var bl = getBodyScrollLeft();
		var bt = getBodyScrollTop();
		var dbl = Math.abs(bodyls-bl);
		var dbt = Math.abs(bodyts-bt);

		if (dbl>Config.step || dbt>Config.step) {
			str += (dbl>Config.step ? (bl-bodyls) : '')+','+(dbt>Config.step ? (bt-bodyts): '')+';';
			var d = counter-lastcounter;
			if (d*Config.recordStep>=Config.delay) { str+='s'+(d>1 ? d : '')+';'; }
			lastcounter = counter;
		}

		bodyls = bl;
		bodyts = bt;

		// capturing mouse move

		var dbx = Math.abs(x-oldx);
		var dby = Math.abs(y-oldy);

		if (coordsInitiated) { 
			if (dbx>Config.step || dby>Config.step) {
				str+= (dbx>Config.step ? (x-oldx) : '')+"."+(dby>Config.step ? (y-oldy) : '')+";";
				oldx = x;
				oldy = y;
				var d = counter-lastcounter;
				if (d*Config.recordStep>=Config.delay) { str+='s'+(d>1 ? d : '')+';'; }
				lastcounter = counter;
			}
		}

		// MAXIMIZE, MINIMIZE capturing included

		// capturing window resizing
		var wnd_w = document.body.clientWidth;
		var wnd_h = document.body.clientHeight;
		if (Math.abs(window_w-wnd_w)>Config.step || Math.abs(window_h-wnd_h)>Config.step) {
			str += 'w'+wnd_w+'.'+wnd_h+';';
			window_resized = true;
			window_w = wnd_w;
			window_h = wnd_h;
		}

		// capturing window moving
		var wnd_x = window.screenLeft;
		var wnd_y = window.screenTop;
		if (Math.abs(window_x-wnd_x)>Config.step || Math.abs(window_y-wnd_y)>Config.step) {
			str += 'p'+wnd_x+'.'+wnd_y+';';
			window_x = wnd_x;
			window_y = wnd_y;
		}
		if (window_resized) str += 'w'+wnd_w+'.'+wnd_h+';';
		counterID = setTimeout(function(){ runCounter();}, Config.recordStep);
	}

	function writeHeader() {
		var from = document.referrer;
		var app=0;
		switch (navigator.appName) {
			case 'Opera': app = 1; break;
			case 'Netscape': app = 2; break;
			case 'Microsoft Internet Explorer': app = 3; break;
			default: break;
		}
		var appv = navigator.appVersion;
		appv = appv.substr(0,1);
		app+=','+appv;
		str+="br="+app+";";
		if (!from) { from = "bm"; }
		str+="from="+from+";";

		var wnd_w = screen.width;
		var wnd_h = screen.height;
		str+="sw"+wnd_w+";sh"+wnd_h+";";
	}

	function compressData() {
		str = str.replace(/;s;/g, '#');
	}

	function sendData() {
		if (isComp) {
			sendRequest('/dotrack/writestat.php',  'tuid='+encodeURIComponent(tracker_uid)+'&url='+encodeURIComponent(thisurl)+'&history='+encodeURIComponent(str), 'POST');
		} else {
			sendRequest('/dotrack/writestat.php?tuid='+tracker_uid+'&url='+thisurl+'&history='+str,'', 'GET'); 	// FIXME OPTIMISE FOR OPERA     
		}

		clearTimeout(counterID);
		unregisterAllEvents();
	}

	/* record method end */


	/* abstract capturing functions */
	capture={
		focus:function(e) { 
			var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
			if ((srcEl.tagName == 'INPUT' && srcEl.type.toUpperCase() == 'TEXT') || srcEl.tagName == 'TEXTAREA') {
				str+="fo"+getCaretPos(srcEl)+";";
			}
			return true;
		},

		close:function() {
			str+='x;';
			sendData();
		},
		click: function(e) {
			var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
			if (srcEl.tagName == 'INPUT' && srcEl.type.toUpperCase() == 'SUBMIT') { 
				while (srcEl.tagName != "FORM" && srcEl.tagName != 'BODY') {
					srcEl = (srcEl.parentElement ? srcEl.parentElement : srcEl.parentNode);
				}
				if (srcEl.tagName == "FORM") {
					str+="f"; str+=srcEl.action+";";      
					//       sendData(); 
				}

			} else if ((srcEl.tagName == 'INPUT' && srcEl.type.toUpperCase() == 'TEXT') || srcEl.tagName == 'TEXTAREA') {
				str+="fo"+getCaretPos(srcEl)+";";
			} else {
				while (srcEl.tagName != "A" && srcEl.tagName != 'BODY') {
					srcEl = (srcEl.parentElement ? srcEl.parentElement : srcEl.parentNode);
				}
				if (srcEl.tagName == "A" && srcEl.href != '#'  && (srcEl.href.indexOf(decodeURIComponent(thisurl)+'#')==-1)) { 
					if ((srcEl.href.indexOf('http') == -1 || srcEl.href.indexOf(Config.domainName)>=0) && srcEl.href.indexOf('@')==-1) {
						str+="c"; str+="u"+srcEl.href+";";      
						//       sendData(); 
					} else {
						str+="c"; str+="o"+srcEl.href+";";
					}
				} else {
					str+="c;";
				}
			}
			return true;
		},
		mouseup: function() {
			var sel= document.selection;
			if (sel) { 
				var range = sel.createRange();
				allhtml = document.body.innerHTML.replace(/<\/*.*?>/g, '');
				if (range.text && allhtml.indexOf(range.text)>-1) { 
					var rt = range.text;
					range.expand('word');
					var rtb = range.text;
					rtb = rtb.replace(rt, "<text>"+rt+"</text>");       
					str+="sel="+encodeURIComponent(rtb)+";"; 
				}
			}
		},
		keydown: function(e) {
			prevElFocus = elFocus;
			var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
			var keyCode =(typeof event!=='undefined')? event.keyCode : e.which;
			elFocus = srcEl;
			if (elFocus.tagName == 'INPUT' || elFocus.tagName == 'TEXTAREA') { 
				if (elFocus != prevElFocus) { str+='fi'+elFocus.id+';'; addEventListener(elFocus, 'blur', capture.blur);  }
				if (keyCode == 8 || keyCode == 35 || keyCode == 36 || keyCode ==37 || keyCode == 39 || keyCode == 46) {
					str+='k'+keyCode+';';
				}
				var d = counter-lastcounter;
				if (d*Config.recordStep>=Config.delay) { str+='s'+(d>1 ? d : '')+';'; }
				lastcounter = counter;
			}
		},
		copy:function() {
			str+='copy;';
		},
		keypress:function(e) {
			prevElFocus = elFocus;
			var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
			elFocus = srcEl;
			if (elFocus.tagName == 'INPUT' || elFocus.tagName == 'TEXTAREA') { 
				if (elFocus != prevElFocus) { str+='fi'+elFocus.id+';'; addEventListener(elFocus, 'blur', capture.blur);  }
				var keyCode =(typeof event!=='undefined')? event.keyCode : e.which;
				if (keyCode == '46') { keyCode = 100046; }
				str+='k'+keyCode+';';
				var d = counter-lastcounter;
				if (d*Config.recordStep>=Config.delay) { str+='s'+(d>1 ? d : '')+';'; }
				lastcounter = counter;
			}
		},
		blur: function() {
			str += "q;";
//			elFocus.detachEvent('onblur', capture.blur);
		},
		move: function(e) { 
			x =(typeof event!=='undefined')? event.x : e.clientX;
			y =(typeof event!=='undefined')? event.y : e.clientY;
			var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
			while (srcEl.tagName != 'BODY') {
				if (srcEl.style.position == 'relative') {
					x+=srcEl.offsetLeft;
					y+=srcEl.offsetTop;
				}
				srcEl = (srcEl.parentElement ? srcEl.parentElement : srcEl.parentNode);		// FIXME in firefox
			}
			if (!coordsInitiated) { 
				str+= "i"+x+"."+y+";"; 
				oldx = x;
				oldy = y;
				coordsInitiated = 1; 
			}
		}
	}
	/* abstract capturing functions end */

	/* events handlers */

	function addEventListener(instance, eventName, listener) {
		var listenerFn = listener;
		if (instance.addEventListener) {
        		instance.addEventListener(eventName, listenerFn, false);
		} else if (instance.attachEvent) {
	       		listenerFn = function() {
		        	listener(window.event);
	        	};
			instance.attachEvent("on" + eventName, listenerFn);
		} else {
        		throw new Error("Event registration not supported");
		}
		var event = {
		        instance: instance,
		        name: eventName,
		        listener: listenerFn
		};
		__eventListeners.push(event);
		return event;
	}

	function removeEventListener(event) {
		var instance = event.instance;
		if (instance.removeEventListener) {
			instance.removeEventListener(event.name, event.listener, false);
		} else if (instance.detachEvent) {
			instance.detachEvent("on" + event.name, event.listener);
		}
		for (var i = 0; i < __eventListeners.length; i++) {
			if (__eventListeners[i] == event) {
				__eventListeners.splice(i, 1);
				break;
			}
		}
	}

	function unregisterAllEvents() {
		while (__eventListeners.length > 0) removeEventListener(__eventListeners[0]);
	}

	function traceCustomEvent(e) {
		var srcEl =(typeof event!=='undefined')? event.srcElement : e.target;
		var type =(typeof event!=='undefined')? event.type : e.type;
		var ev = null;
		eval("ev = srcEl.on"+type+";");
		var initsrc = srcEl;
		while (!ev && srcEl) {
			srcEl = (srcEl.parentElement ? srcEl.parentElement : srcEl.parentNode);
			if (srcEl) eval("ev = srcEl.on"+type+";");
		}
		if (!srcEl) srcEl = initsrc;
		if (srcEl) str+='e'+srcEl.id+'='+type+';'; 
	}

	/* events handlers end */	

	/* DOM elements markers */
	function initiateTAFocus() {
		var obj = document.getElementsByTagName('input');
		for (var i=1;i<obj.length;i++) { if (obj[i].type != 'hidden') addEventListener(obj[i], 'focus', capture.focus); }
		bj = document.getElementsByTagName('textarea');
		for (i=1;i<obj.length;i++) addEventListener(obj[i], 'focus', capture.focus);
	}

	function initiatePreTraps() {

		/*  Your code here */
		/*  INSERTING SLOW TRAPS THAT DEPEND ON OTHER DOMAINS */

		/*
		if (thislongelement.readyState='complete') { 
			thislongelement.setAttribute('traceEvents', 'click');
			initiateTraps(); 
		} else { 
			setTimeout(function () { initiatePreTraps(); }, 100);
		}
		*/

		/* DELETE THIS STRING AFTER SLOW TRAPS ACTIVATED */
		/* \/\/\/\/\/\/\/ */
		initiateTraps();		
		/* /\/\/\/\/\/\/\ */
	}

	function getNodeList(el) {
		if (el == null || !el.childNodes) return;  
		for (var i = 0; i < el.childNodes.length; i++) {
			if (el.childNodes[i].getAttribute && el.childNodes[i].getAttribute('traceEvents')) {
				var j = traps.length;
				traps[j] = [];
				traps[j][0] = el.childNodes[i].id;
				traps[j][1] = [];
				traps[j][1] = el.childNodes[i].getAttribute('traceEvents').split(","); 
				for (var k in traps[j][1]) {
					try { addEventListener(el.childNodes[i], traps[j][1][k], traceCustomEvent);		} catch(e) {  alert('Ошибка приложения. Пожалуйста, сообщите нам об ошибке по адресу info@itrack.ru\nType:'+e.name+'\nFile: '+e.fileName+'\nDescr:'+e.description+'\nMessage:'+e.message+'\n Number:'+e.lineNumber+'\nStack:'+e.stack);  }
				}
			}

			if (el.childNodes[i].getAttribute) {
				// ENHANCE TO ALL AVAILABLE EVENTS
				if (typeof el.childNodes[i].onfocus == "function" && el.childNodes[i].getAttribute("id")) { addEventListener(el.childNodes[i], 'focus', traceCustomEvent); }	//FIXME typeof el.childNodes[i].onfocus =="function" TESTME
				if (typeof el.childNodes[i].onblur == "function" && el.childNodes[i].getAttribute("id")) {  addEventListener(el.childNodes[i], 'blur', traceCustomEvent); }
			}

			getNodeList(el.childNodes[i]);
		}
		return;
	}

	function initiateTraps() { getNodeList(document); }


	/* DOM elements markers end */

	/* common methods */

	function getCaretPos(obj) {
		obj.focus();
		if(obj.selectionStart) { 
			return obj.selectionStart; 
		} else if (document.selection) {
			var sel = document.selection.createRange();
			var clone = sel.duplicate();
			sel.collapse(true);
			if (obj.tagName == 'TEXTAREA') { 
				clone.moveToElementText(obj);
			} else {
        	        	clone.moveStart("character",-65536);
			}
			clone.setEndPoint('EndToEnd', sel);
			return clone.text.length;
		}
		return 0;
	}
	/* common methods end */

	/* private methods end */


	/* constructor */

	var isComp = (encodeURIComponent) ? true : false;
	if (isComp) { 
		addEventListener(document.body, 'mousemove', capture.move);
		addEventListener(document.body, 'copy', capture.copy);
		addEventListener(document.body, 'click', capture.click);
		addEventListener(document.body, 'mouseup', capture.mouseup);
		addEventListener(document.body, 'keydown', capture.keydown);
		addEventListener(document.body, 'keypress', capture.keypress);
		document.body.onload = initiateTAFocus;
		if (window.captureEvents) {
			window.addEventListener('unload', capture.close, true);
		} else {
			document.body.onunload=capture.close;
		}

		/* Initiate event traps, wait for body loaded */

		initiatePreTraps();
	
		writeHeader();

		// SCROLL INITIATE 
		var bodyls = getBodyScrollLeft();
		var bodyts = getBodyScrollTop();
		str += 'bi'+bodyls+'.'+bodyts+';';
		setInterval(function(){ compressData();}, Config.compStep);
		runCounter();

	} else {
		writeHeader();
		str+="x;";
		sendData();
	}

	/* constructor end */


}
