//This is an information-holder for everything related to a specific image to be precached.
//Initially, as the loadee sits untouched in the ImageLoader group, it's neither requested nor completed.
//Once the image is requested, its image becomes a new Image, with an onload that sets completed
//to true, notifies its thread, and calls the onload function in the loadee.
ImageLoadee = FS.Object.extend({
	className: "ImageLoadee",
	//data
	url: null,
	//state
	requested: false,
	completed: false,
	//controllers
	group: null,				//null until returned from a group
	thread: null,				//null when not requested. Once requested, thread will point to the thread downloading the image.
	//controller handling
	importance: 0,
	proximityAdjust: 0,
	//action
	onload: null,				//user-supplied function that will be called when image finishes downloading. This is NOT the onload of the image.
	//the actual loader
	image: null,
	
	init: function() {
		arguments.callee.base.call(this);
		if(!this.url) {return;}
		
	},
	
	load: function() {
		if(this.completed) return;
		this.requested = true;
		this.image = document.createElement('img');
		this.image.onload = this.loaded.bind(this);
		this.image.src = this.url;
	},
	
	loaded: function() {
		this.completed = true;
		for(var i in this) {
			if(i.indexOf('onload')!=-1 && typeof this[i]=='function') {
				// alert(this+"\n"+this.url+"\n"+i);
				this[i]();
				this[i] = null;
			}
		}
		if(this.thread) this.thread.oneMoreDown();
		if(this.group)  this.group.handleFinished(this);
	},
	
	needsAttention: function() {
		return this.requested==false && this.completed==false;
	},
	
	enter: function(group,importance) {
		if(!group) return false;
		group.add(this,importance);
		return this;
	},
	
	accommodate: function(hash) {
		if(hash.url!=this.url) return;
		if(typeof hash.onload == 'function') this["onload"+FS.realNow()+Math.random()] = hash.onload;
		if(!this.completed) {
			if(hash.group) {} //this is unfinished; make it so that one loadee can occupy two different groups, and processing the loadee puts it in both groups' finished-queues. this will take an addGroup function and will also require the loaded function to be able to handle all the groups the loadee occupies. As for now, a loadee will occupy the first group it was created for, and no accommodations can expedite its downloading; enabling this will enable the highest priority group associated with the loadee to handle, instead of the FIRST group associated with it.
		} else {
			this.group = null; this.thread = null;
			FS.setTimeout(this.loaded.bind(this),0);
		}
	}
});
function useLoadee(hash, group) {
	if(!hash.url) return null;
	var existing = allImageLoadees[hash.url];
	if(existing) {
		// alert('using existing loadee for '+hash.url+"\n\n"+existing+"\n\n"+existing.mixin);
		existing.accommodate(hash);
		return existing;
	} else {
		// alert('making new loadee for '+hash.url);
		var ret = ImageLoadee.create(hash);
		if(group) ret.enter(group);
		allImageLoadees[hash.url] = ret;
		return ret;
	}
}
var allImageLoadees = {};

//A thread is one image download procession. It begins inactive. Once it becomes active, it sucks a loadee from
//the group of its owner loader, requests it, and waits for completion. Once the loadee is complete, the thread
//will forget about it and request the next one. It will become inactive if a request for the next picture turns
//up no pictures (i.e. if the loader has no more pictures to request).

ImageThread = FS.Task.extend({
	className: "ImageThread",
	
	owner: null,				//required; will be an imageLoader
	
	waiting: false,				//time in between last completion and new request
	loading: false,				//time during picture download
	active: false,				//active when either waiting or loading; inactive initially and when owner loader is complete for the time being.
	currentLoadee: null,		//null when waiting, a Loadee when downloading
	
	_nextRequestTime: 0,
	
	loadedCount:0,
	
	onframe: function() {
		if(!this.active || this.loading || (this.waiting && this.elapsedTime<this._nextRequestTime)) return;
		this.requestNext();
	},
	requestNext: function() {
		if(this.loading) return;
		this.currentLoadee = this.owner.getNext();
		if(this.currentLoadee==null) {this.active = false; this.loading = false; this.waiting=false; return;}
		this.loading = true;
		this.waiting = false;
		this.currentLoadee.thread = this;
		this.currentLoadee.load();
	},
	
	oneMoreDown: function() {
		this.currentLoadee = null;
		this.loading = false;
		if(this.active) this.waiting = true;
		this._nextRequestTime = this.elapsedTime+this.owner.delay;
		this.loadedCount++;
	},
	
	toString: function() {
		var s = arguments.callee.base.call(this);
		return s.substring(0,s.length-1)+" "+(this.active?"A":"a")+(this.waiting?"W":"w")+(this.loading?"L":"l")+" count="+this.loadedCount+"]";
	},
	
	terminate: function() {
		arguments.callee.base.call(this);
		this.active = false;
		this.waiting = false;
	}
});


