/**
* Removes leading and trailing spaces from string
*/
String.prototype.trim=function(){
	return this.replace(/&nbsp;/g," ").replace(/(^[ \t]*)|([ \t]*$)/g,"");
};

/************Array extensions************/
/**
*	Removes array element from specified location
*/
Array.prototype.removeAt=function(index){
	if(Number(index)<0 || this.length==0) 
		return false;
	for(var i=index;i<this.length;i++){
		this[i]=this[i+1];
	}
	this.length--;
};
/**
*	Removes specified element(s) from array
*/
Array.prototype.remove=function(value){
	for(var i = 0; i < this.length; i++)
		if (this[i]==value){
			this.removeAt(i);
			i--;
		}
};
/**
*	Inserts new value at specified location
*/
Array.prototype.insertAt=function(value, index){
	if (typeof index=='undefined' || index == this.length) this.push(value);
	else if (index == 0) this.unshift(value);
	else{
		var i = this.length;
		while(i>index){
			this[i] = this[i-1];
			i--;
		}
		this[index] = value;
	}
};
/**
*	Moves array element from one position (fromIndex) to another(toIndex)
*/
Array.prototype.moveTo=function(fromIndex, toIndex){
	if (fromIndex==toIndex) return;
	var temp = this[fromIndex];
	this.removeAt(fromIndex);
	this.insertAt(temp, toIndex);
};
/**
*	Calculates number of elements that satisfy specified condition
*/
Array.prototype.countBy=function(testFunction){
	var count = 0;
	for(var i = 0; i < this.length; i++)
		if (testFunction(this[i], i)) count++;
	return count;
};

/**
 * Creates new collection of function results received after applying argument function on each element of source collection
 * @param fSelector function to apply on each element of source collection
 * @return collection of function results received after applying argument function on each element of source collection
 */
Array.prototype.select=function(fSelector){
	var result = new Array();
	for(var i = 0; i < this.length; i++)
		result.push(fSelector(this[i], i));
	return result;
};

/**
 * Creates new collection of elements of source collection that satisfy argument function
 * @param fFilter filter function to test elements of source collection by
 * @return collection of elements of source collection that satisfy argument function
 */
Array.prototype.where=function(fFilter){
	var result = new Array();
	for(var i = 0; i < this.length; i++)
		if(fFilter(this[i], i))
			result.push(this[i]);
	return result;
};

/**
 * Gets first element of collection
 * @return first element of collection
 */
Array.prototype.first=function(){
	return this[0];
};

/**
 * Gets last element of collection
 * @return last element of collection
 */
Array.prototype.last = function(){
	return this[this.length - 1];
};

/**
 * Finds location of value in source collection
 * @param value Value to find in collection
 * @return -1 if element was not found, or its index otherwise
 */
Array.prototype.indexOf = function(value){
	for(var i = 0; i < this.length; i++)
		if (this[i] == value)
			return i;
	return -1;
};

/**
 * Checks if element is in collection
 * @param value Element to check
 * @return True if element was found
 */
Array.prototype.contains=function(value){
	return this.indexOf(value) != -1;
};

/**
 * Summarizes all numbers found in collection
 * @return Sum of all numeric values in array
 */
Array.prototype.sum=function(){
	var sum = 0;
	for(var i = 0; i < this.length; i++){
		var val = parseFloat(this[i]);
		if (val != Number.NaN)
			sum += val;
	}
	return sum;
};

