/*!
 * WebUI Core (C) digitop.de
 * $LastChangedRevision: 863 $
 * $LastChangedDate: 2010-11-24 18:43:27 +0100 (Mi, 24 Nov 2010) $
 */

/**
 * The base library takes care of setup, logging, and events.
 * @module Library
 **/

/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false, plusplus:false */
/*global DT: true, jQuery: false, window: false */

DT = window.DT || {};

DT.scriptPath = DT.scriptPath || 'templates/DT_Scripts/Library/';
DT.cssPath = DT.cssPath || 'templates/CSS/';

/**
* Contains all constants.
* @class CONSTANTS
* @static
**/
DT.CONSTANTS = {
	/*
		LOGGING
	*/
	/**
	* Turns logging off.
	* @property LOGGING_LEVEL_OFF
	* @type string
	* @final
	**/
	LOGGING_LEVEL_OFF: 0,
	/**
	* All messages should be logged.
	* @property LOGGING_LEVEL_DEBUG
	* @type string
	* @final
	**/
	LOGGING_LEVEL_DEBUG: 1,
	/**
	* Only warnings and critical messages should be logged
	* @property LOGGING_LEVEL_WARN
	* @type string
	* @final
	**/
	LOGGING_LEVEL_WARN: 2,
	/**
	* Only errors should be logged.
	* @property LOGGING_LEVEL_ERROR
	* @type string
	* @final
	**/
	LOGGING_LEVEL_ERROR: 3,
	/**
	* Contains the names of the logging levels.
	* @property LOGGING_LEVEL_LABELS
	* @type array
	* @final
	**/
	LOGGING_LEVEL_LABELS: ['OFF', 'DEBUG', 'WARN', 'ERROR'],

	/*
		EVENTS
		TODO: finalize events
	*/
	/**
	* Layout changed event.
	* @property EVENT_LAYOUT_CHANGED
	* @type string
	* @final
	**/
	EVENT_LAYOUT_CHANGED: 'layout changed',
	/**
	* Window resized event (replication).
	* @property EVENT_WINDOW_RESIZED
	* @type string
	* @final
	**/
	EVENT_WINDOW_RESIZED: 'window resized',
	/**
	* Window scrolled event (replication).
	* @property EVENT_WINDOW_SCROLLED
	* @type string
	* @final
	**/
	EVENT_WINDOW_SCROLLED: 'window scrolled',
	/**
	* Initialization complete event.
	* @property EVENT_INIT_COMPLETE
	* @type string
	* @final
	**/
	EVENT_INIT_COMPLETE: 'initialization complete',
	/**
	* Setup complete event.
	* @property EVENT_SETUP_COMPLETE
	* @type string
	* @final
	**/
	EVENT_SETUP_TEARDOWN: 'setup complete',
	/**
	* Partial refresh (AJAX) event.
	* @property EVENT_PARTIAL_REFRESH
	* @type string
	* @final
	**/
	EVENT_PARTIAL_REFRESH: 'partial refresh'
};

