//NOTE: Some browsers can't handle .textContent="abc", therefore use .innerHTML! If this doesn't work, use .appendChild(document.createTextNode("abc")))

var System={
	version: "1.0.0",
	insertStyle:function(name,styles,media) {
		if (!document._style) {
			document._style = document.createElement("style"); 
			document._style.setAttribute("type", "text/css"); 
			document._style.setAttribute("media", media||"all"); 
			document.getElementsByTagName('head')[0].appendChild(document._style);
		}
		document._style.appendChild(document.createTextNode(name+" {"+Object.toCSS(styles)+"}"));
	},
	getSourceElement:function(e) { e=e||window.event; return e.retarget||e.target||e.srcElement; },
	retarget:function(e,newTarget) { e=e||window.event; e.retarget=newTarget; return e; }
	//Stupid IE can't handle Event.prototype!!!! Event.prototype.getSourceElement=function() { return this.target||this.srcElement; }
};
Object.getClass=function(object) {
   var s = object.constructor.toString();
   var i = s.indexOf("(");
   if (i<0) return null;
   return s.substring(9,i);
}
Object.isAnonymousObject=function(object) {
    return object.constructor.toString().indexOf("[native code]")>10;
}
Object.isArray=function(object) { return object!=null && typeof(object)=="object" && 'splice' in object && 'join' in object; }
Object.clone=function(source,target,deep) {
	for (var p in source) {
		if (target.tagName && target[p]==undefined && target.style && target.style[p]!=undefined) {
		    if (typeof(source[p])=="number" && (p=="left"||p=="top"||p=="width"||p=="height"||p=="right"||p=="bottom"))
		        target.style[p] = Math.round(source[p])+"px";
		    else 
		        target.style[p] = source[p];
		} else if (deep && typeof(source[p])=="object" && !source[p].getHours && !target[p]==undefined) 
			Object.clone(source[p],target[p],deep);
		else target[p] = source[p];
	}
	return target;
}
Object.applyRecursive=function(object,fnc) {
	var stack = [object]; // Recursive call - the non-recursive way :o)
	var fillStack = (object.nodeType==undefined)?
		function(stack,object) {
			for (var p in object) if (typeof(object[p])=="object") stack.push(object[p]);
		}:function(stack,element) {
			for (var e=element.firstChild;e;e=e.nextSibling) stack.push(e);
		};
	
	while (stack.length>0) {
		var top = stack.pop();
		fillStack(stack,top);
		if (fnc(top)) return top;
	}
	return null;
}
Object.getChildrenByProperty=function(root,property,value) { // value can be omitted
	var output = [];
	Object.applyRecursive(root,function(object) { 
		if (object[property] && (value==undefined || object[property]==value))
			output.push(object); 
	});
	return output;
}
Object.getFirstChildByProperty=function(root,property,value) { // value can be omitted
	return Object.applyRecursive(root,function(object) { 
		return (object[property] && (value==undefined || object[property]==value));
	});
}

Array.prototype.top=function() {
	return this.length?this[this.length-1]:null;
}
Array.prototype.indexOf=function(item) {
	for (var i=0;i<this.length;++i) {
	    if (item.equals&&item.equals(this[i])) return i;
		if (this[i]==item) return i;
	}
	return -1;
}
Array.prototype.remove=function(item) {
	var i=this.indexOf(item);
	if (i>=0) this.splice(i,1);
	return i>=0;
}

//System.insertStyle(".hidden",{display:"none"});
Date.SUNDAY=0;
Date.MONDAY=1;
Date.TUESDAY=2;
Date.WEDNESDAY=3;
Date.THURSDAY=4;
Date.FRIDAY=5;
Date.SATURDAY=6;
Date.UNDEFINED=new Date(-10000000);

Date.minute = 60*1000;
Date.hour = 60*Date.minute;
Date.day = 24*Date.hour;
Date.firstWeekDay = Date.SUNDAY;
Date.dayNames = ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];  
Date.monthNames = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

