/**
 * @author Martin Kleppe <kleppe@ubilabs.net>
 * @copyright 2007 by Navigon
 *
 * @fileOverview
 * This script file handles dynamic modules and applications. 
 * It must be included below "prototype.js"!
 *     <script type="text/javascript" src="js/lib/prototype.js"></script>
 *     <script type="text/javascript" src="js/Core.js"></script>
 * </pre>
 */

Object.extend(Class, {
	_basePath: "",

	/**
	 * Append one or more classes to the document.
	 * @example Class.include("skobbler.map.Map", "skobbler.Settings");
	 * @param {String} ...	Path to classes eg: "skobbler.map.GeoCoder"
	 */
	include: function(classPaths){
		if (typeof Class.includedScripts == "undefined"){
			Class.includedScripts = [];
		}

		$A(arguments).flatten().each(function(classPath){
			if (typeof classPath == "string"){
				classPath = classPath.replace("*", "_package");
				if (!Class.includedScripts.include(classPath)){
					Class.load(Class._basePath + classPath.replace(/\./g, "/") + '.js');
					Class.includedScripts.push(classPath);
				}
			}
		});
	},

	/**
	 * Dynamically loads a script file.
	 * @param {String} script	Path to script eg: "script/skobbler/map/GeoCoder.js"
	 */
	load: function(script){
        var today = new Date();
        var first = new Date(today.getFullYear(), 0, 1);
        var theDay = Math.round(((today - first) / 1000 / 60 / 60 / 24) + .5, 0);
        var rnd = (theDay<2)?0:parseInt(theDay/2);

		if (Class._domLoaded){
			$$("head").first().insert(new Element("script", {type: "text/javascript", src: script}));
		} else {
			document.write('<script src="' + script +'?rnd=' + rnd + '" type="text/javascript"><\/script>'); //+ '?rnd='+parseInt(Math.random()*1000000)
		}
	},

	alias: function(alias, klass){
		if (typeof window[alias] != "undefined" && window[alias] != klass){
			throw new Error('Alias "' + alias + '" was already assigned to another class.');
		}
		window[alias] = klass;
	},

	/**
	 * Prepares a foreign class to be inherited via Prototype.
	 * @param {Object} klass
	 */
	prepare: function(klass){
		klass.subclasses = klass.subclasses || [];
		klass.prototype.initialize = klass.prototype.initialize || klass;
	}
});

var Package = {
	/**
	 * Provides a package namespace.
	 *
	 * @example Package.create("for.bar.events");
	 * @param {String} namespace	Namespace eg "for.bar.events".
	 * @return {Object} Returns the base package.
	 */
	create: function(namespace){
		var parts = namespace.split(".");
		var basePackage = window;
		for (var i=0; i<parts.length; i++){
			var part = parts[i];
			if (typeof basePackage[part] == "undefined"){
				basePackage[part] = {};
			}
			basePackage = basePackage[part];
		}

		return basePackage;
	},
	/**
	 * Incluses a list of classes.
	 * @param {Array} classes Classes to include.
	 */
	include: function(classes){
		$A(arguments).each(Class.include);
	}
};

/**
 * Page module constructed via it's class literal.
 *
 * @constructor
 * @example new Module("skobbler.map.Map", {container: "map1"});
 * @param {String} klass		Class literal.
 * @param {Object} [options]	Options to pass to constructor.
 * @param {Function} [callback]	Callback when module is initialized.
 */
var Module = Class.create({
	initialize: function(klass, options, callback){
		this.args = $A(arguments).slice(1);
		this.callback = callback;
		this.options = options;
		this.klass = klass;
		Class.include(klass);
		Module._modules.push(this);
	}
});
Module._modules = $A();

/** 
 * Initialize all modules on DOM load.
 */
Module._initialize = function(){
	Module._modules.each(function(module){
		eval("var Klass = " + module.klass);
		var instance = new Klass(module.options);
		if (module.callback){
			module.callback.apply(instance);
		}
	});
};

