/*
 *	adenin.core.js
 *	Core adenin JS framework
 *	2009-01-20 fk
 */

(function($){

	$.adenin = $.adenin || {};
	$at = $.adenin;

	$.extend($at,
	{
		/*
			Inherits a subClass from a superClass.
			Usage:
			var SubClass = $at.extend(SuperClass,
			{
				constructor: function()
				{
					// SubClass constructor code ...
				},
				method1: function(arg, ...)
				{
					// SubClass code for method1 ...
				}
			});
		*/
		extend: function(subClass, superClass, overrides)
		{
			var className;

			if (typeof subClass == 'string')
			{
				// Actually called as $at.extend(className, superClass, overrides):
				className = subClass;
				subClass = undefined;
			}
			else if (typeof subClass == 'object')
			{
				// Actually called as $at.extend(overrides, superClass):
				overrides = subClass;
				subClass = undefined;
			}
			else if (typeof superClass == 'object')
			{
				// Actually called as $at.extend(superClass, overrides):
				overrides = superClass;
				superClass = subClass;
				subClass = undefined;
			}

			if (!subClass)
			{
				// Create subClass: Use a constructor if provided in overrides;
				// otherwise create one which simply calls the constructor of superClass.
				subClass = overrides.constructor == Object.prototype.constructor ?
						function(){superClass.apply(this, arguments);} :
						overrides.constructor;
			}

			if (superClass)
			{
				// Create an empty class M with the prototype of superClass.
				var M = function(){};
				M.prototype = superClass.prototype;

				// Create an M instance and make it the prototype of subClass.
				subClass.prototype = new M();

				subClass.prototype.superclass = superClass.prototype;
			}

			subClass.prototype.constructor = subClass;

			// Apply member overrides
			$at.override(subClass, overrides);

			// Add members to subClass directly:
			subClass.superclass = superClass.prototype;
			subClass.extend = function(name, o){return $at.extend(name, subClass, o);};
			subClass.override = function(o){$at.override(subClass, o);};

			if (!className)
				className = overrides.className;
			if (className)
			{
				subClass.__className = className;
			}

			return subClass;
		},

		/*
			Adds or overrides methods in the protype of targetClass.
			targetClass: The class to override.
			overrides: An object literal containing one or more methods to add to targetClass.
		*/
		override: function(targetClass, overrides)
		{
			if (overrides)
			{
				for (var name in overrides)
				{
					var member = overrides[name];

					if (typeof member === 'undefined')
						continue;

					if ($.isFunction(member))
					{
						member.__methodName = name;
					}
					targetClass.prototype[name] = overrides[name];
				}
				if ($.browser.msie && overrides.hasOwnProperty('toString'))
				{
					targetClass.prototype.toString = overrides.toString;
				}
			}
		}
	});

	$.extend($at,
	{
		Class: $at.extend('Class', function(){},
		{
			base: function base()
			{
				var caller = base.caller || arguments.callee.caller, proto = this.classPrototype(caller);
				if (proto && (proto = proto.superclass))
				{
					return proto[caller.__methodName].apply(this, arguments);
				}
			},
			classPrototype: function classPrototype(caller)
			{
				if (!caller) caller = classPrototype.caller || arguments.callee.caller;
				var methodName = caller.__methodName;
				var proto = this.constructor.prototype;
				var i = 99;
				while (!proto.hasOwnProperty(methodName) || caller !== proto[methodName])
				{
					proto = proto.superclass;
					if (!proto || i-- <= 0)
						return null;
				}
				return proto;
			}
		})
	});


	$at.Observable = $at.Class.extend('Observable',
	{
		constructor: function(options)
		{
			this.events = {};
		},

		/*
			Registers an event handler.
			eventName: Name of the event.
			fn: The function to be called when the event is raised.
			options: An object of options which may include:
				context: The context ("this" object) for the handler function. Default: The current Observable instance.
				single: If true, the handler is executed at most once and then automatically removed.

			Alternate usage:
			obj.addEventHandler(
			{
				myEvent1:
					function(o){ ... },
				myEvent2:
				{
					fn: this.myMethod,
					context: that
				}
			},
			{
				context: this
			});
		*/
		addEventHandler: function(eventName, fn, options)
		{
			if (typeof eventName === 'object')
			{
				var o = eventName;
				options = fn;
				for (eventName in o)
				{
					if (eventName != 'context')
					{
						var handler = o[eventName];
						this.addListener(eventName, handler.fn || handler, handler.context || context);
					}
				}
			}
			else
			{
				var event = this.events[eventName];
				if (!event || event === true)
				{
					event = { handlers: [] };
					this.events[eventName] = event;
				}
				event.handlers.push($.extend({fn:fn}, options));
			}
		},

		/*
			Raises ("fires") an event.
			eventName: Name of the event.
			args: Optional parameters to pass to event handlers.
		*/
		raiseEvent: function()
		{
			var args = $.makeArray(arguments), eventName = args.shift();
			var event = this.events[eventName];
			if (event && event.handlers)
			{
				var handlers = event.handlers.slice(0);
				for (var i = 0, l = handlers.length; i < l; i++)
				{
					var handler = handlers[i];
					if (handler.single)
						event.handlers.splice(i, 1);
					handler.fn.apply(handler.context || this, args);
				}
			}
		}
	});


	var Culture = $at.Class.extend('Culture',
	{
		constructor: function(options)
		{
			$.extend(this,
			{
				language: 'en',
				decimalSeparator: '.',
				negativeSign: '-',
				groupSize: 3,
				groupSeparator: ','
			}, options);
			this.decimalCode = this.decimalSeparator.charCodeAt(0);
			this.minusCode = this.negativeSign.charCodeAt(0);
			this.groupCode = this.groupSeparator.charCodeAt(0);
			this.reDecimalSeparator = new RegExp('\\' + this.decimalSeparator, 'g');
			this.reNonNumChars = new RegExp('[^\\d\\' + this.decimalSeparator + ']', 'g');
			this.reDecimal = new RegExp('(\\d*)(?:\\' + this.decimalSeparator + '(\\d*))?');
		},
		isNumericKey: function(k)
		{
			return (k > 47 && k < 58)
				|| k == null || k == 0 || k == 8 || k == 9 || k == 13 || k == 27
				|| k == this.decimalCode || k == this.minusCode || k == this.groupCode;
		},
		parseDecimal: function(n, p)
		{
			if (typeof(n) !== 'string')
				n = n.toString();

			var neg = (n.indexOf(this.negativeSign) >= 0);

			// Remove everything except digits and decimal separator
			n = n.replace(this.reNonNumChars, '');

			var m = n.match(this.reDecimal);
			if (!m) return 0;

			var f = m[1];

			if (neg)
				f = '-' + f;

			if (m.length >= 3 && typeof(m[2]) !== 'undefined' && m[2] !== '')
			{
				var d = m[2];
				if (d.length > p)
					d = d.substr(0, p)
				f += '.' + d;
			}
			return new Number(f);
		},
		formatDecimal: function(f, p)
		{
			f = new Number(f);

			if (isNaN(f))
				f = new Number(0);

			var w = (new Number(f)).toFixed(p);
			var neg;

			if (w.substr(0, 1) === '-')
			{
				neg = true;
				w = w.substr(1);
			}

			var l = w.length - this.groupSize;

			if (p > 0)
			{
				w = w.substr(0, w.length - p - 1) + this.decimalSeparator + w.substr(w.length - p);
				l -= (p + 1);
			}

			while (l > 0)
			{
				w = w.substr(0, l) + this.groupSeparator + w.substr(l);
				l -= this.groupSize;
			}

			if (neg)
				w = this.negativeSign + w;

			return w;
		}
	});

	var Page = $at.Observable.extend('Page',
	{
		constructor: function(options)
		{
			this.events =
			{
				/*
					Raised when content is added or modified.
					handler(page, element)
					page: The Page object.
					element: The DOM element which has received new content.
				*/
				load: true
			};
		},
		init: function(options)
		{
			var opts = $.extend({ sitePath: '', boWrapper: 'portal.aspx' }, options);
			if (typeof(opts.mvcRoot) === 'undefined')
				opts.mvcRoot = opts.sitePath + 'Portal.mvc/';
			// Global legacy variables
			$.extend(window,
			{
				PortalURL: opts.sitePath,
				HomePage: opts.boWrapper,
				HomeURL: opts.sitePath + opts.boWrapper
			});
			if (opts.preloadLibs)
			{
				if (!$at.require.loadedLibs) $at.require.loadedLibs = {};
				$.each(opts.preloadLibs, function(i, lib)
				{
					if ($at.require.loadedLibs[lib])
					{
						$at.log('Warning:', lib, 'was already loaded dynamically.');
					}
					else
					{
						$at.require.loadedLibs[lib] = 'preload';
						$at.log(lib, 'was preloaded.');
					}
				});
			}
			this.opts = opts;
			if (opts.utcNow)
			{
				var utcNow = new Date(opts.utcNow);
				var localNow = new Date(opts.utcNow + opts.tzOffset);
				$at.log('Now:', utcNow.toUTCString(), '(Offset:' + utcNow.getTimezoneOffset() + ')', localNow.toUTCString(), '(Offset:' + localNow.getTimezoneOffset() + ')');
			}
		},
		getSitePath: function()
		{
			return this.opts.sitePath;
		},
		mvcUrl: function(mvcPath)
		{
			return (typeof(mvcPath) === 'undefined') ? this.opts.mvcRoot : this.opts.mvcRoot + mvcPath;
		},
		boUrl: function(bo, args)
		{
			var url = this.opts.sitePath + this.opts.boWrapper + '?bo=' + bo;
			if (typeof args === 'string')
			{
				url += '&' + args;
			}
			else
			{
				$.each(args, function(key, val){
					url += '&' + key + '=' + encodeURIComponent(val);
				});
			}
			return url;
		},
		setCulture: function(options)
		{
			var culture = this.culture = new Culture(options);
			window.EnsureNumeric = function(e)
			{
				return culture.isNumericKey(getKeyCode(e));
			};
			window.DecimalToNumber = function(n, p, hf)
			{
			};
		},
		getCultureName: function()
		{
			return this.opts.culture || this.opts.language || 'en-US';
		},
		getJqCalCultureName: function()
		{
			return this.opts.jqCalCulture;
		},
		getLanguage: function()
		{
			return this.opts.language || 'en';
		},
		ready: function(id, fn, opts)
		{
			if (!this.ready.done)
				this.ready.done = {};
			if (!this.ready.done[id])
			{
				var me = this;
				$(function() { fn.apply(me); });
				this.ready.done[id] = true;
			}
		}
	});

	$at.page = new Page();

	var Library = $at.Class.extend('Library',
	{
		constructor: function(options)
		{
		}
	});

	var cssQ = [];

	var loadNextCss = function()
	{
		if (cssQ.length == 0)
			return;
		var lib = cssQ.shift();
		var node = document.createElement('link');
		node.type = 'text/css';
		node.rel = 'stylesheet';
		node.href = lib;
		node.onreadystatechange = function()
		{
			var readyState = this.readyState;
			if (readyState === 'loaded' || readyState === 'complete')
			{
				this.onreadystatechange = null;
				$at.require.loadedLibs[this.href] = 'ready';
				loadNextCss();
			}
		};
		document.getElementsByTagName('head')[0].appendChild(node);
	};

	var cssPath = function(lib)
	{
		if (lib.indexOf('../') == 0)
			return $at.page.getSitePath() + lib.substr(3);
		else if (lib.indexOf('/') >= 0)
			return $at.page.getSitePath() + 'portal/' + lib;
		else
			return $at.page.getSitePath() + 'stylesheets/' + lib;
	};

	$at.require = function(jsLibs, options)
	{
		if (!$at.require.loadedLibs) $at.require.loadedLibs = {};
		if (typeof jsLibs === 'string')
		{
			if (jsLibs == 'atGadgets')
				jsLibs = ['gadgets/files/container/atGadgets.js'];
			else
				jsLibs = new Array(jsLibs);
		}
		var opts = $.extend({
			callback: function(){},
			cache: true
		}, options);
		$.each(jsLibs, function()
		{
			var lib = this;
			var css = false;
			if (lib.match(/\.css$/))
			{
				css = true;
				lib = cssPath(lib);
			}
			else
			{
				if (lib.indexOf('../') == 0)
					lib = $at.page.getSitePath() + lib.substr(3);
				else if (lib.indexOf('/') != 0)
					lib = $at.page.getSitePath() + 'portal/' + lib;
				if (!lib.match(/\.js$/))
					lib += '.js';
			}
			var indent = '          '.substr(0, ($at.require.depth++ || ($at.require.depth = 1, 0)) * 2);
			if (!$at.require.loadedLibs[lib])
			{
				if (css)
				{
					$at.log(indent + 'Loading stylesheet ' + lib + '...');
					$($.support.htmlSerialize ?
						function()
						{
							$('<link/>')
								.attr(
								{
									type: 'text/css',
									rel: 'stylesheet',
									href: lib
								})
								.appendTo('head')
								.load(function()
								{
									$at.require.loadedLibs[lib] = 'ready';
								});
						} :
						function()
						{
							cssQ.push(lib);
							loadNextCss();
						}
					);
				}
				else
				{
					$at.log(indent + 'Loading JS library ' + lib + '...');
					$.ajax(
					{
						type: 'GET',
						url: lib,
						complete: function(req, status)
						{
							$at.log(indent + lib + ': ' + status);
						},
						success: opts.callback,
						error: function(req, msg, e)
						{
							$at.log(indent + lib + ': ' + msg);
						},
						dataType: 'script',
						cache: opts.cache,
						async: false
					});
				}
				$at.require.loadedLibs[lib] = 'require';
			}
			else
				$at.log(indent + lib + ' is already loaded (' + $at.require.loadedLibs[lib] + ').');
			$at.require.depth--;
		});
	};

	var probeStyle = function(options)
	{
		if ($at.require.loadedLibs[options.path] == 'ready' || options.prober.css(options.key) == options.value)
		{
			options.prober.remove();
			options.callback(jQuery);
			return;
		}
		$at.log(options.path + ': #' + options.prober[0].id + ' style not found, queueing timer.');
		window.setTimeout(function()
		{
			probeStyle(options);
		}, 60);
	};

	$at.require.style = function(cssLib, prober, options)
	{
		probeStyle($.extend(
		{
			path: cssPath(cssLib),
			callback: function(){},
			prober: (prober.jquery ? prober : $('<div id="' + prober + '"></div>')).appendTo('body'),
			key: 'display',
			value: 'none'
		},
		options));
	};


	$at.datetime =
	{
		toUserDate: function(clientDate)
		{
			if (!clientDate)
				clientDate = new Date();
			if (!$at.page.opts.utcNow)
			{
				return clientDate;
			}
			return new Date(clientDate.getTime() + clientDate.getTimezoneOffset() * 60000 + $at.page.opts.tzOffset);
		}
	};

	$at.date = function()
	{
		$at.require([
			'jquery.calendars/smoothness.calendars.picker.css',
			'jquery.calendars/jquery.calendars.js',
			'jquery.calendars/jquery.calendars.plus.js',
			'jquery.calendars/jquery.calendars.picker.js'
		]);
		var calendarType = 'gregorian';
		var jqCalCulture = $at.page.getJqCalCultureName();
		if (!jqCalCulture)
		{
			// Fallback if jqCalCulture not available
			// (Edit list manually to add custom culture settings.)
			var availableCultures = ['af', 'ar', 'az', 'bg', 'bs', 'ca', 'cs',
				'da', 'de', 'de-CH', 'el', 'en-GB', 'eo', 'es', 'es-AR', 'et', 'eu',
				'fa', 'fi', 'fo', 'fr', 'fr-CH', 'gl', 'gu', 'he', 'hr', 'hu', 'hy',
				'id', 'is', 'it', 'ja', 'ko', 'lt', 'lv', 'ms', 'nl', 'nl-BE', 'no',
				'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sr-SR', 'sv',
				'ta', 'th', 'tr', 'uk', 'ur', 'vi', 'zh-CN', 'zh-HK', 'zh-TW'];
			jqCalCulture = $at.page.getCultureName();
			if ($.inArray(jqCalCulture, availableCultures) < 0)
			{
				var p = jqCalCulture.indexOf('-');
				if (p > 0)
				{
					jqCalCulture = jqCalCulture.substr(0, p);
				}
				if ($.inArray(jqCalCulture, availableCultures) < 0)
				{
					jqCalCulture = 'en';
				}
			}
		}
		if (jqCalCulture != 'en')
		{
			$at.require([
				'jquery.calendars/jquery.calendars-' + jqCalCulture + '.js',
				'jquery.calendars/jquery.calendars.picker-' + jqCalCulture + '.js'
			]);
		}
		$at.date.cal = $.calendars.instance(calendarType, jqCalCulture);
		$('input.atDate').calendarsPicker($.extend(
				{
					calendar: $at.date.cal,
					renderer: $.extend({}, $.calendars.picker.defaultRenderer, 
									{ picker: $.calendars.picker.defaultRenderer.picker.replace(/\{link:clear\}/, '') }),
					selectDefaultDate: true
				},
				$.calendars.picker.regional[jqCalCulture]
			)
		);
	};


	/*
		Reads the value of a calendar picker field and an optional time entry field and returns a JS Date object.
	*/
	$at.date.getDate = function(calPickerField, timeField)
	{
		var d = calPickerField.calendarsPicker('getDate');
		if ($.isArray(d) && d.length == 1)
		{
			d = d[0].toJSDate();
		}
		else
		{
			d = new Date(); // no date selected
		}
		var date = new Date(d);
		if (timeField)
		{
			var t = timeField.timeEntry('getTime');
			date.setHours(t.getHours());
			date.setMinutes(t.getMinutes());
		}
		return date;
	};

	/*
		Changes the value of a calendar picker field.
	*/
	$at.date.setDate = function(calPickerField, jsDate)
	{
		calPickerField.calendarsPicker('setDate', $at.date.cal.fromJSDate(jsDate));
	};

	/*
		Changes the value of a time entry field.
	*/
	$at.date.setTime = function(timeField, time)
	{
		timeField.timeEntry('setTime', time);
	};

	/*
		Formats a date.
	*/
	$at.date.formatDate = function(jsDate, format)
	{
		var cd = $at.date.cal.fromJSDate(jsDate);
		return cd.formatDate(format);
	};


	/*
		Compares two dates.
	*/
	$at.date.compare = function(calPicker1, calPicker2)
	{
		var d1 = $at.date.getDate(calPicker1).getTime();
		var d2 = $at.date.getDate(calPicker2).getTime();
		if (d1 == d2)
			return 0;
		if (d1 < d2)
			return -1;
		return 1;
	};


	$at.time = function(options)
	{
		$at.require([
			'jquery.timeentry/jquery.timeentry.css',
			'jquery.timeentry/jquery.timeentry.js'
		]);
		var language = $at.page.getLanguage();
		if (language != 'en')
		{
			$at.require([
				'jquery.timeentry/jquery.timeentry-' + language + '.js'
			]);
		}
		$('input.atTime').timeEntry($.extend( 
				{
					timeSteps: [1, 5, 0],
					spinnerImage: $at.page.getSitePath() + 'portal/jquery.timeentry/spinnerDefault.png'
				},
				options
			)
		);
	};

	$at.time.formatTime = function(jsDate)
	{
		if (!$.timeEntry)
			return jsDate.toLocaleTimeString();
		var ted = $.timeEntry._defaults;
		var h24 = jsDate.getHours();
		var h = ted.show24Hours ? h24 : h24 % 12;
		var m = jsDate.getMinutes();
		var s = (h > 9 ? h : '0' + h) + ted.separator + (m > 9 ? m : '0' + m);
		if (!ted.show24Hours)
			s += ted.ampmNames[(h < 12) ? 0 : 1];
		return s;
	}

	$at.util =
	{
		// Adjusts the height of an IFRAME to its content.

		adjustIframeHeight: function(iframe)
		{
			var newHeight;

			// Get the height of the viewport inside the IFRAME
			var cw = iframe.contentWindow;
			var vh;
			if (cw.innerHeight)
			{
				// all except IE
				vh = cw.innerHeight;
			}
			else if (cw.document.documentElement && cw.document.documentElement.clientHeight)
			{
				// IE6 Strict Mode
				vh = cw.document.documentElement.clientHeight;
			}
			else if (document.body)
			{
				// IE otherwise
				vh = cw.document.body.clientHeight;
			}
			else
			{
				vh = 0;
			}

			var body = cw.document.body;
			var docEl = cw.document.documentElement;

			if (document.compatMode == 'CSS1Compat' && docEl.scrollHeight)
			{
				// In Strict mode:
				// Use docEl.scrollHeight or body.scrollHeight, whichever of these is NOT equal to the IFRAME viewport height:
				newHeight = ($.browser.msie) ? body.scrollHeight :
						(docEl.scrollHeight != vh ?
							 docEl.scrollHeight : body.scrollHeight);

				var maxHeight = $(window).height() - 150;
				if (newHeight > maxHeight)
					newHeight = maxHeight;
			}
			else
			{
				// In Quirks mode:
				var sh = docEl.scrollHeight;
				var oh = docEl.offsetHeight;
				if (docEl.clientHeight != oh)
				{
					sh = body.scrollHeight;
					oh = body.offsetHeight;
				}
				// Detect whether the inner content height is bigger or smaller
				// than the bounding box (viewport).
				if (sh > vh)
				{
					// Content is larger
					newHeight = sh > oh ? sh : oh;
				} else
				{
					// Content is smaller
					newHeight = sh < oh ? sh : oh;
				}
			}
			newHeight ++;

			iframe.style.height = newHeight + "px";
			body.style.margin = "0";
			body.style.border = "none";
			body.style.backgroundColor = "transparent";
		}
	};


	/*
		Logging.
	*/
	$at.log = (typeof window.console === 'object' && window.console.log && $.isFunction(window.console.log)) ?
		function()
		{
			window.console.log.apply(window.console, arguments);
		} :
		function(msg){};

	if (false)
	{
		$at.require('jquery.profile');
		$(function()
		{
			$(window).load(function()
			{
				$.displayProfile();
			});
		});
	}

})(jQuery);