/**
* The core takes care of starting and stopping registered modules. It also gatheres statistics
* of the initialization phase of each module.
* @class CORE
* @static
**/
DT.CORE = (function ($) {
	var modules, ioMaker, eventTargets, io, externalIo, stats, version, loggingLevel = (DT.OPTIONS && DT.OPTIONS.CORE && DT.OPTIONS.CORE.LOGGING_LEVEL) || DT.CONSTANTS.LOGGING_LEVEL_OFF, loadOptions;
	version = '1.0 $LastChangedDate: 2010-11-24 18:43:27 +0100 (Mi, 24 Nov 2010) $ $LastChangedRevision: 863 $';

	loggingLevel = DT.CONSTANTS.LOGGING_LEVEL_DEBUG;
	eventTargets = {};
	modules = {};
	stats = [];

	/**
	 * Augments existing options with values from JSON data (which is stored in
	 * hidden input fields), if the option contains a <code>dataSelector</code>
	 * attribute.
	 * @private
	 **/
	loadOptions = function () {
		var mod, i, initOptions, dataOptions, mergedOptions;
		if (DT.OPTIONS) {
			for (mod in DT.OPTIONS) {
				if (DT.OPTIONS.hasOwnProperty(mod)) {
					for (i = DT.OPTIONS[mod].length; i--;) {
						initOptions = DT.OPTIONS[mod][i];
						if (initOptions.dataSelector) {
							dataOptions = $.parseJSON($(initOptions.dataSelector).attr('value'));
							// deep-copy dataSelector options to regular options
							mergedOptions = {};
							$.extend(true, mergedOptions, initOptions, dataOptions);
							DT.OPTIONS[mod][i] = mergedOptions;
						}
					}
				}
			}
		}
	};
	/**
	 * Creates an IO object.
	 * @param moduleName {String} the name of the module which will obtain this IO object
	 * @return {IO} the created IO object
	 * @private
	 **/
	ioMaker = function (moduleName) {
		var io, logNames, i;
		logNames = ['Debug', 'Warn', 'Error'];
		/**
		* Takes care of event communication and logging. Also gives registered modules a way to obtain the name they were registered with.
		* @class IO
		* @for CORE
		**/
		io = {
			/**
			 * Fires an event.
			 * @param event {string} the name of the event.
			 * @param data {object} additional <i>optional</i> data.
			 **/
			notify: function (event, data) {
				var list, i, note;
				note = {
					type: event,
					data: data,
					triggeredBy: moduleName
				};
				this.logDebug('Notification "' + note.type + '" from "' + moduleName + '"', note);
				list = eventTargets[note.type];
				if (list) {
					for (i = list.length; i--;) {
						list[i].handler.call(list[i].context, note);
					}
				}
			},
			/**
			 * Registers an event listener.
			 * @param listenTo {string} the event name, use an array of strings for multiple events.
			 * @param handler {function} the callback.
			 * @param context {object} the execution context (this).
			 **/
			listen: function (listenTo, handler, context) {
				var i, e;
				if (typeof listenTo === 'string') {
					listenTo = [listenTo];
				}
				for (i = listenTo.length; i--;) {
					e = listenTo[i];
					this.logDebug(moduleName + ' listens to "' + e + '"');
					if (!eventTargets[e]) {
						eventTargets[e] = [];
					}
					eventTargets[e].push({
						handler: handler,
						context: context
					});
				}
			},
			/**
			 * Writes a message to the console if the logging level is high enough.
			 * @param level {int} the logging level
			 * @param msg {string} the message to log
			 **/
			log: function (level, msg) {
				if (!window.console) {
					return;
				}
				var func, args;
				switch (level) {
				case DT.CONSTANTS.LOGGING_LEVEL_DEBUG:
					func = window.console.log;
					break;
				case DT.CONSTANTS.LOGGING_LEVEL_WARN:
					func = window.console.warn;
					break;
				case DT.CONSTANTS.LOGGING_LEVEL_ERROR:
					func = window.console.error;
					break;
				}
				if (!func) {
					func = window.console.log;
				}
				args = Array.prototype.slice.call(arguments);
				args.shift(); // -level
				args.unshift('[' + moduleName + ']: ');
				if (loggingLevel && level >= loggingLevel) {
					args.unshift('[' + DT.CONSTANTS.LOGGING_LEVEL_LABELS[level] + ']');
					try {
						func.apply(this, args);
					} catch (e) {
						//func(args.join(''));
						window.console.log(args.join(''));
					}
				}
			},
			/**
			 * Returns the name of the module which obtained this IO object.
			 **/
			whoAmI: function () {
				return moduleName;
			},
			/**
			 * Serializes and writes the options (back) to the hidden input field
			 * specified in this attribute's <code>dataSelector</code> property.
			 * It dynamically loads the json2 library if necessary.
			 * @param option {object} the option object to save
			 **/
			saveOption: function (option) {
				var save = function () {
					$(option.dataSelector).attr('value', JSON.stringify(option));
				};
				if (!option.dataSelector) {
					this.logError('tried to save without having a data selector');
				} else {
					if (window.JSON) {
						save();
					} else {
						$.getScript(DT.scriptPath + '/json2.min.js', save);
					}
				}
			}
		};
		for (i = logNames.length; i--;) {
			io['log' + logNames[i]] = (function () {
				var loggingLevel = i + 1;
				return function () {
					var args = Array.prototype.slice.call(arguments);
					args.unshift(loggingLevel);
					this.log.apply(this, args);
				};
			}());
		}
		return io;
	};
	io = ioMaker('DT.CORE');
	externalIo = ioMaker('DT.CORE (external)');
	return {
		/**
		 * Registers a module.
		 * @param moduleId {string} the name of the module
		 * @param maker {function} the function which creates an instance
		 * @for CORE
		 **/
		register: function (moduleId, maker) {
			io.logDebug('register: "' + moduleId + '"');
			if (modules[moduleId] !== undefined) {
				io.logWarn('WARNING: redeclaration of "' + moduleId + '"');
			}
			modules[moduleId] = {
				maker: maker,
				instance: null
			};
		},
		/**
		 * <p>Starts one specific registered module.</p>
		 *
		 * <p>If an <tt>init</tt> function exists it's called first. If also an options array for this module exists
		 * it's handed over to this <tt>init</tt> function. If no options array exists <tt>init</tt> is called with
		 * an empty array.</p>
		 *
		 * <p>If an <tt>initEach</tt> function exists it's called after <tt>init</tt>. The <tt>initEach</tt> function
		 * is called for every option in this module's option array (in reverse order).</p>
		 *
		 * @param moduleId {string} the name of the module
		 * @for CORE
		 **/
		start: function (moduleId) {
			var start, module, i, optionsArray;

			module = modules[moduleId];
			module.instance = module.maker(ioMaker(moduleId), $);

			start = (new Date()).getTime();

			if (module.instance.init) {
				module.instance.init((DT.OPTIONS && DT.OPTIONS[moduleId]) || []);
			}

			if (module.instance.initEach && DT.OPTIONS && DT.OPTIONS[moduleId]) {
				optionsArray = DT.OPTIONS[moduleId];
				for (i = optionsArray.length; i--;) {
					module.instance.initEach(optionsArray[i]);
				}
			}

			stats.push({
				name: moduleId,
				time: (new Date()).getTime() - start
			});
		},
		/**
		 * Stops one specific registered module by calling its (optional) <tt>destroy</tt> function.
		 * @param moduleId {string} the name of the module
		 * @for CORE
		 **/
		stop: function (moduleId) {
			var module = modules[moduleId];
			if (module.instance) {
				if (module.instance.destroy) {
					module.instance.destroy();
				}
				module.instance = null;
			}
		},
		/**
		 * Starts all registered modules by invoking <tt>start</tt> for every registered module.
		 * @for CORE
		 **/
		startAll: function () {
			var moduleId, start, setupStart;

			loadOptions();

			setupStart = start = (new Date()).getTime();
			for (moduleId in modules) {
				if (modules.hasOwnProperty(moduleId)) {
					this.start(moduleId);
				}
			}
			stats.push({
				name: 'DT.CORE.startAll',
				time: (new Date()).getTime() - start
			});
			start = (new Date()).getTime();
			io.notify(DT.CONSTANTS.EVENT_INIT_COMPLETE);
			stats.push({
				name: 'INIT_COMPLETE event handling',
				time: (new Date()).getTime() - start
			});
			stats.push({
				name: 'SETUP',
				time: (new Date()).getTime() - setupStart
			});
			io.notify(DT.CONSTANTS.EVENT_SETUP_TEARDOWN);

			// overwrites DT.init with a function which initializes the module directly.
			DT.init = function (moduleId, initOptions) {
				io.logDebug('re-attached init() for partial refreshs was called with:', moduleId, initOptions);
				var module, dataOptions, mergedOptions = {};
				module = modules[moduleId];
				module.instance = module.maker(ioMaker(moduleId), $);

				dataOptions = $.parseJSON($(initOptions.dataSelector).attr('value'));
				// deep-copy dataSelector options to regular options
				$.extend(true, mergedOptions, initOptions, dataOptions);

				if (module.instance.init) {
					module.instance.init([mergedOptions]);
				}

				if (module.instance.initEach) {
					module.instance.initEach(mergedOptions);
				}
			};
		},
		/**
		 * Stops all registered modules by invoking <tt>stop</tt> for every registered module.
		 * @for CORE
		 **/
		stopAll: function () {
			var moduleId;
			for (moduleId in modules) {
				if (modules.hasOwnProperty(moduleId)) {
					this.stop(moduleId);
				}
			}
		},
		/**
		* Fires an event from external sources. If the event is a partial refresh and if the selector and
		* moduleId is provided and if this module also has options with the same selector, the module in
		* question will be reinitialized.
		* @param event {string} the name of the event.
		* @param data {object} additional <i>optional</i> data.
		* @for CORE
		**/
		fire: function (event, data) {
			if (event === DT.CONSTANTS.EVENT_PARTIAL_REFRESH) {
				if (data && data.selector && data.moduleId &&
						DT.OPTIONS && DT.OPTIONS[data.moduleId]) {
					var module, optionsArray, option, i;

					optionsArray = DT.OPTIONS[data.moduleId];
					for (i = optionsArray.length; i--;) {
						if (optionsArray[i].selector === data.selector) {
							option = optionsArray[i];
							module = modules[data.moduleId];
							module.instance = module.maker(ioMaker(data.moduleId), $);

							if (module.instance.init) {
								module.instance.init([option]);
							}

							if (module.instance.initEach) {
								module.instance.initEach(option);
							}
							break;
						}
					}
				}
			}
		    externalIo.notify(event, data);
		},
		/**
		 * Contains the statistics of the init phase.
		 * @property stats
		 * @type array
		 * @for CORE
		 **/
		stats: stats,
		/**
		 * The version number of the core.
		 * @property version
		 * @type string
		 * @final
		 * @for CORE
		 **/
		version: version
	};
}(jQuery));