/**
*	Sorts elements by one or more property names that each collection element is expected to have
*	E.g.:
*	var arr = new Array(.....);
*	arr.sortByMany(['stopTime']);
*	//or
*	arr.sortByMany(['stopTime ASC']);
*	//or
*	arr.sortByMany(['stopTime DESC']);
*	//or
*	arr.sortByMany(['startTime ASC,finishTime ASC,registerDate DESC']);
*/
Array.prototype.sortByMany=function(arrFields, arrComparers){
	var comparers = new Array();
	for(var i = 0; i < arrFields.length; i++){
	    var desc = arrFields[i].toUpperCase().indexOf(' DESC') != -1;
	    var sortField = arrFields[i].replace(/( DESC)|( ASC)$/ig, '');
	    var comparerName = arrComparers[i];
	    
	    //TODO Optimization candidate 2
	    //var funcStr = "if (v1."+sortField+"==v2."+sortField+") return 0;else if (v1."+sortField+" < v2."+sortField+") return " + (desc?'':'-')+ "1;else return " + (desc?'-':'')+ "1;";
		var funcStr = "return "+(desc?"-1*":"")+"window.Utils.Compare(v1."+sortField+", v2."+sortField+", '" + comparerName + "');";
		comparers[i] = new Function("v1,v2", funcStr);
	}
	var comparer = function(v1,v2){
		for(var i = 0; i < comparers.length;i++){
			var result = comparers[i](v1,v2);
			if (result != 0) return result;
		}
		return 0;
	};
	return this.sort(comparer);
};

/**
 * Filters collection of objects by filterExpression given
 * @param filterExpression string or object to search in objects collection
 * @param arrCheckForProperties array of properties values of which will be checked while filtering collection
 * @param funcDataObjectExtractor optional. Function to format element before applying filter rules
 * @return new collection of elements that satisfy given criteria
 */
Array.prototype.filter = function(filterExpression, arrCheckForProperties, funcDataObjectExtractor){
	if (filterExpression==null || filterExpression==undefined)
		return this;
	else{
		var result = new Array();
		for(var i = 0; i < this.length; i++)
			for(var j = 0; j < arrCheckForProperties.length; j++){
				var dataObj = (funcDataObjectExtractor?funcDataObjectExtractor(this[i]):this[i]);
				var val = dataObj[arrCheckForProperties[j]];
				if (!(val==null || val==undefined || (val != filterExpression && val.toString().indexOf(filterExpression) == -1)))
					result.push(this[i]);
			}
		return result;
	}
};


String.prototype.htmlEscape = function(){
	return this.replace("<", "&lt;").replace(">", "&gt;");
};

var DatesAPI;
var GroupsAPI;
var ConverterAPI;
		