Date.setWeekStart=function(day) { if (day<0) day=7-day; Date.firstWeekDay=day%7; }
Date.getWeekDay=function(day) { return Date.dayNames[(Date.firstWeekDay+day)%7]; }
Date.getToday=function() {
	var today = new Date();
	today.setHours(0); today.setMinutes(0);
	return today;
}
//TODO Date formatting
Date.defaultFormat=null;
Date.setDefaultFormat=function(format) { Date.defaultFormat=format; }
Date.prototype.setFormat=function(format) { this._format=format; }
Date.prototype.getFormat=function() { return this._format; }
Date.prototype.format=function(format) {
	var f=format||this._format||Date.defaultFormat;
	if (!f) return this.toString();
	var MM = this.getMonth()+1;
	var dd = this.getDate();
	var hh = this.getHours();
	var mm = this.getMinutes();
	var ss = this.getSeconds();
	
	var out = "";
	for (var i=0;i<f.length;++i) {
		var c = f.charAt(i);
		if (c==f.charAt(i+1)) {
			var common = 1;
			while (i+1<f.length && f.charAt(i+1)==c) { ++common; ++i; }
			if (i+1<f.length && f.charAt(i+1)=='!') { common=100; ++i; }
			
			switch (c) {
				case 's': out += (ss>9?ss:"0"+ss); break;
				case 'm': out += (mm>9?mm:"0"+mm); break;
				case 'h': out += (hh>9?hh:"0"+hh); break;
				case 'd': out += (dd>9?dd:"0"+dd); break;
				case 'D': 
					out += Date.dayNames[this.getDay()].substring(0,common); 
					break;
				case 'M': 
					if (common>=3) out += Date.monthNames[this.getMonth()].substring(0,common);
					else out += (MM>9?MM:"0"+MM); 
					break;
				case 'y': out += this.getFullYear(); break;
				default: out += c; break;
			}
		}
		else out += c;
	}
	return out;
}
Date.prototype.parse=function(string,format) {
	var f=format||this._format||Date.defaultFormat;
	if (!f) return { success:true };
	
	for (var i=0,j=0;i<f.length && j<string.length;++i,++j) {
		var c = f.charAt(i);
		var c0 = string.charAt(j);
		var c1 = string.charAt(j+1);
		if (c==f.charAt(i+1)) {
			var value = 0;
			var common = 1;
			while (i+1<f.length && f.charAt(i+1)==c) { ++common; ++i; }
			var value = string.substring(j,j+common);
			j+=common-1;

			switch (c) {
				case 's': this.setSeconds(parseInt(value,10)); break;
				case 'm': this.setMinutes(parseInt(value,10)); break;
				case 'h': this.setHours(parseInt(value,10)); break;
				case 'd': this.setDate(parseInt(value,10)); break;
				case 'D': 
					for (var k=0;k<Date.dayNames.length;++k) if (Date.dayNames[k]==value) { this.setDay(k); break; }
					break;
				case 'M': 
					if (common==3) for (var k=0;k<Date.monthNames.length;++k) if (Date.monthNames[k]==value) { this.setMonth(k); break; }
					else this.setMonth(parseInt(value,10)-1);
					break;
				case 'y': 
					this.setFullYear(parseInt(value,10));
					break;
				default: 
					if (c0!=c) return { success:false, source: j, pattern: i }; 
					break;
			}
		} else if (c0!=c) return { success:false, source: j, pattern: i };
	}
	return { success:true };
}

Date.prototype.getID=function() { return ""+this.getYear()+"_"+this.getMonth()+"_"+this.getDate(); }
Date.prototype.msToDays=function(ms) { return Math.round(ms/Date.day); } 
Date.prototype.copy=function() { return new Date(this); }
Date.prototype.addDays=function(days) { this.setDate(this.getDate()+days); return this; }

Date.prototype.daysToWeekStart=function() { return (7+Date.firstWeekDay-this.getDay())%7; }
Date.prototype.daysFromWeekStart=function() { return (7+(this.getDay()-Date.firstWeekDay))%7; }

Date.prototype.nextMonth=function() { this.setMonth(this.getMonth()+1); return this; }
Date.prototype.prevMonth=function() { this.setMonth(this.getMonth()-1); return this; }
Date.prototype.nextDay=function() { this.setDate(this.getDate()+1); return this; }
Date.prototype.prevDay=function() { this.setDate(this.getDate()-1); return this; }
Date.prototype.nextHour=function() { this.setHours(this.getHours()+1); return this; }
Date.prototype.prevHour=function() { this.setHours(this.getHours()-1); return this; }
Date.prototype.nextMinute=function() { this.setMinutes(this.getMinutes()+1); return this; }
Date.prototype.prevMinute=function() { this.setMinutes(this.getMinutes()-1); return this; }
Date.prototype.nextSecond=function() { this.setSeconds(this.getSeconds()+1); return this; }
Date.prototype.prevSecond=function() { this.setSeconds(this.getSeconds()-1); return this; }
Date.prototype.next=function() { this.setMilliseconds(this.getMilliseconds()+1); return this; }
Date.prototype.prev=function() { this.setMilliseconds(this.getMilliseconds()-1); return this; }