/**
* Contains a few miscellaneous helper functions.
* @class TOOLKIT
* @static
**/
DT.TOOLKIT = (function ($) {
	return {
		/**
		 * Removes the query string from URIs (if any).
		 * @param uri {string} the URI whose query string should be removed
		 * @return {string} the query-free URI
		 **/
		removeQueryString: function (uri) {
			return uri.split('?')[0];
		},
		/**
		 * Determines if the current page is one of the specified pages.
		 * @param pages {object} either and array of strings with the names or a single string with a single name
		 * @return {boolean} whether the current page is one of the specified pages
		 **/
		anyOfThesePages: function (pages) {
			var i, loc;
			if (!(pages instanceof Array)) {
				pages = [pages];
			}
			loc = document.location.href;
			loc = loc.slice(loc.lastIndexOf('/') + 1, loc.lastIndexOf('.')).toLowerCase();
			for (i = pages.length; i--;) {
				if (pages[i].toLowerCase() === loc) {
					return true;
				}
			}
			return false;
		},
		/**
		* True if this URI contains "://axcms.".
		* @property isBE
		* @type boolean
		* @final
		**/
		isBE: document.location.href.indexOf('://axcms.') !== -1,
		/**
		* True if this URI does not contain "://axcms.".
		* @property isFE
		* @type boolean
		* @final
		**/
		isFE: document.location.href.indexOf('://axcms.') === -1,
		/**
		* True if the visitor uses IE6.
		* @property isIE6
		* @type boolean
		* @final
		**/
		isIE6: !!($.browser.msie && $.browser.version.substr(0, 1) === '6'),
		/**
		* True if the visitor uses IE7.
		* @property isIE7
		* @type boolean
		* @final
		**/
		isIE7: !!($.browser.msie && $.browser.version.substr(0, 1) === '7'),
		/**
		* The IE version number, <code>undefined</code> if IE isn't used.
		* @property IE
		* @type int
		* @final
		**/
		IE: $.browser.msie ? (+$.browser.version.substr(0, 1)) : undefined,
		/**
		 * Creates a cookie or overwrites an exisiting one.
		 * @param name {string} the name of the cookie
		 * @param value {string} the value of the cookie
		 * @param days {int} number of days until the cookie expires
		 **/
		createCookie: function (name, value, days) {
			var date, expires;
			if (days) {
				date = new Date();
				date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
				expires = '; expires=' + date.toGMTString();
			} else {
				expires = '';
			}
			document.cookie = name + '=' + value + expires + '; path=/';
		},
		/**
		 * Reads the value of a cookie.
		 * @param name {string} the name of the cookie
		 * @return {string} the value of the specified cookie or null if the cookie doesn't exist
		 **/
		readCookie: function (name) {
			var nameEQ = name + '=', ca = document.cookie.split(';'), c, i;
			for (i = 0; i < ca.length; i++) {
				c = ca[i];
				while (c.charAt(0) === ' ') {
					c = c.substring(1, c.length);
				}
				if (c.indexOf(nameEQ) === 0) {
					return c.substring(nameEQ.length, c.length);
				}
			}
			return null;
		},
		/**
		 * Deletes a cookie.
		 * @param name {string} the name of the cookie
		 **/
		deleteCookie: function (name) {
			DT.TOOLKIT.createCookie(name, '', -1);
		},
		/**
		 * Generates an id by increasing a private counter and prefixing it with "dt-auto-id-".
		 * @return {string} a new id (without #)
		 **/
		generateId: (function () {
			var counter = 0;
			return function () {
				counter++;
				return 'dt-auto-id-' + counter;
			};
		}()),
		/**
		 * Processes data for up to 50 msec, waits for 25 msec, and continues if necessary.
		 * @param items {array} the data to process
		 * @param process {function} the function which handles the processing
		 **/
		throttleExecution: function (items, process, callback) {
			var todo;
			if ($.isArray(items)) {
				todo = items.concat();
			} else {
				todo = $.makeArray(items);
			}
			setTimeout(function () {
				var start = +new Date();
				do {
					process(todo.shift());
				} while (todo.length && +new Date() - start < 50);
				if (todo.length) {
					setTimeout(arguments.callee, 25);
				} else if (callback) {
					callback();
				}
			}, 25);
		}
	};
}(jQuery));

jQuery.noConflict();
jQuery(document).ready(function ($) {
	DT.CORE.startAll();
	window.onunload = function (e) {
		DT.CORE.stopAll();
	};
});
