/**
 * Comp Library
 */
vapi.comp = {

	defaultScriptName: "script.js",

	includeURI: null,
	definedClasses: {},

	root: null,
	rootComps: [],
	instances: {},

	constructQueue: [],
	instantiateQueue: [],
	loadingClasses: [],
	definedClassQueue: [],

	/**
     * Dump hierarchical representation of comp structure to document.
     */
	dumpTree: function() {
		var format = false;
		var $panel = $('<div/>').addClass("vapi-debug").addClass("vapi-tree");
		$("body").append($panel);
		var style = format ? {
			width: "10px",
			right: "0",
			//overflow: "hidden",
			position: "absolute",
			zIndex: "100",
			opacity: 0.2
		} : {};
		$panel.css(style);
		if (format) {
			$panel.hover(
				function(){
					$(this).css({
						opacity:1.0,
						width:"90%"
					})
				},
				function(){
					$(this).css({
						opacity:0.2,
						width:"10px"
					})
				}
				);
		}
		//        panel.append(this.root.dump());
		for (var i in this.rootComps) {
			$panel.append(this.rootComps[i].getDump());
		}
	//$("body").append($panel);
	},

	onload: function() {
		$(this.rootComps).each(function(){
			this.load();
		});
	},

	/**
     * Extend comp.
     * @param {string} baseURI base comp URI
     * @param {object} extension
     */
	extend: function(baseURI, extension) {
		extension = extension || {};
		return this.define(extension, baseURI);
	},

	/**
     * Define comp class.
     * @param {function|object} definition
     * @param {string} baseURI optional base comp URI
     */
	define: function(definition, baseURI) {

		//
		// Get queued key.
		//
		var uri = this.includeURI;

		vapi.trace.warnf("Defining Comp: {%s}", [uri]);

		//
		// Get base class.
		//
		var baseClass = vapi.comp.generic;
		if (baseURI) {
			//
			// Handle relative URI.
			//
			if (!baseURI.match(/\//)) {
				baseURI = uri + "/" + baseURI;
				baseURI = baseURI.replace(/\/[^\/]+\/\.\./g, "");
			}

			if (this.definedClasses[baseURI]) {
				//alert("Extending " + baseURI);
				baseClass = this.definedClasses[baseURI];
			}
		}

		//
		// Require proper JS class definition?
		//
		// See: http://groups.google.com/group/jquery-dev/msg/12d01b62c2f30671
		//
		if (typeof(definition) != "function") {
		}

		//alert("Base for " + uri + " is:\n\n" + baseClass);

		//
		// Create "class" (function) for this new comp, where:
		//
		// 1. the only base initialization code is to call the
		// base constructor (using "call" function to apply to "this")
		//
		// 2. the actual prototype (template object) for this
		// comp is the "definition" object (parameter)
		//
		// 3. this comp's constructor is properly assigned
		//
		// References:
		//   http://phrogz.net/JS/Classes/OOPinJS2.html
		//   http://peter.michaux.ca/articles/class-based-inheritance-in-javascript
		//   http://www.broofa.com/2009/02/javascript-inheritance-performance/
		//
		//vapi.trace.cryf("%s base is %s", [uri, baseClass.prototype.toSource()]);
		//var proto = definition.call ? baseClass.prototype : definition;
		var compClass = function(){
			//vapi.alert("I am %s descending from %s", [proto.name, baseClass.prototype.name]);
			baseClass.call(this);
			if (definition.call) definition.call(this);
		};
		compClass.prototype = definition.call ? baseClass.prototype : $.extend(true, new baseClass(), definition);
		compClass.prototype.constructor = compClass;

		//
		// Create "base" reference for calling base methods.
		//
		compClass.prototype._base = baseClass.prototype;

		/* 10/16/09 : Replaced
        //
        //
        // Apply "comp" inheritance to defined class.
        //
        // Prepend base constructor call to source of class definition.
        //
        var compClass = definition;
        //compClass.prototype = new vapi.comp.generic();
        compClass.prototype = new baseClass();
        var source = compClass.toString().match(/\{([\s\S]*)\}/m)[1];
        compClass.prototype.constructor = new Function(
            "vapi.comp.generic.call(this);\n" + source
            );
        */

		/* Orig: Replaced.
        compClass.prototype.constructor = function(){
            vapi.comp.generic.call(this);   // simulate base constructor
            compClass.call(this);           // derived constructor
        }
        */

		//
		// Store class definition for use in later
		// instantiations.
		//
		//this.definedClasses[uri] = compClass;
		this.definedClassQueue.push(compClass);
	},

	/**
     * Start construction process for comp.
     * @param {string} id comp ID
     * @param {string} uid comp UID
     * @param {string} uri comp URI
     * @param {string} parentUID ID of parent comp
     */
	construct: function(id, uid, uri, parentUID) {
		vapi.trace.warnf("Construct Comp: #%s (%s) as a child of %s ...", [uid, uri, parentUID]);

		//
		// If parent is not yet constructed, queue this
		// construction for later (until parent is found).
		//
		if (parentUID && !this.get(parentUID)) {
			this.queueConstruction(id, uid, uri, parentUID);
		}
		//
		// If script is detected, call method for constructing
		// coded comp.
		//
		else if (uri.match(/\/script.js$/)) {
			this.constructCoded(id, uid, uri.replace("/script.js", ""), parentUID);
		}
		//
		// Otherwise, call method for constructing generic comp.
		//
		else {
			this.constructGeneric(id, uid, uri, parentUID);
		}
	},

	/**
     * Queue construction of comp.
     * @param {string} id comp ID
     * @param {string} uri comp URI
     * @param {string} parentUID UID of parent comp
     */
	queueConstruction: function(id, uid, uri, parentUID){
		if (!this.constructQueue[parentUID]) {
			this.constructQueue[parentUID] = [];
		}
		this.constructQueue[parentUID].push({
			id: id,
			uid: uid,
			uri: uri,
			parentUID: parentUID
		});
	},

	/**
     * Queue instantiation of comp.
     * @param {string} id comp ID
     * @param {string} uri comp URI
     * @param {string} parentUID UID of parent comp
     */
	queueInstantiation: function(id, uid, uri, parentUID){
		if (!this.instantiateQueue[uri]) {
			this.instantiateQueue[uri] = [];
		}
		this.instantiateQueue[uri].push({
			id: id,
			uid: uid,
			uri: uri,
			parentUID: parentUID
		});
	},

	/**
	 * Call instantiation for generic (uncoded) comp.
	 * @param {string} id comp ID
	 * @param {string} uid comp UID
	 * @param {string} uri comp URI
	 * @param {string} parentUID ID of parent comp
	 */
	constructGeneric: function(id, uid, uri, parentUID) {
		vapi.trace.warnf("Construct Generic Comp: #%s as a {%s}...", [id, uri]);
		this.instantiate(id, uid, uri, parentUID);
	},

	/**
	 *
	 * @param {string} id comp ID
	 * @param {string} uid comp UID
	 * @param {string} uri comp URI
	 * @param {string} parentUID ID of parent comp
	 */
	constructCoded: function(id, uid, uri, parentUID) {
		vapi.trace.warnf("Construct Coded Comp: #%s as a {%s}...", [id, uri]);

		var scriptURI = (uri == "/" ? "" : uri) + "/" + this.defaultScriptName;

		//
		// Store key/uri for subsequent class definition
		// which doesn't know its own URI.
		//
		// See vapi.comp.define().
		//
		this.includeURI = uri;

		//        vapi.trace.writef('Initializing comp %s as a <span class="class">%s</span>', [uri, id]);

		//
		// If comp "class" is already loaded, instantiate.
		//
		if (this.definedClasses[uri]) {
			vapi.trace.writef("Comp class is already loaded. {%s}", [uri]);
			this.instantiate(id, uid, uri, parentUID);
		}
		//
		// If comp "class" is loading (HTTP request) queue instantiation.
		//
		else if (this.loadingClasses[uri]) {
			this.queueInstantiation(id, uid, uri, parentUID);
		}
		//
		// If comp "class" is not loaded/loading, queue instantiation and load.
		//
		else {
			vapi.trace.warnf('Loading Comp Script: %s', [scriptURI]);

			this.queueInstantiation(id, uid, uri, parentUID);
			this.loadingClasses[uri] = true;

			//
			// Load comp (HTTP)
			//
			$.ajax({
				url: scriptURI,
				dataType: "script",
				cache: false,
				async: false,
				success: function(js){

					if(jQuery.browser.safari){
					//eval(js);
					}

					vapi.trace.warnf('Script Loaded: %s', [scriptURI]);

					delete vapi.comp.loadingClasses[uri];
					vapi.trace.warnf("Defined Comp: {%s}", [uri]);
					vapi.comp.definedClasses[uri] = vapi.comp.definedClassQueue.shift();

					//
					// Instantiate objects waiting for class to load.
					//
					if (vapi.comp.instantiateQueue[uri]) {
						vapi.trace.warn("Items queued for instantiation...");
						var items = vapi.comp.instantiateQueue[uri];
						delete vapi.comp.instantiateQueue[uri];
						for (var i in items) {
							var item = items[i];
							vapi.trace.warn("Instantiate: " + $.toJSON(item));
							vapi.comp.instantiate(item.id, item.uid, uri, item.parentUID);
						}
					}
				}
			});
		}
	},

	/**
	 * Instantiate Comp
     * @param {string} id comp ID
     * @param {string} uid comp UID
     * @param {string} uri comp URI
     * @param {string} parentUID ID of parent comp
	 */
	instantiate: function(id, uid, uri, parentUID) {
		vapi.trace.writef("Instantiating Comp: #%s as a {%s}", [id, uri]);

		//        if (!this.definedClasses[uri]) return;
		var comp;

		if (this.definedClasses[uri]) {
			vapi.trace.writef("Instantiating Coded Comp: #%s as a {%s}", [id, uri]);

			//
			// Get class definition and instantiate.
			//
			// For some reason we have to use the prototype.constructor()
			// method to get the overloaded version here?
			//
			var compClass = this.definedClasses[uri];
			comp = new compClass();
		//var comp = new compClass.prototype.constructor();

		} else {
			vapi.trace.writef("Instantiating Generic Comp: #%s as a {%s}", [id, uri]);
			comp = new vapi.comp.generic();
		}

		//
		// Set comp properties.
		//
		comp.id = id;
		comp.uid = uid;
		comp.uri = uri;

		//
		// Tell comp to initialize.
		//
		comp.init();

		//
		// Add comp to parent (if specified).
		//
		if (parentUID) {
			//vapi.trace.cryf("Adding Comp #%s to parent #%s", [id, parentUID]);
			if (!this.get(parentUID)) {
				vapi.trace.writef("No such parent! (#%s)", [parentUID]);
			} else {
				//alert(this.get(parentUID).toSource());
				this.get(parentUID).addChild(comp);
			}
		}

		//
		// Store comp.
		//
		this.register(comp, uid);

		vapi.trace.warn("Initialized!");

		//
		// Call constructors for children.
		//
		if (this.constructQueue[uid]) {
			vapi.trace.warn("Children queued for construction...");
			var children = this.constructQueue[uid];
			delete this.constructQueue[uid];
			for (var i in children) {
				var child = children[i];
				vapi.trace.warn("Construct: " + $.toJSON(child));
				this.construct(child.id, child.uid, child.uri, id);
			}
		}

		vapi.trace.write("...");
	},

	/**
     * Store comp instance under UID.
     * @param {vapi.comp.generic} comp comp object
     * @param {string} uid comp UID
     */
	register: function(comp, uid) {
		vapi.trace.writef("Registering comp: %s", [uid]);

		if (!this.root) {
			this.root = comp;
		}

		//
		// If comp doesn't have a parent, it must be at the root level.
		//
		if (!comp._parent) {
			this.rootComps.push(comp);
		}

		this.instances[uid] = comp;
	},

	/**
     * Retrieve instance of comp by UID.
     * @param {string} id comp UID
     */
	get: function(uid) {
		return this.instances[uid];
	}

};



//
// Generic "Comp" Class Prototype
//
vapi.comp.generic = function(){
	this._anon = [];
	this._children = [];
	this._parent = null;
};
vapi.comp.generic.prototype = {

	constructor: vapi.comp.generic,

	/**
	 * anonymous functions
	 * @see $(f)
	 */
	_anon: [],

	/**
	 * child comps
	 * @see addChild()
	 */
	_children: [],

	/**
	 * parent comp
	 * @see addChild()
	 */
	_parent: null,

	/**
	 * name of comp
	 */
	name: "Generic Comp",
    
	/**
	 * ID of comp
	 */
	id : 'undefined',

	/**
	 * unique ID of comp
	 */
	uid : 'undefined',

	/**
	 * base URI source of comp
	 * (May not be unique)
	 */
	uri: 'undefined',


	//
	// *** True JavaScript OOP ***
	//
	// This method calls base constructor (vapi.comp.generic())
	// in context of actual object being instantiated instead
	// of common (shared) prototype ancestor.  This is required
	// to give descendants their own copies of ancestor members.
	//
	// See: http://www.coolpage.com/developer/javascript/Correct%20OOP%20for%20Javascript.html
	//
	/*
    base: function(){
        //alert("Base!");
        vapi.comp.generic.call(this);
    },
    */

	load: function(){
		vapi.trace.writef("Loading %s...", [this.uid]);
		$(this._children).each(function(){
			this.load();
		});
		/*
        for (var i in this._children) {
            var child = this._children[i];
            child.load();
        }
        */
		this.onload();
	},

	init: function(){
	},

	onload: function(){
		vapi.trace.warn("Generic Comp Loader!");
	},

	/**
	 * Show comp.
	 * @abstract
	 */
	show: function(){
		alert("show " + this.uid);
	},
	/**
	 * Hide comp.
	 * @abstract
	 */
	hide: function(){
		alert("hide " + this.uid);
	},

	/**
	 * Add child comp.
	 * @param {vapi.comp.generic} c comp to add
	 */
	addChild: function(c) {
		//vapi.trace.writef("Adding Child: #%s to #%s", [c.id, this.id]);
		this._children.push(c);
		c._parent = this;
	},

	/**
	 * Get child by ID.
	 * @param {string} id ID of comp
	 * @type vapi.comp.generic
	 */
	getChild: function(id){
		if (!this._children) return null;
		for (var i in this._children) {
			var child = this._children[i];
			if (child.id == id) return child;
			if (child.uid == id) return child;
		}
		return null;
	},

	getChildren: function(){
		return this._children;
	},

	/**
	 * Magic method for doing context-sensitive ops for comp.
	 *
	 * @param {mixed} p parameter
	 * @return mixed
	 *
	 * $()         - Return jQuery for calling comp's ID
	 * $(selector) - Return jQuery for selector relative to comp
	 * $(function) - Return reference to anonymous function in context of calling comp.
	 *
	 */
	$: function(p){
		switch (typeof(p)) {
			case "function" :
				var i = this._anon.length;  // get index of anon. function
				var fid = "_anon_" + i;      // create unique ID for new function
				this[fid] = p;               // add function to this object
				this._anon.push(fid);        // add id of new function to incr. count

				vapi.trace.write("Adding anonymous function...");

				//
				// Create "delegate" for new anon. function
				//
				eval("var d = function(a,b,c){return vapi.comp.instances['" + this.uid + "']." + fid + "(a,b,c);}");

				return d;
			case "string" :
				return $("#" + this.uid).find(p);
			case "undefined" :
				return $("#" + this.uid);
			default:
				alert(typeof(p));
		}
	},
	$f: function(f) {
		if (typeof(f) == "function") {
			return this.$(f);
		}
		eval("var f = function(a,b,c){return vapi.comp.instances['" + this.uid + "']." + f + "(a,b,c);}");
		return f;
	},

	/**
	 * @deprecated
	 */
	$call: function(f) {
		vapi.deprecate("vapi.comp.proto.$call()", "$()");
		return this.$(f);
	},

	/**
	 * Get dump structure.
	 * @type jQuery
	 * @see vapi.comp.dumpTree()
	 */
	getDump: function() {
		var $node = $("<div/>");
		$node.css({
			paddingLeft: "2em"
		});
		//node.append(vapi.sprintf("#%s (%s) #children:%s", [this.id, this.uri, this._children.length]));
		$node.append(vapi.sprintf("#%s (%s)", [this.uid, this.uri]));
		for (var i in this._children) {
			var child = this._children[i];
			$node.append(child.getDump());
		}
		return $node;
	//vapi.trace.writef("%s%s (ID=%s)", [indent, this.uri, this.key]);
	}

};