// The set of functions for dates. InitDates to make it working
function DatesAPIObject(){
	var self = this;
	// make start and end date connected to avoid incorrect date interval.
	this.initDates = function(startDateID,endDateID){
		$('#'+endDateID).datepicker({duration:0,dateFormat:ConverterAPI.options.dateFormat,beforeShow:function(input){self._backupData("end",input.value);}});
		$('#'+startDateID).datepicker({duration:0,dateFormat:ConverterAPI.options.dateFormat,beforeShow:function(input){self._backupData("start",input.value);}});
		this.startDateID = startDateID;	
		this.endDateID = endDateID;	
		this.insertDate(startDateID,_previousDateStart);	
		this.insertDate(endDateID,_previousDateEnd);	
		$('#'+startDateID).change(function(){self.fixDateAfterChange(this,"start");});
		$('#'+endDateID).change(function(){self.fixDateAfterChange(this,"end");});
		/*
		$('#'+startDateID).bind(
			'dpClosed',
			function(e, selectedDates){
				var d = selectedDates[0];
				if (d) {
					d = new Date(d);
					$('#'+endDateID).dpSetStartDate(d.addDays(1).asString());
				}
			}
		);
		$('#'+endDateID).bind(
			'dpClosed',
			function(e, selectedDates){
				var d = selectedDates[0];
				if (d) {
					d = new Date(d);
					$('#'+startDateID).dpSetEndDate(d.addDays(-1).asString());
				}
			}
		);
		*/
	};
	this.initDatesQuickSelection = function(id,startDateID,endDateID){				
		$('#'+id).change(function(o){
			var sDate = new Date();
			var eDate = new Date();
			sDate.setHours("0");
			sDate.setMinutes("0");
			eDate.setHours("23");
			eDate.setMinutes("59");
			sDate.setSeconds("0");
			eDate.setSeconds("0");
			switch(this.value){
				case "today":
				break;
				case "yesterday":
					sDate.addDays(-1);
					eDate.addDays(-1);
				break;
				case "thisweek":
					var d = sDate.getDay();
					sDate.addDays(-d);
					eDate.addDays(6-d);
				break;
				case "lastweek":
					var d = sDate.getDay();
					sDate.addDays(-d-7);
					eDate.addDays(6-d-7);
				break;
				case "currentmonth":
					sDate.setDate(1);
					eDate.setDate(1);
					
					eDate.addMonths(1);
					eDate.setHours("0");
					eDate.setMinutes("0");
					eDate.addMinutes(-1);
				break;
				case "lastmonth":
					sDate.addMonths(-1);
					eDate.addMonths(-1);
					sDate.setDate(1);
					eDate.setDate(1);
					
					eDate.addMonths(1);
					eDate.setHours("0");
					eDate.setMinutes("0");
					eDate.addMinutes(-1);
				break;
			};
			self.insertDate(startDateID,sDate);
			self.insertDate(endDateID,eDate);
		});
	};
	//////////////////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////////////////
	_previousDateStart = new Date();
	_previousDateEnd = new Date();
	_previousDateStart.setSeconds(0);
	_previousDateStart.setMinutes(0);
	_previousDateStart.setHours(0);
	_previousDateEnd.setSeconds(0);
	_previousDateEnd.setMinutes(59);
	_previousDateEnd.setHours(23);
	_numberFormat = function(n){
		if (n.toString().length==1) return "0"+n;
		return n;
	};
	this._backupData = function(type,v){
		if (type=="start")
			_previousDateStart = ConverterAPI.stringToDate(v,ConverterAPI.options.dateFormat+" HH:nn");
		if (type=="end")
			_previousDateEnd = ConverterAPI.stringToDate(v,ConverterAPI.options.dateFormat+" HH:nn");	
	};
	this.fixDateAfterChange = function(node,dateType){
		var v = node.value+(node.value.indexOf(":")>-1?"":(dateType=="end"?" 23:59":" 00:00"));
		var dtm = ConverterAPI.stringToDate(v,ConverterAPI.options.dateFormat+" HH:nn");
		if (v==ConverterAPI.dateTimeFormatSmall(dtm)){
			_previousDate = dtm;
		}
		this.insertDate(node.id,_previousDate);	
	};
	this.insertDate = function(id,date){
		$('#'+id).val(ConverterAPI.dateTimeFormatSmall(date));
	};
	_getDateById = function(id){
		var v = $('#'+id).val();
		var date = ConverterAPI.stringToDate(v);
		return date;
	};
	this.getStartDate = function(){
		return _getDateById(this.startDateID);
	};
	this.getEndDate = function(){
		return _getDateById(this.endDateID);
	};
	this.getStartDateValue = function(){
		return $('#'+this.startDateID).val();
	};
	this.getEndDateValue = function(){
		return $('#'+this.endDateID).val();
	};
};
DatesAPI = new DatesAPIObject();