Date.prototype.getDayName=function(day) { return Date.dayNames[day==undefined?this.getDay():day]; }
Date.prototype.getMonthName=function(month) { return Date.monthNames[month==undefined?this.getMonth():month]; }

Date.prototype.getDayStart=function() { return new Date(this.getFullYear(),this.getMonth(),this.getDate()); }
Date.prototype.getDayEnd=function() { return new Date(this.getFullYear(),this.getMonth(),this.getDate(),23,59,59,999); }
Date.prototype.getWeekStart=function() { return new Date(this.getFullYear(),this.getMonth(),this.getDate()).addDays(-this.daysFromWeekStart());	}
Date.prototype.getMonthStart=function() { return new Date(this.getFullYear(),this.getMonth(),1); }
Date.prototype.getYearStart=function() { return new Date(this.getFullYear(),0,1); }

Date.prototype.getDayDiff=function(last) { return this.msToDays(last.getTime() - this.getTime()); }
Date.prototype.getHourDiff=function(date) {	return Math.floor((date-this)/Date.hour); }
Date.prototype.getMinuteDiff=function(date) { return Math.floor((date-this)/Date.minute); }
Date.prototype.getDiff=function(date) { return date-this; }

Date.prototype.isWeekStart=function() { return Date.firstWeekDay==this.getDay(); }
Date.prototype.isWeekend=function() { return this.getDay()==0||this.getDay()==6; }
Date.prototype.isSameDay=function(day) {
	if (!day) return false;
	return this.getYear()==day.getYear() && this.getMonth()==day.getMonth() && this.getDate()==day.getDate();
}
Date.prototype.getWeek=function() {
	var firstDate = this.getYearStart();
	var countFirst = firstDate.daysToWeekStart();     // Get number of days to week start
	var count = firstDate.getDayDiff(this);           // Get current days day count 
	if (countFirst>=4) count += 7;               // If first (work) day of the year is week 1 - offset with 1 or (count-countFirst) will be <0
	else if (count<countFirst) count += (countFirst<=2?52:53)*7;

	var result = 1+Math.floor((count-countFirst)/7); 	
	if (result==53) { // Hmmm.... Now we must check if the really is week 53 - or week 1
		var lastWeekLength = 31-(this.getDate()-this.daysFromWeekStart())+1;
		if (this.getMonth()==11 && lastWeekLength<4) result = 1;
		if (this.getMonth()==0 && count<countFirst) result = 1;
	}
	return result;
}

function Period(date1, date2) { this.start = date1; this.end = date2; }
Period.prototype.intersects = function (period) { return this.start<period.end && this.end>period.start; }
Period.prototype.containsDate = function(date) { return this.start<=date && date<=this.end; }
Period.prototype.contains = function(period) { return period.start<=this.start && this.end<=period.end; }

