
/*
Script: Listener.js
	Utility class for catching and throwing events. originally made for use in asset loader singleton.

License:
	MIT-style license.
*/

Listener = new Class({
	Implements: Events/*,
	fireEvent: function(event, bindTo){
		parent.fireEvent(event, bindTo);
	}*/
});


/* 
Script: AssetLoader.js
	Javascript webpage boot utility

Author:
	Pat Cullen

Version:
	0.1
	
Example:
	new AssetLoader(
		{ type:'js', url:'app/file1.js', ld: 20, depend: [
			{ type:'js', url:'app/file2.js', ld: 10, depend: [
				{ type:'manifest', url:'app/mf1.asp', ld: 30 }
			] }
		], vis: visualization });

*/
 
AssetLoader = new Class({
	
	Implements: [Events, Options],

	options: {
		ld: 1,
		onLoad: $empty,
		vis: $empty
	},

	initialize: function(options){
		$extend(this, {
			loaded: 0,
			total: options.total ? options.total : AssetLoader.calculateLoad(options)
		});
		this.setOptions(options);
//		this.options.url += '?d='+(new Date()).getTime();
//		console.log('_'+this.options.url);
		if (this.options.depend) {
			var ld = new Array();
			this.options.depend.each(function(e){
				if ($defined(e)) {
					if (!$defined(e.vis)) e.vis = this.options.vis;
					if (!$defined(e.total)) e.total = this.total;
					ld.push(new AssetLoader(e));
				}
			}.bind(this));
			new Group(ld).addEvent('load', function() { 
				this.fetch();
			}.bind(this) );
		} else {
			if (this.options.url) {
				this.fetch.delay(15, this);
			}
		}
	},
	
	fetch: function() {
//	console.log(this.options.url);
//		console.log('-'+this.options.url);
		switch(this.options.type){
			case 'js':
			//	console.log(this.options.url);
				new Asset.javascript(this.options.url, { 
					onload: this.assetLoaded.bind(this)
				});
				return this;
			case 'manifest':
				new Request({ url: this.options.url, 
					onComplete: function(r) {
						var ld = new Array();
						var relative = this.getURLLocation(this.options.url);
	//					console.log(this.options.url, relative);
						var arr = eval(r);
						var relTot = this.options.ld / AssetLoader.calculateLoad({ ld:0, depend:arr });
						//console.log(relative);
						arr.each(function(e){
							if ($defined(e)) {
								if (!e.vis) e.vis = this.options.vis;
								if (!e.total) e.total = this.total;
								AssetLoader.applyRelativePath(e, relative);
								e.ld *= relTot;
								ld.push(new AssetLoader(e));
							}
						}.bind(this));
						new Group(ld).addEvent('load', this.assetLoaded.bind(this) );
					}.bind(this),
					onFailure: function(a, b) {
						console.log('404: cant find manifest', a, b);
					}
				}).send();
				return this;
			case 'css':
				new Asset.css(this.options.url);
				this.assetLoaded();
				return this;
			default:
		}
	},
	
	assetLoaded: function() {
		var d = { url: this.options.url, type: this.options.type, loaded: new Date() };
		AssetLoader.loaded.push(d);
		if (this.options.vis)
			this.options.vis(this.options.type!='manifest'?(this.options.ld/this.total)*100:0, this.options.url);
		this.fireEvent('load', this);
		AssetLoader.listener.fireEvent('load', d);
	},
	
	getURLLocation: function(u) {
		var i = u.lastIndexOf('/');
		return i!=-1 ? u.substring(0, u.lastIndexOf('/')) : '';
	}
	

});