///////////////////////////////////////////////////////
// Some converter fucntioanlity
function ConverterAPIObject(){
	var self = this;
	this.options = {
		distance: "km", // {km,mile}
		distanceRound: "0.01",
		dateFormat: "dd M yy",
		timeFormat: "HH:nn:ss",
		timeFormatSmall: "HH:nn",
		jsonDateFormat: "yy-mm-ddTHH:nn:ssZ"
	};
	
	// Init options
	this.initOptions = function(options){
		for (var i in options){
			this.options[i] = options[i];	
		}
	};
	this._zeroPad=function(v) {
		return v < 10 ? '0' + v : v;
	};
	this._round = function(v){
		var r = 1/this.options.distanceRound;
		v = Math.round(v*r)/r;
		return v;
	};
	this._getAMPMHours = function(h){
		if (h>12) h-=12;
		return h;
	};
	this._getAMPM = function(h){
		var res = (h>12)?"PM":"AM";
		return res;
	};
	
	
	// convert string to minutes
	this.strToMinutes = function(v){
		var res = Math.round(this.strToSeconds(v)/60);
		return res;
	};
	// convert string to seconds
	this.strToSeconds = function(v){
		var s = v.split(":");
		var res = Math.round(s[2]*1+s[1]*60+s[0]*3600);
		return res;
	};
	
	// get datetimeFormat for the grid sort.
	this.dateTimeFormatForGrid= function(){
		var str = this.options.dateFormat+" "+this.options.timeFormat;
		str = str.replace(/[p]+/gi,"");
		str = str.replace(/n/gi,"i");
		str = str.replace(/([ymdhis]+)/gi,function($1){return $1.substr(0,1);});
		return str.toLowerCase();
	};
	this.timeFormatForGrid = function(){
		var str = this.options.timeFormat;
		str = str.replace(/[p]+/gi,"");
		str = str.replace(/n/gi,"i");
		str = str.replace(/([his]+)/gi,function($1){return $1.substr(0,1);});
		return str.toLowerCase();
	};
	
	/**
	 * Converts internal string representation of duration into UI duration string + surrounds result with DIV tags with proper set title
	 * @param value - duration to convert(string)
	 * @return formatted duration
	 * @type string
	 */
	this.durationToDecoratedString = function(value){
		var result = "<div title='" + value + "'>";
		if (!value||value==''||value=='00:00:00')
			result += "<span style='display:none;'>00:00:00</span>&nbsp;";
		else{
			result += this.durationToPlainString(value);
		}
		return result + "</div>";
	};
	/**
	 * Converts internal string representation of duration into UI duration string
	 * @param value - duration to convert(string)
	 * @return formatted duration
	 * @type string
	 */
	this.durationToPlainString = function(value){
		var result = "";
		if (!value||value==''||value=='00:00:00')
			result = "&nbsp;";
		else{
			var parser = /((\d*)[.])?(\d*):(\d*):(\d*)/;
			parser.exec(value);
			var days = RegExp.$2==""?"0":RegExp.$2;
			var hours = RegExp.$3==""?"0":RegExp.$3;
			var minutes = RegExp.$4==""?"0":RegExp.$4;
			var seconds = RegExp.$5==""?"0":RegExp.$5;
			
			days = parseInt(days.replace(/^0*/ig,''));
			hours = parseInt(hours.replace(/^0*/ig,''));
			minutes = parseInt(minutes.replace(/^0*/ig,''));
			seconds = parseInt(seconds.replace(/^0*/ig,''));
			
			if (days) result += days + "d ";
			if (hours) result += hours + "h ";
			if (minutes) result += minutes + "m ";
			if (!days && !hours && !minutes && seconds) result += "<1m";
		}
		return result;
	};
	
	// Date functions.
	this.dateFormat=function(d,format,isUTC){
		var str = format||this.options.dateFormat;
		str = str
			.split('yy').join(isUTC?d.getUTCFullYear():d.getFullYear())
			.split('y').join(((isUTC?d.getUTCFullYear():d.getFullYear()) + '').substring(2))
			.split('M').join(Date.abbrMonthNames[isUTC?d.getUTCMonth():d.getMonth()])
			.split('mm').join(this._zeroPad((isUTC?d.getUTCMonth():d.getMonth())+1))
			.split('m').join((isUTC?d.getUTCMonth():d.getMonth())+1)
			.split('dd').join(this._zeroPad(isUTC?d.getUTCDate():d.getDate()))
			.split('d').join(isUTC?d.getUTCDate():d.getDate());
		return str;	
	};
	this.timeFormat=function(d,format,isUTC){
		var str = format||this.options.timeFormat;
		str = str
			.split('HH').join(this._zeroPad(isUTC?d.getUTCHours():d.getHours()))
			.split('hh').join(this._zeroPad(this._getAMPMHours(isUTC?d.getUTCHours():d.getHours())))
			.split('pp').join(this._zeroPad(this._getAMPM(isUTC?d.getUTCHours():d.getHours())))
			.split('nn').join(this._zeroPad(isUTC?d.getUTCMinutes():d.getMinutes()))
			.split('ss').join(this._zeroPad(isUTC?d.getUTCSeconds():d.getSeconds()));
		return str;	
	};
	//date time format that is created as a result of concatenating of date and time formats
	this.dateTimeFormat=function(d){
		return this.dateFormat(d)+" "+this.timeFormat(d);
	};
	// date time format in small format( may be without seconds)
	this.dateTimeFormatSmall=function(d){
		return this.dateFormat(d)+" "+this.timeFormat(d,this.options.timeFormatSmall);
	};
	//convert string to date
	this.stringToDate=function(str,format,isUTC){
		var f = format||(this.options.dateFormat+" "+this.options.timeFormat); 
		var d = new Date('01/01/1977');
		var arr = str.split(/[\\\/\.\:\- TZ]/g);
		var arrF = f.split(/[\\\/\.\:\- TZ]/g);
		if (arr.length>arrF.length) return d;
		for (var i=0; i<arr.length;i++){
			switch(arrF[i]){
				case "yy":
					isUTC?d.setUTCFullYear(Number(arr[i])):d.setFullYear(Number(arr[i]));
				break;
				case "M":
					for (var j=0; j<Date.abbrMonthNames.length; j++)
						if (Date.abbrMonthNames[j].toLowerCase()==arr[i].toLowerCase()){
							arr[i] = j+1;
							break;
						}
				case "mm":
				case "m":
					isUTC?d.setUTCMonth(Number(arr[i]-1)):d.setMonth(Number(arr[i]-1));
				break;
				case "dd":
				case "d":
					isUTC?d.setUTCDate(Number(arr[i])):d.setDate(Number(arr[i]));
				break;
				case "HH":
					isUTC?d.setUTCHours(Number(arr[i])):d.setHours(Number(arr[i]));
				break;
				case "hh":
					var h = arr[i];
					h+=(str.toLowerCase().indexOf("am")>-1)?0:(str.toLowerCase().indexOf("pm")>-1?12:0);
					isUTC?d.setUTCHours(Number(h)):d.setHours(Number(h));
				break;
				case "nn":
					isUTC?d.setUTCMinutes(Number(arr[i])):d.setMinutes(Number(arr[i]));
				break;
				case "ss":
					isUTC?d.setUTCSeconds(Number(arr[i])):d.setSeconds(Number(arr[i]));
				break;
			}
		}
		return d;
	};
	
	//JSON functions
	this.parseJSONDate=function(str){
		//var d =  this.stringToDate(str,"yyyy-mm-ddTHH:nn:ssZ",true)
		var arr = str.split(/[\\\/\.\:\- TZ]/gi);
		var d = new Date();
		d.setTime(Date.UTC(Number(arr[0]),Number(arr[1])-1,Number(arr[2]),Number(arr[3]),Number(arr[4]),Number(arr[5])));
		return d;
	};
	this.parseJSONTime=function(str){
		var d = str.replace(/^.*T/ig, "").split(":");
		if (d[0].split(".").length==2) d[0] = d[0].split(".")[1]+d[0].split(".")[0]*24;
		d[2] = Math.round(parseFloat(d[2]));
		var res = this._zeroPad(d[0]*1)+":"+this._zeroPad(d[1]*1)+":"+this._zeroPad(d[2]*1);
		return res;
	};
	this.dateToJSON=function(d){
		return this.dateFormat(d,"yy-mm-dd",true)+"T"+this.timeFormat(d,"HH:nn:ss",true)+"Z";
	};
	
	//Distance functions
	this.milesToKm=function(n){
		return this._round(n*1.609344);
	};
	this.kmToMiles=function(n){
		return this._round(n*0.621371192);
	};
	this.fixDistance = function(v){
		if (this.options.distance=="km") return this._round(v);
		return this.kmToMiles(v);
	};
}