if (document) {
	document.useEZ=function() {
		if (this.rawCreateElement) return; // Don't go here more than once!
		this.rawCreateElement=document.createElement;
		this.rawGetElementById=document.getElementById;
		
		this._resizeObjects=[];
		window.onresize=function(e) { for (var i=0;i<document._resizeObjects.length;++i) document._resizeObjects[i].onresize(e); }
		document.receiveEvent=function(eventId,object) { if (eventId=="onresize") document._resizeObjects.push(object); }
		
		this._tagMap={}
		this.createElement=function(tag,setup) {
			var element = null;
			var fnc = this._tagMap[tag];
			if (!fnc) { element = this.extendElement(this.rawCreateElement(tag)); if (setup) Object.clone(setup,element,true); }
			else element = this.extendElement(fnc(setup));
			return element;
		}
		this.getElementById=function(id) { return this.extendElement(this.rawGetElementById(id)); }
	}
	document.extendElement=function(element) {
		var _this = element;
		_this.setStyle=function(style) { Object.Clone(style,this,false); }
		_this.getStyle=function() { return this.style; }
		_this.setBounds=function(left,top,width,height) { Object.Clone({left:left,top:top,width:width,height:height},this,false); }
		
		_this.remove=function() { _this.parentNode.removeChild(_this); return _this; }
		_this.hasClassName=function(className) {
			if (!className || !_this.className) return false;
			var classes = _this.className.split(" ");
			for (var i=0;i<classes.length;++i) if (classes[i]==className) return true;
			return false;
		}
		_this.addClassName=function(className) {
			if (!className) return;
			if (_this.className==undefined) _this.className="";
			if (!this.hasClassName(className)) _this.className += " "+className;			
		}
		_this.addFirstClassName=function(className) {
			if (!className) return;
			if (_this.className==undefined) _this.className="";
			if (!_this.hasClassName(className)) _this.className = className+" "+_this.className;			
		}
		_this.removeClassName=function(className) {
			if (!className || !_this.className) return;
			var classes = _this.className.split(" ");
			_this.className = "";
			for (var i=0;i<classes.length;++i) if (classes[i]!=className) _this.className += " "+classes[i];
		}
		_this.toggleClassName=function(className) {
			if (!className) return;
			var classes = _this.className.split(" ");
			_this.className = "";
			
			var foundClassName = false;
			for (var i=0;i<classes.length;++i)  
				if (classes[i]!=className) _this.className += " "+classes[i];
				else foundClassName = true;

			if (!foundClassName) _this.className += " "+className;
		}
		_this.startDrag=function(e) {
			e=e||window.event;
			_this.addClassName("dragging");
			_this.dragOffset = {x:e.screenX-_this.offsetLeft,y:e.screenY-_this.offsetTop};
			document.onmousemove=function(e) {
				e=e||window.event;
				_this.style.left=(e.screenX-_this.dragOffset.x)+"px";
				_this.style.top=(e.screenY-_this.dragOffset.y)+"px";
				return false;
			}
			document.onmouseup=function(e) { 
				document.onmousemove=function(e){};
				document.onmouseup=function(e){};
				_this.removeClassName("dragging");
				return false; 
			}
		}
		_this.getAbsolutePosition=function(offsetElement) {
			var point = {x:0, y:0}
			var pos = "dummy"; // Prevent elements with absolute position to always return 0,0 - get position related to parent elements
			var element = _this;
			offsetElement = offsetElement||document.body;
			while (element && element!=offsetElement && element!=document && element.style.position!=pos) {
				point.x += element.offsetLeft;
				point.y += element.offsetTop;
				element=element.offsetParent||element.parentNode;
				pos = "absolute";
			}
			return point;
		}
		_this.pointInsideBox=function(point) {
			var p1 = _this.getAbsolutePosition();
			if (!(p1.x<=point.x && point.x<p1.x+_this.offsetWidth))
				return false;
			if (!(p1.y<=point.y && point.y<p1.y+_this.offsetHeight))
				return false;
			return true;
		}
		_this.getBoundingBox=function() {
			var p = _this.getAbsolutePosition();
			return {left:p.x, top:p.y, right:p.x+_this.offsetWidth, bottom:p.y+_this.offsetHeight};
		}	
		_this.showTooltip=function(fncFormat,offsetElement,delay) {
			var point = _this.getAbsolutePosition(offsetElement);
			var tooltip = document.body.appendChild(document.createElement("span"));
			tooltip.addClassName("tooltip hidden");
			tooltip.style.position = "absolute";	
			tooltip.style.left = point.x+"px";
			tooltip.style.top=(point.y+element.offsetHeight)+"px";
			
			var _mouseout=_this.onmouseout||function(e){}; // Store old method....
			var _mouseenter=_this.onmouseenter||function(e){}; // Store old method....
			
			tooltip.close=function() { window.clearTimeout(tooltip.timerID); tooltip.timerID=null; tooltip.parentNode.removeChild(tooltip); }

			if (!fncFormat(_this,tooltip)) {
				tooltip.close();
				return null;
			}

			//tooltip.onclick=tooltip.close;	
			/*
			tooltip.toggleSticky=function(e) {
				if (ClassName.has(tooltip,"sticky")) {
					ClassName.remove(tooltip,"sticky");
				} else {
					ClassName.add(tooltip,"sticky");
					element.onmouseenter=function(e){}
					element.onmouseout=function(e){}
				}
				return false;
			}*/
			_this.onmouseout=function(e) {
				if (tooltip.hasClassName("sticky")) return true;
				_this.onmouseenter=_mouseenter;
				_this.onmouseout=_mouseout;
				tooltip.close();
				return _mouseout(e);
			}
			tooltip.showTip=function() { 
				var bodyWidth = offsetElement?offsetElement.offsetWidth:document.body.offsetWidth;
				var bodyHeight = offsetElement?offsetElement.offsetHeight:window.screenHeight;
				
				tooltip.removeClassName("hidden"); 
				
				if ((tooltip.offsetLeft+tooltip.offsetWidth)>bodyWidth)
					tooltip.style.left = (bodyWidth-tooltip.offsetWidth)+"px";
				if ((tooltip.offsetTop+tooltip.offsetHeight)>bodyHeight)
					tooltip.style.top = (bodyHeight-tooltip.offsetHeight)+"px";			
			}
			if (delay) tooltip.timerID = window.setTimeout(tooltip.showTip,delay);
			else tooltip.showTip();
			return tooltip;
		}

		return element;
	}
	document.registerTag=function(tagName,creator) {
		if (!this._tagMap) document.useEZ();
		if (typeof(tagName)!="object") this._tagMap[tagName]=creator; 
		else for (var tag in tagName) this._tagMap[tag]=tagName[tag]; 
	}

	document.calculateIntersectingBox=function(box1,box2) {
		var left = Math.max(box1.left,box2.left);
		var top = Math.max(box1.top,box2.top);
		var right = Math.min(box1.right,box2.right);
		var bottom = Math.min(box1.bottom,box2.bottom);
		
		if (left>right) return null;
		if (top>bottom) return null;
		return {left:left, top:top, right:right, bottom:bottom};
	}
	document.isPointInsideBox=function(point,box) {
		return box.left<=point.x && point.x<=box.right && box.top<=point.y && point.y<=box.bottom;
	}

	var applyKeyHandling=function(target) {
		if (!document._keyTable) {
			document._keyTable=[];
			var t=document._keyTable;
			for (var i=0;i<256;++i) t.push(null);
			t[8]  = "Backspace";
			t[9]  = "Tab";
			t[13] = "Enter";
			t[33] = "PageUp";
			t[34] = "PageDown";
			t[35] = "End";
			t[36] = "Home";
			t[37] = "Left";
			t[38] = "Up";
			t[39] = "Right";
			t[40] = "Down";
			t[45] = "Insert";
			t[46] = "Delete";
		}
		if (!document._keyhandlers) document._keyhandlers=[];
		var dispatcher = function(e) {
			e=e||window.event; 
			var keynum=e.which||e.keyCode; 
			var key="onKey"+(document._keyTable[keynum]||"");
			if (target[key]) target[key](e,keynum);
			if (keynum==27) close(); 
			return true; 
		};
		var close = function() {
			if (target.onKeyHandlingEnd) target.onKeyHandlingEnd();
			document._keyhandlers.pop();
			document.onkeydown=document._keyhandlers.top();
		}
		document._keyhandlers.push(document.onkeydown=dispatcher);
		if (target.onKeyHandlingStart) target.onKeyHandlingStart();
		return close;
	}

	// Usage - document.debug(object, prefix/title)
	// If "object" implements 'dump' then this method is called, a default formatting is applied otherwise.
	document.enableDebug=function(enable) { document.debugEnabled = enable; }
	document.debugAlways=function(value,prefix) {
		var oldValue = document.debugEnabled;
		if (!oldValue) document.enableDebug(true);
		document.debug(value,prefix);
		if (!oldValue) document.enableDebug(false);
	}
	document.debug=function(value,prefix) {
		if (!document.debugEnabled) return;

		if (!document.debugWindow) {
			var _doc = document;
			_doc.debugWindow = window.open('','debug','width=800,height=600,scrollbars=yes');
			_doc.debugWindow.document.write("<html><head><title>Debug</title></head><body>");
			_doc.debugWindow.document.write("</body></html>");
			_doc.debugWindow.document.close();	 
			_doc.debugWindow.add=function(object,prefix) {
				var p = this.document.body; 
				var c = p.appendChild(this.document.createElement("DIV"));
				c.innerHTML = "["+typeof(object)+"]&gt; "+(prefix==undefined?"":prefix)+object;
			}
			_doc.debugWindow.clear=function() { this.document.body.innerHTML=""; }
			_doc.debugWindow.onunload=function(e) { _doc.debugWindow=null; }
		}
		if (value && value.dump) value.dump(document.debugWindow.document,prefix);
		else document.debugWindow.add(value,prefix);
	}

}