AssetLoader.extend({

	loaded: new Array(),
	listener: new Events(),
	
	hasLoaded: function(n) {
		var flag = false;
		AssetLoader.loaded.each(function(e){
			if (e.url==n)
				flag = true;
		});
		return flag;
	},

	// calculates the total load of any node and all of its children
	calculateLoad: function(a) {
		if ($defined(a)) {
			//console.log(a);
			var t = ($defined(a.ld) ? a.ld : 0);
			if ($defined(a.depend)) 
				a.depend.each(function(b){
					t += AssetLoader.calculateLoad(b);
				});
			return t;
		} else {
			return 0;
		}
	},

	// prefixes a relative path to a node url and every url of its children.
	applyRelativePath: function(a, u) {
		if (u) a.url = u+'/'+a.url;
		if (a.depend) 
			a.depend.each(function(b){
				AssetLoader.applyRelativePath(b, u);
			});
		return a;
	}

});


var Asset = {

	javascript: function(source, properties){
		properties = Object.append({
			document: document
		}, properties);

		if (properties.onLoad){
			properties.onload = properties.onLoad;
			delete properties.onLoad;
		}

		var script = new Element('script', {src: source, type: 'text/javascript'});
		var load = properties.onload || function(){},
			doc = properties.document;
		delete properties.onload;
		delete properties.document;

		return script.addEvents({
			load: load,
			readystatechange: function(){
				if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
			}
		}).set(properties).inject(doc.head);
	},

	css: function(source, properties){
		properties = properties || {};
		var onload = properties.onload || properties.onLoad;
		if (onload){
			properties.events = properties.events || {};
			properties.events.load = onload;
			delete properties.onload;
			delete properties.onLoad;
		}
		return new Element('link', Object.merge({
			rel: 'stylesheet',
			media: 'screen',
			type: 'text/css',
			href: source
		}, properties)).inject(document.head);
	},

	image: function(source, properties){
		properties = Object.merge({
			onload: function(){},
			onabort: function(){},
			onerror: function(){}
		}, properties);
		var image = new Image();
		var element = document.id(image) || new Element('img');
		['load', 'abort', 'error'].each(function(name){
			var type = 'on' + name;
			var cap = name.capitalize();
			if (properties['on' + cap]){
				properties[type] = properties['on' + cap];
				delete properties['on' + cap];
			}
			var event = properties[type];
			delete properties[type];
			image[type] = function(){
				if (!image) return;
				if (!element.parentNode){
					element.width = image.width;
					element.height = image.height;
				}
				image = image.onload = image.onabort = image.onerror = null;
				event.delay(1, element, element);
				element.fireEvent(name, element, 1);
			};
		});
		image.src = element.src = source;
		if (image && image.complete) image.onload.delay(1);
		return element.set(properties);
	},

	images: function(sources, options){
		options = Object.merge({
			onComplete: function(){},
			onProgress: function(){},
			onError: function(){},
			properties: {}
		}, options);
		sources = Array.from(sources);
		var counter = 0;
		return new Elements(sources.map(function(source, index){
			return Asset.image(source, Object.append(options.properties, {
				onload: function(){
					counter++;
					options.onProgress.call(this, counter, index, source);
					if (counter == sources.length) options.onComplete();
				},
				onerror: function(){
					counter++;
					options.onError.call(this, counter, index, source);
					if (counter == sources.length) options.onComplete();
				}
			}));
		}));
	}

};


/*
---

script: Group.js

name: Group

description: Class for monitoring collections of events

license: MIT-style license

authors:
  - Valerio Proietti

requires:
  - Core/Events
  - /MooTools.More

provides: [Group]

...
*/

(function(){

this.Group = new Class({

	initialize: function(){
		this.instances = Array.flatten(arguments);
		this.events = {};
		this.checker = {};
	},

	addEvent: function(type, fn){
		this.checker[type] = this.checker[type] || {};
		this.events[type] = this.events[type] || [];
		if (this.events[type].contains(fn)) return false;
		else this.events[type].push(fn);
		this.instances.each(function(instance, i){
			instance.addEvent(type, this.check.pass([type, instance, i], this));
		}, this);
		return this;
	},

	check: function(type, instance, i){
		this.checker[type][i] = true;
		var every = this.instances.every(function(current, j){
			return this.checker[type][j] || false;
		}, this);
		if (!every) return;
		this.checker[type] = {};
		this.events[type].each(function(event){
			event.call(this, this.instances, instance);
		}, this);
	}

});

})();