ConverterAPI = new ConverterAPIObject();

//**************** JQUERY PLUGINS *********************/

function createToolButton(container, options){
		var button = $("<div></div>")
		.text(options.title)
		.addClass("toolButton")
		.addClass(options.pressed ? "pressed" : "")
		.width(options.width?options.width:80)
		.appendTo(container)
		.click(function(event){
			var $this = $(this);
			if (options.pushbutton){
				if ($this.hasClass("pressed")){
					$this.removeClass("pressed");
					if (options.press) options.press.apply($this, [event]);
				}
				else
					$this.addClass("pressed");

			} 
			else if (options.press) 
				options.press.apply($this, [event]);
			if (options.click) 
				options.click.apply($this, [event]);
		});
	if (options.id) 
		button.attr("id", options.id);
	if (options.popup)
		button.popupWindow(options.popup);
	return button;
}

//{title:"",pressed:bool,click:function,icon:"",width:100,pushbutton:false,id:"",press:function,popup:{containerID:"containerID", onShow:function,onHide:function}}
/**
*	Adds something that looks and behaves like a button into a container assumed to be a toolbar :)
*	@options JSON object that may container the following properties:
*		@title text to be indicated inside of button
*		@pressed [bool] if true, makes button to look like in "pressed" state. Makes sence only if pushbutton is true 
*		@click function to process onClick events
*		@icon one day it will be a URL to icon to be indicated before button's title
*		@width button width
*		@pushbutton if true, button will be in 2 states: pressed/released
*		@id button id to assign during html element creation
*		@press function to be called when button enteres Pressed state
*		@popup JSON object that contains settings for popup window to be shown when user clicks on button. See more details in popupWindow manual
*/
jQuery.fn.addToolButton = function(options){
	this.button = createToolButton(this, options);
	return this;
};