//A Loader manages threads and images. It has a group of loadees and a pool of threads. Every frame, it will:
//	check to see if there are any pictures left;
//		if so, activate any inactive threads.
//	check to see if the numThreads has changed;
//		if so, add and activate threads or remove threads by telling them to die after they finish their next picture.

ImageLoader = FS.Task.extend({
	className: "ImageLoader",
	
	numThreads: 5,
	delay: 0,
	
	group: null,
	threads: null,
	
	init: function() {
		arguments.callee.base.call(this);
		this.group = ImageGroup.create();
		this.threads = [];
		for(var i=0; i<this.numThreads; i++) this.threads[i] = ImageThread.create({owner:this}).start();
		this._lastNumThreads = this.numThreads;
	},
	
	numThreadsOnFrameObserver: function() {
		if(this._lastNumThreads==this.numThreads) return;
		//implement here the handling of a changing numThreads
		if(this._lastNumThreads<this.numThreads) {
			for(var i=this._lastNumThreads; i<this.numThreads; i++)
				this.threads.unshift(ImageThread.create({owner:this, active:true}).start());
		} else {
			var deletedThreads = this.threads.splice(0,this._lastNumThreads - this.numThreads);
			for(var i=0; i<deletedThreads.length; i++) deletedThreads[i].terminate();
		}
		//done handling changed numThreads
		this._lastNumThreads = this.numThreads;
	},
	
	handlePhotosAddedOnFrameObserver: function() {
		//Whenever the group needs attention, one thread is activated. If the group
		//needs lots of attention, one thread will be activated per frame until they're
		//all active.		
		if(this.group.needsAttention()) {
			for(var i=0; i<this.threads.length; i++)
				if(!this.threads[i].active) {
					this.threads[i].active = true;
					return;
				}
		}
	},

	getNext: function() {
		return this.group.getNext();
	},
	
	terminate: function() {
		arguments.callee.base.call(this);
		for(var i=0; i<this.threads.length; i++) this.threads[i].terminate();
	},
	
	handleInterrupt: function() {
		this.terminate();
	}
});


//An ImageGroup is like a super duper priority queue; it can hold loadees or other ImageGroups.
//	Each loadee or subgroup has an importance number; the highest importance in the group will be
//	drawn from first when a thread grabs a new loadee. If the object with the highest importance
//	is in fact a Loadee, the loadee will be removed from the group and given to the thread via
//  getNext method of the loader. If the object with the highest importance is a subgroup, then
//  that subgroup will be told to give up ITS object of highest importance, and so on.
//
//	This recursive prioritizability (sp? =D) should be quite useful in determining which views
//	get their assets downloaded first, as well as, in each view, determining which pieces of each
//	view get priority. This will avoid super-complex array reshuffling when views switch; for
//	example, all that'd be required for switching from Grid to Mosaic, for example, is a flop of
//  importance numbers for the two views. Grid and Mosaic are done downloading? Well, continue
//	downloading scrub images on the index view!
//
//	As a side note, only loadees will be placed into the "loaded" array; Subgroups will stay in
//	pending, and *their* finished loadees will go into *their* loaded arrays.
//
//	As another side note, the getNext function actually works with a target importance; it draws
//	from the item with the closest importance. Based on the default value for target, pretty much
//	the highest importance will be drawn from.
//
//	As yet another side note, the proximityAdjust property manually alters the calculated
//	difference between the target importance and importance observed.
//	
//  Keep in mind: a group's importance and proximityAdjust values have nothing to do with what 
//	that group returns from its getNext function.
ImageGroup = FS.Object.extend({
	className: "ImageGroup",
	
	importance: 0,
	proximityAdjust: 0,
	
	target: 2147483647,		//todo: allow "hi" and "lo" as values, to strictly choose highest or lowest importances first.
	
	pending: null,
	loaded: null,
	init: function() {
		this.pending = [];
		this.loaded = [];
	},
	getNext: function() {
		if(this.pending.length==0) return null;
		var lowestDiff = -1;
		var winner = null;
		var winningIndex = -1;
		for(var i=0; i<this.pending.length; i++) {
			var it = this.pending[i];
			if((it.importance===undefined) || (it.importance===null)) it.importance = 0;
			var diff = Math.abs(it.importance-this.target)+it.proximityAdjust;
			if(it.needsAttention() && (diff<lowestDiff || lowestDiff==-1)) {
				winner = it;
				lowestDiff = diff;
				winningIndex = i;
			}
		}
		if(winningIndex==-1) return null;
		if(winner.className=="ImageGroup") return winner.getNext();
		else {
			winner.group = this;
			this.pending.splice(winningIndex,1);
			return winner;
		}
	},
	needsAttention: function() {
		for(var i=0; i<this.pending.length; i++)
			if(this.pending[i].needsAttention()) return true;
		return false;
	},
	add: function(obj, importance) {		//obj can be a loadee or another group
		this.pending.push(obj);
		if(!(importance===undefined) && !(importance===null)) obj.importance = importance+1-1;
	},
	handleFinished: function(loadee) {
		this.loaded.push(loadee);
	}
});