/**
 * Load and run an application class. The application will be global
 * accessible as "application".
 *
 * @constructor
 * @example new Application("foo.bar.app.MyApp");
 * @param {String} klass		Class reference as string pattern.
 * @param {Function} [callback]	Callback method to run when app is initialized.
 */
var Application = Class.create({
	initialize: function(klass, callback){
		new Module(klass, {}, function(){
			window.application = this;
			if (callback){
				callback.apply(this);
			}
		});
	}
});

Package.create("Script");
/**
 * Loads an external script file.
 *
 * @param {String} url		URL pointing to the file.
 * @param {Object} [params]	Parameters to append as GET key/value pairs.
 */
Script.load = function(url, params){
 	if (params) {
		url += url.include('?') ? '&' : '?';
		url += Object.toQueryString(params);
	}
	Class.load(url);
};


Package.create("Shortcut");

/**
 * Makes a nested object global accessible.
 *
 * @param {String} shortcut	Name of global shortcut.
 * @param {Object} object	Object reference.
 *
 * @example
 * my.deeply = {nested: {value: "VALUE"}};
 * Shortcut.create("VALUE", my.deeply.nested.value);
 * alert(VALUE); // VALUE
 */
Shortcut.create = function(shortcut, object){
	window[shortcut] = object;
};

/**
 * Creates global accessible references to complex object properties.
 *
 * @param {String} prefix	Prefix to put first.
 * @param {Object} object	Object to parse.
 * @example
 * org.foo.bar.Messages = {YES: "You got it!"};
 * Shortcut.prefix("M_", org.foo.bar.Messages);
 * alert(M_YES); // You got it!
 */
Shortcut.prefix = function(prefix, object){
	var old = Shortcut.prefixes.get(prefix);
	if (old){
		console.error("Shortcut ", prefix, " all ready assigned to ", old);
	} else {
		Shortcut.prefixes.set(prefix, object);
		$H(object).each(function(pair){
			if (!window[prefix + pair.key]) {
				window[prefix + pair.key] = pair.value;
			}
		});
	}
};
Shortcut.prefixes = $H();

/**
 * A DOM node reference using a object hash of simple selectors. 
 * As soon as the document is loaded the targets will be available.
 *
 * @constructor
 * @param {Object} targets	Key / value pairs.
 * @example
 * var targets = new Container({
 *   info: "#head .info",
 *   details: "#details p div"
 * });
 */
var Container = Class.create({
	initialize: function(targets){
		Container._containers.push({
			object: this, targets: $H(targets)
		});
	}
});

Container._containers = $A();

/**
 * Initialize all containers on DOM load.
 */
Container._initialize = function(){
	Container._containers.each(function(container){
		container.targets.each(function(pair){
			container.object[pair.key] = $$(pair.value).first();
		});
	});
};

/**
 * Template based on an available DOM node. This will wait until the document
 * is loaded and parse node.innerHTML.
 *
 * @constructor
 * @extends Template
 * @param {String} selector The DOM node selector. eg "#head .template"
 * @param {Object} selector	Object which properties will be assigned.
 *
 * @example
 * var template = new DomTemplate({
 *   info: "#info .inner",
 *   details: "#details div"
 * });
 *
 * var template = new DomTemplate("#head .template");
 */

var DomTemplate = Class.create(Template, {
	initialize: function(selector){
		DomTemplate._templates.push({
			object: this, selector: selector
		});
	    this.pattern = Template.Pattern;
	}
});

DomTemplate._templates = $A();

/**
 * Initialize all DOM templates on page load.
 */
DomTemplate._initialize = function(){
	DomTemplate._templates.each(function(target){
		if (typeof target.selector == "string") {
			target.object.template = $$(target.selector).first().innerHTML;
		} else {
			$H(target.selector).each(function(pair){
				target.object[pair.key] = new Template($$(pair.value).first().innerHTML);
			});
		}
	});
};

/**
 * Event dispatcher mixin methods. Provides #observe, #dispatch, #pass,
 * and #stopObserving.
 *
 * @constructor
 * @alias Event.Dispatcher
 * @example var Klass = Class.create(Event.Dispatcher, {...});
 */