jQuery.fn.addMenuButton = function(options){
	this.button = createToolButton(this, options)
		.addClass("menuButton")
		.click(function(){
			if (this.parentNode.popupWin)
				this.parentNode.popupWin.hide();
		});
	return this;

};

jQuery.fn.addMenuSeparator = function(){
	this.separator = $("<hr />")
	.addClass("menuSeparator")
	.appendTo($(this));
	return this;
};

/**
*	Simple popup window. Being assigned to custom container it will show itself when user clicks on it.
*	@options JSON object with init settings. E.g. {containerID:"someDivID",onShow:function(){},onHide:function(){}}
*/
jQuery.fn.popupWindow = function(options){
	//construction
	var offset = $(this).offset();
	var popupWin = $("#" + options.containerID)
		.addClass("popupWindow")
		.css({left:offset.left, top:offset.top + $(this).outerHeight()})
		.hide();
	//reference to container
	var trigger = this.get(0);
	popupWin.get(0).popupWin = popupWin;
	trigger.popupWin = popupWin; //add reference to menu instance to 
	//events and behavior
	$(document.body).click(function(event){
		function isOver(event, target){
			var x = event.pageX;
			var y = event.pageY;
			var leftTopCorner = target.offset();
			var rightBottomCorner = {left:leftTopCorner.left + target.width(), top: leftTopCorner.top + target.height()};
			return true
					&& x >= leftTopCorner.left 
					&& x <= rightBottomCorner.left 
					&& y >= leftTopCorner.top
					&& y <= rightBottomCorner.top;
			};
		//click on button whereas popup window is closed
		if (isOver(event, $(trigger)) && popupWin.css("display") == "none"){
			if (options.onShow) options.onShow.apply(popupWin, [event]);
			popupWin.show();
			return true;
		}
		//popup window is closed - nothing else matters
		if (popupWin.css("display") == "none") 
			return true;
		//click somewhere on the page. Need to check coordinates
		if (!isOver(event, popupWin) ){
			if (options.onHide) options.onHide.apply(popupWin, [event]);
			popupWin.hide();
		}
		return true;
	});
	return popupWin;
};
/**
*	Gets value indicating whether toolbar button is pressed
*	@param state - if provided - pressed state to be assigned to button
*/
jQuery.fn.pressed = function(state){
	if (state == undefined)
	return $(this).hasClass("pressed");
	else if (state && !$(this).hasClass("pressed"))
		$(this).addClass("pressed");
	else if (!state && $(this).hasClass("pressed"))
		$(this).removeClass("pressed");		
};

/**
 * Adds "waiting" dialog to containter specified
 */
