// ===========================================================================================================================================================================
Utils.pad = function(s, c, width)
{
	var _s = '';
	s = '' + s; // You must do this as then number 0 equates to the empty string and so padding a numerical zero will not work otherwise
	
	if(s != null && s != '')
	{				
		c = '' + c;

		for(var i = 0; i < width - s.length; i++)
			_s += c;
			
		_s += s;
	}
	
	return _s;
};

Utils.fix = function(val, digits) 
{
	return val.toFixed ? val.toFixed(digits ? digits : 2) : val;
};


Utils.toTitleCase = function(s, mcsupport, osupport)
{
	s = s == null ? null : s.replace(/\w\S*/g, function(txt){ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
	
	if(mcsupport)
		s = s.replace(/^Mc\w/, __mctitlecasedelegate);
		
	if(osupport)
		s = s.replace(/^O'\w/, __mctitlecasedelegate);
		
	return s;
}

function __mctitlecasedelegate(txt)
{
	return txt.substr(0, txt.length -1) + txt.charAt(txt.length -1).toUpperCase();
}

// ===========================================================================================================================================================================
ViewState = 
{
	get:function(id)
	{
		var el = Utils.get('CLIENTVIEWSTATE');
		if(el)
		{
			var o = eval(el.value.charAt(0) != '(' ? '(' + el.value + ')' : el.value); 
			o = (typeof(o) == "undefined") || (o == null) || (o == '') ? {} : o;
			
			return id ? o[id] : o;
		}
		else
			return null;
	},

	getArray:function(id)
	{
		var o = this.get(id);
		return o != null ? o.split('|') : null;
	},
	
	set:function(id, val)
	{
		var el = Utils.get('CLIENTVIEWSTATE');
		if(el)
		{		
			var o = this.get();
			o[id] = val;
			
			el.value = "(" + Utils.objectToJSON(o) + ")";
		}
	},
	
	setArray:function(id, arrval)
	{
		this.set(id, arrval.join('|'));
	}
}

// ===========================================================================================================================================================================
Evt.Handlers = 
{
	clickGo:function(url)
	{
		return {click:function(e, id, pt, data) { Utils.go(url); } };
	}
};

// ===========================================================================================================================================================================
Utils.Data =
{
	defaultObjectName:'object',
	skipFields:Utils.arrayExtend(['Events']),
	
	// API:Utils.Data:Convert an object to XML representation. Will recurse object hierarchies and arrays
	objectToXml:function(o, name, props) { return this.objectToXML(o, name, props); },
	objectToXML:function(o, name, props)
	{ 
		var _o = o;
		var wrap = !props || !props.noWrap;
		
		if(wrap)
		{
			var _o = {};
			_o[name ? name : this.defaultObjectName] = o;
		}
		
		return this._objectToXml(_o); 
	},
	_objectToXml:function(o)
	{
		var s = '';
		
		if(o == null || typeof(o) == 'undefined')
			s = null;
		else
		{
			if(Utils.getType(o) == 'Object')
			{				
				for(var el in o)
					if(typeof(o[el]) != 'function' && !this.skipFields.contains(el))
						s += this.genEl(el, Utils.getType(o[el]), this._objectToXml(o[el]));
			}
			else if(Utils.getType(o) == 'Array')
			{				
				for(var i = 0; i < o.length; i++)
					s += this.genEl('el', Utils.getType(o[i]), this._objectToXml(o[i]));
			}
			else
				s += this.esc(o);
		}
		
		return s;
	},
	
	genEl:function(el, type, val)
	{
		var s = '';		
		var type_s = type == 'Object' ? ' isobject="1"' : (type == 'Array' ? ' isarray="1"' : '');
		
		if(val == null || typeof(val) == 'undefined')
			s += '<' + el + ' isnull="1" />';
		else if(val == '')
			s += '<' + el + ' />';
		else
			s += '<' + el + type_s + '>' + val + '</' + el + '>';
			
		return s;
	},
	
	esc:function(s)
	{
		var _s = new String(s);
		
		return _s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	}
};

Utils.Date = 
{
	MONTHS:function(idx)
	{
		var a = new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December");
		return idx >= 1 && idx <= 12 ? a[idx - 1] : "INVALID";
	},
	
	DAYS:function(idx)
	{
		var a = new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); // Same indexes as returned by SQL Server DatePart()
		return idx >= 1 && idx <= 7 ? a[idx - 1] : "INVALID";
	},
	
	guess:function(ds, props)
	{
		// 3 part date; guess century if 2 digit year provided
		var re = /^(\d{1,2})[\/\.\-](\d{1,2})[\/\.\-](\d{2,4})$/;
		if(re.exec(ds))
		{
			var d = RegExp.$1;
			var m = RegExp.$2;
			var y = new String(RegExp.$3);
				
			if(y.length == 2 && (!props || !props.isAbsoluteYear)) 
			{
				var cfy = new Date().getUTCFullYear();
				var c = Math.round((cfy) / 100); // Current century
							
				if(props && props.centuryLogic == "forcepast")
				{
					// Forces year to be in the past by decrementing century if calculated year greater than current year
					// centuryThreshold is number of years that calculated year must be greater than current year by before the current century is used
					var ct = props && props.centuryThreshold ? props.centuryThreshold : 0; 
					
					if(parseInt(c + y) > (cfy - ct))
						c--;
				}
				
				y = c + y;
			}			
			else if(y.length == 3 && (!props || !props.isAbsoluteYear)) 
				return null;
		}
		// 2 part date; use current year
		else
		{
			re = /^(\d{1,2})[\/\.\-](\d{1,2})$/;
			if(re.exec(ds))
			{
				var d = RegExp.$1;
				var m = RegExp.$2;
				var y = new Date().getUTCFullYear();
			}
			else
				return null;
		}
		
		var d = this.isValid(parseInt(y, 10), parseInt(m, 10), parseInt(d, 10)) ? new Date(parseInt(y, 10), parseInt(m, 10) - 1, parseInt(d, 10)) : null;
		
		return d != null ? (props && props.format ? this.format(d, props.format, props) : d) : null;
	},
	
	isValid:function(y, m, d)
	{
		var aDays = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
		aDays[1] = this.isLeapYear(y) ? 29 : 28;
		
		return res = ((m >= 1 && m <= 12) && (d >= 1 && d <= aDays[m - 1]) && (y >= 1));
	},
	
	isLeapYear:function (y)
	{
		return (y % 4 == 0) && ((y % 100 != 0) || (y % 400 == 0));
	},
	
	// Format a datetime using a template
	format:function(dt, format, props)
	{
		var date = new Date(dt);
		dsep = props && props.dateSeparator ? props.dateSeparator : '/';
		tsep = props && props.timeSeparator ? props.timeSeparator : ':';
	
		if(dt == null || isNaN(date)) 
			return '';
						
		var hr = Utils.pad(date.getHours(), 0, 2) 
		var min = Utils.pad(date.getMinutes(), 0, 2) 
		var sec = Utils.pad(date.getSeconds(), 0, 2);
		
		var fy = date.getFullYear();
		var py = date.getYear();
		var m = date.getMonth() + 1;
		var mm = Utils.pad(date.getMonth() + 1, 0, 2);
		var d = date.getDate();
		var dd = Utils.pad(d, 0, 2);
		
		var tm = this.MONTHS(date.getMonth() + 1);
		var td = this.DAYS(date.getDay() + 1);
		
		var _template;
		
		if(format == 'gb')
			_template = 'dd/mm/yyyy';
		else if(format == 'us')
			_template = 'mm/dd/yyyy';
		else if(format == 'db')
			_template = 'yyyy/mm/dd';
		else if(format == 'packed')
			_template = 'yyyymmdd';
		else if(format)
			_template = format;
		else
			_template = 'dd/mm/yyyy';
			
		var res = _template.replace('ddd', td).replace('dd', dd).replace('d', d);
		res = res.replace('mmm', tm).replace('mm', mm).replace('m', m);
		res = res.replace('yyyy', fy).replace('yy', py);
		res = res.replace('yyyy', fy).replace('yy', py);

		res = res.replace('hh', hr);
		res = res.replace('nn', min);
		res = res.replace('ss', sec);
		
		res = res.replace(/\//g, dsep).replace(/:/g, tsep);
		
		return res;	
	},
	
	pack:function(d)
	{
		return this.formatDateTime(d, 'packed');
	},
	
	unpack:function(s)
	{
		var re = /(\d\d\d\d)(\d\d)(\d\d)/;
		if(re.exec(s))
			return new Date(RegExp.$1, parseInt(RegExp.$2) - 1, RegExp.$3);
		else
			return null;
	},
	
	add:function(date, value, datepart)
	{
		if(date == null)
			return null;
		else 
		{
			var d = new Date(date);
						
			if(datepart.charAt(0) == 'y')
				d.setFullYear(d.getFullYear() + value);
			else if(datepart.charAt(0) == 'm')
				d.setMonth(d.getMonth() + value);
			else if(datepart.charAt(0) == 'd')
				d.setDate(d.getDate() + value);			
							
			return d;
		}
	}
};

// ===========================================================================================================================================================================
Evt.trackCustom = function(eventid, handler, ido, param)
{
	if(typeof(ido) == 'string') // Yes, Utils.get does this, but as ido could also be a non-DOM object, I don't want to rely on it returning the right thing
		ido = Utils.get(ido);

	if(ido)
	{
		var dispatcher = typeof(ido.Events) != 'undefined' ? ido : (typeof(ido.EVENTS) != 'undefined' ? ido.EVENTS : null);
		if(dispatcher)
			dispatcher.attach(eventid, handler, param);
	}
},

Evt.CustomDispatcher = function(owner)
{
	this.owner = owner;
	
	// (Optional) registration if object wants to publish a list of events in advance
	this.register = function(id)
	{
		this.events[id] = Utils.arrayExtend([]);
		
		if(!this.registered)
			this.registered = {};
			
		this.registered[id] = true;
	};
	
	// Attach a handler and optional param to the event
	this.attach = function(id, handler, param)
	{
		if(this.registered && this.enforceRegistered && !this.registered[id])
			eval('Event_' + id + '_not_registered()');
	
		if(!this.events[id])
			this.events[id] = Utils.arrayExtend([]);
			
		this.events[id][this.events[id].length] = new Evt.CustomEvent(handler, param);
	};
	
	// Fire all handlers for an event
	this.fire = function(id, eventargs)
	{
		var handlers = this.events[id];
				
		if(handlers)
			for(var i = 0; i < handlers.length; i++)
				handlers[i].handler(this.owner, eventargs, handlers[i].param);
	};
	
	this.events = {};
	this.registered = null;
	this.enforceRegistered = true; // If true, as soon as any event is registered it becomes an error to attach an unregistered event
};

Evt.CustomEvent = function(handler, param)
{
	this.handler = handler;
	this.param = param;
};

