/*jslint browser:true, onevar:false, white:false */
/*globals window,alert,jQuery,$ */

$(function() {
	window.juvo.build();
});


(function() {
	if( window.juvo ) {
		return;
	}
	
	window.juvo = function(varName) {	
		// Make the id free of the any possible class ('.') or id ('#') modifiers
		return new _juvo(/^[\.|#].*/.test(varName) ? varName.slice(1) : varName);
	};

    window.juvo.bind = function( targetObj, bindTo, callback ) {
		var binding = {
			'target': null,
			'callback': callback
		};
		if( ! juvo._bindings[targetObj] ) {
			juvo._bindings[targetObj] = {};
			juvo._bindings[targetObj][bindTo] = [ binding ];
		} else if( ! juvo._bindings[targetObj][bindTo] ) {
			juvo._bindings[targetObj][bindTo] = [ binding ];
		} else {
			juvo._bindings[targetObj][bindTo].push( binding );			
		}
    };
    
	window.juvo.build = function() {

		for(idx in window.juvo._definitions) {
			defs = window.juvo._definitions[idx];
			
			if( defs.length == 1 ) {
				def = defs[0];
			} else {
				// Could put more complex decision logic here to determine
				// which instance to create.
				def = defs[0];
			}

			//console.log(def.name);
			//console.log(def.args);
			try {
				juvo.instances[def.name] = new def.obj(def.name, def.args);
			} catch(err) {
				try { console.log(err); } catch(err) {}
			}
			
			//console.log(juvo.instances[def.name]);
		}
		
		window.juvo._definitions = [];
		
		for(idx in window.juvo._onBuilds) {
			callback = window.juvo._onBuilds[idx];
			callback();
		}
		
		window.juvo._onBuilds = [];
	};
	
	window.juvo.onBuild = function( callback ) {
		window.juvo._onBuilds.push( callback );
	};
	
	$.extend( window.juvo, {
		'_bindings': {},
		'_definitions': {},
		'instances': {},
		'_onBuilds': []
	});

	function _definition( name, obj, args ) {
		this.name = name;
		this.obj = obj;
		this.args = args ? args : {};
	}
	
	_definition.prototype = {
		constructor: _definition,
		
		using: function( args ) {
			$.extend(this.args, args);
		}
	};
		

	function _juvo(name) {
		this.__name__ = name
	};

	_juvo.prototype = {
		constructor: _juvo,

		instantiate: function() {
			var args = [].slice.call(arguments);
			var objClass = args[0];
			args = args.slice(1);
			
			if( arguments.length == 0 )
				throw "Not enough arguments for the juvo.create function.";
			
			var obj = new objClass(this.__name__, args);
			juvo.instances[this.__name__] = obj;
			
			return obj;
		},
		
		// create( objectClass, *args )
		create: function() {
			var args = [].slice.call(arguments);
			var objClass = args[0];
			args = args.slice(1);
			
			if( arguments.length == 0 )
				throw "Not enough arguments for the juvo.create function.";
			
			var def = new _definition( this.__name__, objClass, args );
			
			if( ! juvo._definitions[this.__name__] ) {
				juvo._definitions[this.__name__] = [];
			}
			
			juvo._definitions[this.__name__].push( def );
			//juvo._definitions.push( def );
				
			return def;
		},
		
		bind: function( targetObj, bindTo, callback ) {		
			var binding = {
				'target': this.__name__,
				'callback': callback
			};
			
			if( ! juvo._bindings[targetObj] ) {
				juvo._bindings[targetObj] = {};
				juvo._bindings[targetObj][bindTo] = [ binding ];
				
			} else if( ! juvo._bindings[targetObj][bindTo] ) {
				juvo._bindings[targetObj][bindTo] = [ binding ];
			} else {
				juvo._bindings[targetObj][bindTo].push( binding );			
			}
		},
		
		fire: function(eventFunc, arguments) {
			if( ! juvo._bindings[this.__name__] || 
				! juvo._bindings[this.__name__][eventFunc] ) {
				return;
			}
			var bindings = juvo._bindings[this.__name__][eventFunc];
			for( idx in bindings ) {
				binding = bindings[idx];
				if( binding.target != null ) {
				    obj = juvo.instances[binding.target]
				    obj[binding.callback].apply(obj, arguments);
			    } else {
			        binding.callback( arguments );
			    }
			}
		},
		
		get: function() {
			if( this.__name__ in juvo.instances ) {
				return juvo.instances[this.__name__];
			} 
			
			return null;
		}
	};

	var _pending = {};
	
	window['juvo']['isDefined'] = function(name) {
		var obj = window, idx, piece;
		var names = name.split('.');

		for( idx in names ) {
			piece = names[idx];
			if( piece == 'window' && idx == 0 )
				continue;
			
			if(!(piece in obj)) {
				return false;
			}
			
			obj = obj[piece];
		}
		
		return true;
	};
	
	// juvo.define(name, def)
	// juvo.define(name, parent, def)
	window['juvo']['define'] = function(name, arg1, arg2) {
		//console.log('Considering: ' + name);
		
		if( ! arg2 ) {
			return juvo._define(name, 'juvo.Class', arg1);
		}
		
		// If the parent object is not yet defined, then we need to wait.
		if( ! juvo.isDefined(arg1) ) {
			//console.log('Post-poning: ' + name);
			
			// This parent has not yet been added, so we need to init the array.
			if( !(arg1 in _pending) ) 
				_pending[arg1] = [];
				
			// Add this object definition to the list of pending items for this parent.
			_pending[arg1].push({
				'name': name, 
				'def': arg2
			});
			
			return;
		}
		
		return juvo._define(name, arg1, arg2);
	};
	
	window['juvo']['_define'] = function(name, parent, def) {
		//console.log('Defining: ' + name);
		
		var parentObj = eval(parent);
		
		//if( ! (parentObj instanceof juvo.Class) )
		//	throw "Cannot define " + name + " as an extension of the non juvo.Class parent: " + parent;

		
		// juvo.ui.file
		var obj = window, piece, idx, children, child;
		var names = name.split('.');
		
		for( idx in names ) {
			piece = names[idx];
			if(piece == 'window')
				continue;
			if(idx == (names.length - 1)) {
				// If this already exists, for whatever reason, we need not proceed any further.
				if( ! obj[piece] ) {
					obj[piece] = parentObj.extend(name, def);
				}
				break;
			}
			if(!(piece in obj)) {
				obj[piece] = {};
			}
			
			obj = obj[piece];
		}
		
		// Let's check to see if this object has pending children.
		if(name in _pending) {
			children = _pending[name];
			for( idx in children ) {
				child = children[idx];
				
				// Check to make sure the child hasn't already been defined.
				if( ! eval(child.name) ) {
					window.juvo._define(child.name, name, child.def);
				}
			}
		}
	};
})();

// From John Resig's inheritance blog post
(function () {
	var initializing = false, 
		fnTest = /xyz/.test(function(){xyz;}) ? /\b_parent\b/ : /.*/; 

    // The base Class implementation (does nothing)
    this.Class = function () {};

    // Create a new Class that inherits from this class
    Class.extend = function (__type__, prop) {
        var _parent = this.prototype,
            name = false;
    
        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;

		//prototype.__self__ = prototype;
		prototype.__type__ = __type__;

		/*
		This is what we ideally want, but cannot have because at this point,
		__self__ would refer to the class, not the instance. We could potentially
		put this definition inside the constructor function wrapping, but I think
		it makes sense that the object be required in the fire def below.
		
		fire = function(func, args) {
			juvo(__self__.__name__).fire(func, args)
		}
		*/
		
		fire = function(self, func) {
			return function() {
				juvo(self.__name__).fire(func, arguments);
			}
		}
		
		// We must first wrap overriden parent functions such that we
		// inject the _parent into their scope. If it's not an 
		// overriden function, then simply perform a direct assignment.
		for (var name in prop) {	
		
			// Check if we're overwriting an existing function
			prototype[name] = typeof prop[name] == 'function' &&
				typeof _parent[name] == 'function' &&
				fnTest.test(prop[name]) ?
				(function (name, fn) {
					return function () {
						var tmp = this._parent,
							stmp = __self__._parent;

						// Add a new ._parent() method that is the same method
						// but on the super-class. We use both this and __self__
						// here to avoid confusion. this could potentially not be
						// pointing to the object, yet _parent would still be applicable.						
						this._parent = _parent[name];
						__self__._parent = _parent[name];

						// The method only need to be bound temporarily, so we
						// remove it when we're done executing
						var ret = fn.apply(this, arguments);        
						this._parent = tmp;
						__self__._parent = stmp;

						return ret;
					};
				})(name, prop[name]) :
					prop[name];
		}

        // The dummy class constructor
        function Class() {// (name, args)
			if( initializing ) return;
			
			// If called from the definition routine, then the first
			// argument is going to be the instance name. The second
			// argument will be our actual array of arguments.
			args = [].slice.call(arguments);

			if( args.length > 0 ) {
				this.__name__ = args[0];
				args = args[1];//.slice(1);
			}
			
			proto = {};
			// Wrap the existing functions so that __self__ will be in scope.
			for (var name in prototype) {
				if( name != 'self' && 
					typeof prototype[name] == 'function' ) {				

					this[name] = (function (self, name, fn) {
						return function() {
							__self__ = self;
							return fn.apply(this, arguments);
						}
					})(this, name, prototype[name]);
				}
			}
			
            // All construction is actually done in the init method
            if (this.init) {
                this.init.apply(this, args);
            }
        }

        // Populate our constructed prototype object
        Class.prototype = prototype;

        // Enforce the constructor to be what we expect
        Class.constructor = Class;

        // And make this class extendable
        Class.extend = arguments.callee;

        // Save static properties
        for (name in this) {
            if (name !== 'prototype' && name !== 'constructor' && name !== 'extend' && name !== 'self') {
                Class[name] = this[name];
            }
        }
        
        // Provide a reference to the static properties
        Class.prototype.self = Class;

        return Class;
    };
	
	window['juvo']['Class']	= Class;
})();



/*
Test script


<div id='objecta'>Object A</div><br />
<div id='objectb'>Object B</div><br />
<div id='objectc'>Object C</div><br />


<script>
juvo.define('juvo.test.objecta', {
	init: function(args) {
		$('#objecta').click( this.testFunc );
	},
	
	testFunc: function() {
console.log(__self__.__name__);	
		//alert('objecta testFunc');
	}
});

juvo.define('juvo.test.objectb', 'juvo.test.objecta', {
	init: function(args) {
		$('#objectb').click( this.testFunc );
	},
	
	testFunc: function() {
console.log(__self__.__name__);	
		__self__._parent();
	}
});

juvo.define('juvo.test.objectc', 'juvo.test.objectb', {
	init: function(args) {
		$('#objectc').click( this.testFunc );
	},
	
	testFunc: function() {
console.log(__self__.__name__);	
		this._parent();
	}
});

// Test multiple inheritance.
juvo('testa').create(juvo.test.objecta);
juvo('testb').create(juvo.test.objectb);
juvo('testc').create(juvo.test.objectc);

// Test same class, multiple objects:
juvo('testa').create(juvo.test.objecta, {id: '#objecta'});
juvo('testb').create(juvo.test.objecta, {id: '#objectb'});
juvo('testc').create(juvo.test.objecta, {id: '#objectc'});

$(function() {
console.log('-------------------------------');
});
</script>
*/