jQuery.fn.addProgressDialog = function(){
	var $container = $(this);
	var originalOverflow = null;
	var $panel = $("<div class='progressDialog'><img border='0' src='/wait.gif' /></div>");
	$panel.hide();
	$panel.appendTo(document.body);
	$panel.resize = function(){
		var offset = $container.offset();
		$panel.css({
			"top":offset.top,
			"left":offset.left
		});
		$panel.width($container.width());
		$panel.height($container.height() - 2);
		$panel.find("img").css("margin-top", ($container.height() - 30) / 2);
	};
	$panel.start = function(){
		$panel.resize();
		originalOverflow = $container.css("overflow");
		$container.css("overflow", "hidden");
		$panel.show();
	};
	$panel.stop = function(){
		$panel.hide();
		$container.css("overflow", originalOverflow);
	};
	$(window).bind("resize", function(){
		if ($panel.css("display") == "block"){
			setTimeout(function(){$panel.resize();}, 100);
		}
	});
	return $panel;
};

$.extend({
	  queryString: function(){
	    var vars = [], hash;
	    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
	    for(var i = 0; i < hashes.length; i++)
	    {
	      hash = hashes[i].split('=');
	      vars.push(hash[0]);
	      vars[hash[0]] = hash[1];
	    }
	    return vars;
	  }
	});

/**
 * 	Clones given object
 * 	@param Object to clone
 * 	@return shallow copy of given object
 */
window.cloneObject = function(prototype){
	var clone = {};
	for(var prop in prototype)
		if (typeof prototype[prop] != 'function')
			clone[prop] = prototype[prop];
	return clone;
};

function component(){
	this.loadInto = function(){
		alert("'loadInto' method must be overwritten in descendant classes!");
	};
	
	this.load = function(containerId, url, callback){
		this.loaded = false;
		var self = this;
		$("#"+containerId).load(url + "?un="+Date.parse(new Date()), null, function(){
			self.loaded = true;
			self.initialize();
			if (callback) 
				callback();
		});
	};
	
	this.initialize = function(){
		alert("'initialize' method must be overwritten in descendant classes!");
	};
	
	this.resize = function(){
		alert("'resize' method must be overwritten in descendant classes!");
	};
}

function eventsCollectionObj(){
	this._eventsCache = {};
	var self = this;
	this.attachEvent=function(strEvName, fnHandler){
		if (!self._eventsCache[strEvName])
			self._eventsCache[strEvName] = new Array();				
		if (!self._eventsCache[strEvName].contains(fnHandler))
			self._eventsCache[strEvName].push(fnHandler);
	};
	
	this.prependEvent=function(strEvName, fnHandler){
		if (!self._eventsCache[strEvName])
			self._eventsCache[strEvName] = new Array();				
		if (!self._eventsCache[strEvName].contains(fnHandler))
			self._eventsCache[strEvName].insertAt(fnHandler, 0);
	};
	
	this.detachEvent=function(strEvName, fnHandler){
		if (self._eventsCache[strEvName] && self._eventsCache[strEvName].contains(fnHandler))
			self._eventsCache[strEvName].remove(fnHandler);
	};

	this.detachEvents=function(strEvName){
		self._eventsCache[strEvName] = new Array();
	};

	
	this.fireEvent=function(strEvName, objSender, arrArgs){
		if (self.hasEventHandler(strEvName))
			for(var i = 0; i < self._eventsCache[strEvName].length && self._eventsCache[strEvName][i].apply(objSender, (arrArgs instanceof Array ? arrArgs : (arrArgs ? [arrArgs] : []))) != false; i++) ;
	};
	
	this.hasEventHandler=function(strEvName){
		return self._eventsCache[strEvName] && self._eventsCache[strEvName].length > 0;
	};
}

/**
 * Base super-class for event handling classes
 * @return undefined
 */
function iEventsContainer(){
	var self = this;
	/**
     * Collection of events and their handlers
     */
    this.events = new eventsCollectionObj();
    
    /**
     * Attaches handler to specified event
     * @param strEvName Event name to attach handler to
     * @param fnHandler function to be executed when event fires
     * @return undefined
     */
    this.attachEvent = function(strEvName, fnHandler){
    	self.events.attachEvent(strEvName, fnHandler);
    };
    
    this.prependEvent = function(strEvName, fnHandler){
    	self.events.prependEvent(strEvName, fnHandler);
    };
    
    this.detachEvent = function(strEvName, fnHandler){
    	self.events.detachEvent(strEvName, fnHandler);
    };
};