/*
Script: dymoonic
License:
	GPLv3  (http://www.gnu.org/licenses/gpl-3.0.txt)
Copyright:
	MaXPert [Zohaib Sibt-e-Hassan] (http://www.gnu.org/licenses/gpl-3.0.txt)
*/

function Dymoonic(depTree, root){
	this.dependencies = new Object();
	this.root = root+'';
	
	this.busy = false;
	
	//Three states of a loadable file
	var dState = {
		notLoaded: 1,
		loadReady: 2,
		loaded: 3
	};
	
	//Is a leaf note if contains deps property
	function isLeafNode(nod){
		return !nod || typeof nod['deps'] !== 'undefined';
	};
	
	//Get load ready
	function filterNodes(dep, list, value){
		ret = [];
		for(var k in list)
			if(dep[list[k]] && dep[list[k]].state == value)
				ret.push(list[k]);
		
		return ret;
	};
	
	//Get the not loaded dependencies of an object
	function getNotLoadedDeps(nod){
		ret = [];
		if(!nod || !nod.loadedDeps)
			return ret;
		for(var k in nod.loadedDeps)
			if(!nod.loadedDeps[k] && typeof k == 'string')
				ret.push(k);
		return ret;
	};
	
	//Dispatch load node request must be bi
	function dispatchLoad(dep, name, f, b, p){
		if(typeof name == 'string')
			importJS(dep[name].path, f, b, p);
		else if(typeof name == 'object' && name.length){
			var pList = [];
			for(var k in name)
				if(dep[name[k]])
					pList.push(dep[name[k]].path);
			importJS(pList, f, b, p);
		}
		return true;
	};
	
	//Is node ready for loading
	function areDepsComplete(dep, name){
		if(!dep[name]) return null;
		if(dep[name].state == dState.loadReady)
			return true;
		
		for(var onName in dep[name].loadedDeps)
			if( onName != name && !dep[name].loadedDeps[onName] )
				return false;
		dep[name].state = dState.loadReady;
		return true;
	};
	
	//Mark a node loaded
	function markLoaded(dep, name){
		if(!dep[name]) return false;
		dep[name].state = dState.loaded;
		
		for(var forName in dep[name].depFor){
			dep[forName].loadedDeps[name] = true;
			areDepsComplete(dep, forName);
		}
		return true;
	};
	
	//Build dependency list
	function depList(dep, name, list){
		var wasFirst = false;
		if(!list){
			list = new Object();
			wasFirst=true;
		}
		if( typeof name !== 'string')
			return ;
		
		list[name] = true;
		var cDep = getNotLoadedDeps(dep[name]);
		for(var k in cDep){
			if( cDep[k] != name)
				depList(dep, cDep[k], list);
		}
		
		//If it was the first call .i.e. param was not supplied build array
		if(!wasFirst) return;
		
		ret = [];
		for(var k in list)
			ret.push(k);
		return ret;
	};
	
	//Build dependency tree structure
	function buildDep(nod, name, dep, path){
		if(!nod)
			return;
		if(isLeafNode(nod)){
			//Create object for itself
			if(!dep[name])
				dep[name] = new Object();
			dep[name].deps = nod.deps;
			dep[name].loadedDeps = new Object();
			if(!nod.path)
				dep[name].path = path+'.js';
			else
				dep[name].path = nod.path;
			dep[name].state = dState.notLoaded;
			//Is a dependency for which ones? depFor will contain name
			if(!dep[name].depFor)
				dep[name].depFor = new Object();
			
			//Build reverse parent depency For
			for(var i=0; i<dep[name].deps.length; i++){
				//Against each name mark loadedDeps to be not loaded
				dep[name].loadedDeps[ dep[name].deps[i] ] = false;
				
				//For that dependent node
				d = dep[name].deps[i];
				if(!dep[d])
					dep[d] = new Object();
				if(!dep[d].depFor)
					dep[d].depFor = new Object();
				dep[d].depFor[name] = true;
			}
			
			return;
		}
		
		for (var chName in nod)	buildDep(nod[chName], chName, dep, path+'/'+chName);
	};
	
	this.load = function(cp, f, b, a){
	
		if(this.busy)
			return ;
		
		this.busy = true;
		
		if(!this.dependencies[cp])
			throw "Invalid class requested";
			
		if(this.dependencies[cp].state == dState.loaded){
			a = a || []; b = b || this;
			this.busy = false;
			if(f) f.apply(b, a);
			return;
		}
	
		var hlist = depList(this.dependencies, cp);
		var count = hlist.length;
		
		for(var key in hlist)
			areDepsComplete(this.dependencies, hlist[key]);
		
		var nowLoad = filterNodes(this.dependencies, hlist, dState.loadReady);
		dispatchLoad(this.dependencies, nowLoad, afterLoad,	this);
		
		function afterLoad(){
			count-=nowLoad.length;
			for(var k in nowLoad) markLoaded(this.dependencies, nowLoad[k]);						
			if(count > 0){
				nowLoad = filterNodes(this.dependencies, hlist, dState.loadReady);
				dispatchLoad( this.dependencies, nowLoad, afterLoad, this);
			}else{
				this.busy = false;
				a = a || []; b = b || this;
				if(f) f.apply(b, a);
			}
		};
		
	};
	
	this.add = function(dpt, pth){
		buildDep.call(this, dpt, null, this.dependencies, pth);
	};
	
	buildDep.call(this, depTree, null, this.dependencies, root);
};