(function () {

	var typesByDomain = {};
	

	/* Global functions */

	window.createComponentFromDomain =
	function createComponentFromDomain(id, domain, context, data) {

		var typeObj = getTypeFromDomain(id, domain);
		if (typeObj === undefined) {
			console.warn('No component registered for "'+id+'" in "'+domain+'", falling back to "BasicObject".');
			console.trace();
			console.log("data was:", data);
			typeObj = typesByDomain["Core"]['BasicObject'];
		}

		var instance = new typeObj(data);
		
		Object.defineProperty(instance, 'context', { value: context });
		//Object.defineProperty(instance, '__domain', { value: domain });
		
		if (instance.initFromData === undefined) {
			console.warn('The type "'+id+'" is missing the standard initializer.');
			return instance;
		}

		var result = instance.initFromData(data);
		return result;
	};

	window.createComponent =
	function createComponent(UTI, context, data) {
		var pos = UTI.lastIndexOf('.'),
			domain = UTI.substr(0, pos),
			id = UTI.substr(pos+1);
		
		return createComponentFromDomain(id, domain, context, data);
	};

	window.setSupertypeFromDomain =
	function setSupertypeFromDomain(func, supertypeId, domain) {
	
		// warning: this replaces the prototype, so it must be called
		// before any other change of the prototype happens
		if (supertypeId !== null) {
			var supertype = getTypeFromDomain(supertypeId, domain);
			try {
				//console.log('creating prototype for', func);
				func.prototype = new supertype;
			}
			catch(err) {
				console.warn('Could not create component of type '+supertypeId+' and domain '+domain, typesByDomain);
				console.trace();
				throw err;
			}
			func.prototype.constructor = func;
			func.prototype.supertype = supertype;
			return supertype;
		}
	
	};

	window.registerType =
	function registerType(domain, func) {
		var domainElementList = typesByDomain[domain] ||
				(typesByDomain[domain] = {} || typesByDomain[domain]);
		domainElementList[func.name] = func;
		Object.defineProperty(func.prototype, '__domain', { value: domain });
	};

	window.getTypeFromDomain =
	function getTypeFromDomain(id, domain) {
		var domainElementList = typesByDomain[domain];
		if (!domainElementList || !domainElementList[id]) {
			console.warn("No component with the ID \""+id+"\" was registered in the domain \""+domain+"\"");
			console.trace();
			return;
		}
		return domainElementList[id];
	};

	window.getType =
	function getType(UTI) {

		var pos = UTI.lastIndexOf('.'),
			domain = UTI.substr(0, pos),
			id = UTI.substr(pos+1);

		return getTypeFromDomain(id, domain);
	};

	window.getPrototype =
	function getPrototype(UTI) {
		var t = getType(UTI);
		if (t) return t.prototype;
	};
	
	window.isKindOfType =
	function isKindOfType(obj, type) {
		return (obj instanceof type) || obj === type || (obj && obj.prototype === type);
	};
	
	window.clone =
	function clone(obj, depth) {
		if (Array.isArray(obj)) {
			if (depth > 0) {
				var copy = [];
				for (i in obj) {
					copy.push(clone(obj[i], depth-1));
				}
			}
			else return obj.slice(0);
		}
		if (null == obj || "object" != typeof obj) return obj;
		var copy = obj.constructor();
		if (!copy) {
			console.warn('COPY MISSING', obj, attr, copy);
			console.trace();
		}
		for (var attr in obj) {
			if (obj.hasOwnProperty(attr)) {
				if (depth > 0) {
					copy[attr] = clone(obj[attr], depth-1);
				}
				else copy[attr] = obj[attr];
			}
			//console.log(1);
		}
		return copy;
	};
	
	window.initApplication =
	function initApplication(contextUTI) {
	
		/* style the body */
	
		var body = document.body;
		body.style.overflow = 'hidden';
		body.style.webkitUserSelect = 'none';
		body.style.cursor = 'default';
		body.style.margin = '0px';
		body.style.position = 'fixed'; // prevent autoscrolling behaviour
		
		/* no drag and drop (would navigate away) */

		body.addEventListener('dragover', function (e) {
			e.preventDefault();
			e.stopPropagation();
			e.dataTransfer.dropEffect = 'none';
		});
		
		
		
		/* create the application context */
	
		var context = createComponent(
			contextUTI || 'Core.ApplicationContext',
			null,
			{
				usePasteboard: true,
				useUndoManager: true
			}
		);
			
		return context;
	};
	
	window.addEventListener('mousedown', function () {
		window.isMouseDown = true;
	});

	window.addEventListener('mouseup', function () {
		window.isMouseDown = false;
	});
	
	window.localizedStrings = {};
	window.LocalizedString = function LocalizedString(str) {
		return localizedStrings[str] || str;
	};
	window.LocalizedString1 = function LocalizedString1(str, p1) {
		return LocalizedString(str).replace(/%1/g, p1);
	};
	window.LocalizedString2 = function LocalizedString2(str, p1, p2) {
		return LocalizedString(str).replace(/%1/g, p1).replace(/%2/g, p2);
	};
	window.LocalizedString3 = function LocalizedString2(str, p1, p2, p3) {
		return LocalizedString(str).replace(/%1/g, p1).replace(/%2/g, p2).replace(/%3/g, p3);
	};
	
	window.TRANSLATE = function TRANSLATE(obj) {
		return obj[language];
	};
	window.LocalizedNumber = window.LocalizedString;

	window.utiFromData = function utiFromData(data) {
		if (data.UTI) return data.UTI;
		return data.$domain+'.'+data.$type;
	};

})();