Event.Dispatcher = {
	/**
	 * Registers an event handler on a DOM element.
	 * @param {String} event
	 * @param {Function} observer
	 * @return {Object} Target object.
	 */
	observe: function(event, observer){
		this._observers = this._observers || $H();
		var observers = this._observers.get(event) || this._observers.set(event,  $A());
		observers.push(observer);
		return this;
	},

	/**
	 * Unregisters an event handler.
	 * @param {String} event
	 * @param {Object} observer
	 * @return {Object} Target object.
	 */
	stopObserving: function(event, observer){
		this._observers.set(event,
			observer ? this._observers.get(event).without(observer) : $A()
		);
		return this;
	},

	/**
	 * Dispatches an event. 
	 * @param {String} event
	 * @return {Object} Target object.
	 */
	dispatch: function(event){
		var observers = (this._observers && this._observers.get(event)) || $A();
		if (observers){
			var args = $A(arguments).slice(1);
			observers.each(function(observer){
				observer.apply(this, args);
			});
		}
		return this;
	},

	/**
	 * Passes an event.
	 * @param {String} event
	 * @param {Object} target
	 * @return {Object} Target object.
	 */
	pass: function(event, target){
		this.observe(event, function(){
			target.dispatch(event);
		});
		return this;
	}
};

/**
 * Create a random integer between 0 and Number.
 * @return {Integer}	Random integer.
 */
Number.prototype.random = function(){return (Math.random()*this).floor();};

/**
 * Returns a random item from this array.
 * @return {Object} Random item.
 */
Array.prototype.random = function(){return this[this.length.random()];};
/**
 * Fix float point errors to precision.
 * @param {Number} [precision]	Number of decimal places to fix. Default to 6.
 * @return {Number} Fixed number.
 */
Number.prototype.fixTo = function(precision){
	var correction = Math.pow(10, precision || 6);
	return (this * correction).round() / correction;
};

/**
 * Rounds a number ba a specific factor.
 * @param {Number} factor	Factor to round to
 * @example (2.2317).roundBy(0.25); // 2.25
 * @name Number.roundBy
 * @function
 */
$w("round floor ceil").each(function(method){
	Number.prototype[method + "By"] = function(factor){
		return (this / factor)[method]() * factor;
	};
});

Element.addMethods({
	/* Removed since updated to latest Prototype
	click: function(element, callback, args){
		return element.observe("click", function(event){
			event.stop();
			(element.blur && element.blur());
			callback($A(arguments).slice(2));
		});
	},
	*/
	doHighlight: function(element){
                   
            var cn = element.className;
            cns = new Array();
            if(cn != '') {
                cns = cn.split(" ");
            }
                 
            element.addClassName('active');
            if(cns.length>1) {
                for(var i=0; i<cns.length; i++) {
                    element.addClassName(cns[i]);
                }
            }
             //return element.addClassName('active');

	}, 
	doDownlight: function(element){
            return element.removeClassName('active');
	}
});

// Attatch document events and initialize scripts and classes;
document.observe("dom:loaded", function(){
	Class._domLoaded = true;
	DomTemplate._initialize();
	Container._initialize();
	Module._initialize();
});

Event.observe(window, "unload", function(){
	if (typeof GUnload == "function") {
		GUnload();
	}
});

Object.extend(Position, {
	fix: function(element, offset){
		offset = Object.extend({top: 0}, offset || {});
		var top = element.cumulativeOffset().top;
		Event.observe(window, "scroll", function(){
			var scroll = document.viewport.getScrollOffsets().top;
			element.style.top = Math.max(0, (scroll - top) + offset.top) + "px";
		}.bind(this));
	}
});
(function(){
	var script = $$("script").findAll(function(script){
		return (script.src && script.src.match(/Core\.js(\?.*)?$/));
	})[0];
	if (script && script.src){
		Class._basePath = script.src.replace(/Core\.js(\?.*)?$/,'');
		var includes = script.src.match(/\?.*include=([a-zA-Z0-9,\.]*)/);
		if (includes){
			Class.include(includes[1].split(","));
		}
	}
})();