/***********************************************************************************************************************************************************\
 * ARREST
 * JavaScript Object-to-RESTful Resource Mapping
 * @author cch1@hapgoods.com
 * @version 2.0
 * Requires: Class (above) and jQuery 1.3 or above
 * This library provides functionality allowing remote REST resources to be modeled locally as JavaScript
 * objects.  It is similar to Jester (http://jesterjs.org) but is intended to be subclassed (via Class-based
 * inheritance) to provide useful client-side functionality.  As such, the interface between Resources 
 * (both collections and members) has been made as transparent as possible, and hopefully, easy to extend.
 * It also completely forgoes any XML functionality in favor of JSON.
\***********************************************************************************************************************************************************/

/************************************************************************************************************************************************************\
 * Most credit for this implementation of class-based inheritance goes to John Resig: http://ejohn.org/blog/simple-javascript-inheritance
 * For standard prototypal object construction consider: http://www.permadi.com/tutorial/jsFunc/index.html
 * For closure-based encapsulation: http://www.wait-till-i.com/2007/08/22/again-with-the-module-pattern-reveal-something-to-the-world
\************************************************************************************************************************************************************/
// Inspired by base2 and Prototype
(function(){
	var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
	// The base Class implementation (does nothing)
	this.Class = function(){};

	// Create a new Class that inherits from this class
	Class.extend = function(prop) {
		var _super = this.prototype;
	
		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializing = true;
		var prototype = new this();
		initializing = false;
	    
		// Workaround IE Bug when overriding toString and valueOf methods.  Still present in IE8!  Note
		// that _super calls are NOT supported for these two methods.
		// Reference: http://webreflection.blogspot.com/2007/07/quick-fix-internet-explorer-and.html
		prototype.toString = prop.toString;
	
		// Copy the properties over onto the new prototype
		for (var name in prop) {
	    	// Check if we're overwriting an existing function
			prototype[name] = typeof prop[name] == "function" && 
			typeof _super[name] == "function" && fnTest.test(prop[name]) ?
			(function(name, fn){
				return function() {
				var tmp = this._super;

				// Add a new ._super() method that is the same method
				// but on the super-class
				this._super = _super[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._super = tmp;

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

		// The dummy class constructor
		function Class() {
			// All construction is actually done in the init method
			if ( !initializing && this.init )
				this.init.apply(this, arguments);
		}

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

		// Enforce the constructor to be what we expect
		// NB: This includes the correction suggested by Miodrag Kekic here: http://ejohn.org/blog/simple-javascript-inheritance/#comment-300098
		//     Without Miodrag's correction, the constructor of subclasses appears to be an anonymous function.
		prototype.constructor = Class;

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

    	return Class;
	};
})();

/***********************************************************************************************************************************************************\
 Inspired by Prototype
\***********************************************************************************************************************************************************/
Function.prototype.bind = function(obj){
	var __method = this;
	return function(){
		__method.apply(obj, arguments);
	}
};

/***********************************************************************************************************************************************************\
 * ARREST 
\***********************************************************************************************************************************************************/

var REST = {};
// A singleton anchoring the resource hierarchy
REST.Root = {
	_children: [],
	path: function(){return this.toParam();},
	root: function(){return (this.parent ? this.parent.root() : this)}, 
	_get: function(callback){
		$.get(this.path(), {}, function(data, textStatus){
			callback(data);
		}, 'json')
	},
	_put: function(){console.debug("Not Yet Implemented [" + this.toString() + ']')},
	_post: function(){console.debug("Not Yet Implemented [" + this.toString() + ']')},
	_delete: function(){console.debug("Not Yet Implemented [" + this.toString() + ']')},
	graft: function(resource){
		var name = arguments[1] || resource.toParam();
		this._children.push(resource);
		this[name] = resource;
		if (resource) {
			if (resource.root() != REST.Root) {			
				if (resource.parent) console.log("Warning: Tree change!  Moving " + resource.path() + " to " + this.path() + " as " + name + ".")
				resource.parent = this;
			}
//			console.log("Grafted " + name + " onto " + this.path() + " (" + resource.path() +")");
		} else console.log("Pruned " + name + " from " + this.path());
		return resource;
	},
	toParam: function(){return "";},
	toString: function(){return "<Root>";}
};

REST.Resource = Class.extend(REST.Root).extend({
	init: function(){
		this.loaded = false;
		this.$ = {};
		this._children = [];
	},
	path: function(){return (this.parent ? this.parent.path() : "") + '/' + this.toParam()},
	load: function(callback){
		if (!this.loaded) this._get(function(attrs){
			this.updateAttributes(attrs);
			this.loaded = true;
			callback.call(this);
		}.bind(this))
		else callback.call(this);
	},
	create: function(){console.debug("Not Yet Implemented [" + this.toString() + ']');},
	destroy: function(){console.debug("Not Yet Implemented [" + this.toString() + ']');},
	update: function(){console.debug("Not Yet Implemented [" + this.toString() + ']');},
	reload: function(){console.debug("Not Yet Implemented [" + this.toString() + ']');},
	updateAttributes: function(attrs){jQuery.extend(this.$, attrs);},
	toParam: function(){return this.toString();},
	toString: Object.prototype.toString
});

REST.CollectionResource = REST.Resource.extend({
	init: function(name){
		this._super();
		this.setMembers(arguments[1] || []);
		this._name = name;
	},
	setMembers: function(members){
		for (i = 0; i < members.length; i++) {
			var m = members[i];
			if (!(m instanceof REST.Resource)) m = REST.hydrate(m);
			this.graft(m);
		}
	},
	toArray: function(){return this._children},
	toString: function(){return this._name}
});

REST.ActiveResource = REST.Resource.extend({
	_collection: null,
	_associations: [],
	init: function(attrs){
		this._super();
		jQuery.extend(attrs, this.defaults);
		this.updateAttributes(attrs);
	},
	_identify: function(attrs){
		if (attrs.id) {
			if (this.id && (this.id != attrs.id)) throw new Error("Mismatch in id: " + this.id + " != " + attrs.id);
			this.id = attrs.id;
			this._collection.graft(this);
		}	
	},
	newRecord: function(){return !this.toParam()},
	updateAttributes: function(obj){
		this._identify(obj);
		obj = this._hydrateAttributes(obj);
		this._super(obj);
		this._linkReferences();
		this._graftAssociates();
	},
	_hydrateAttributes: function(obj){
		if (typeof(obj) == 'object') {
			for (prop in obj) {
				var val = obj[prop];
				if (val && typeof(val) == 'object') {
					if (val instanceof Array)
						obj[prop] = new REST.CollectionResource(prop, val);
					else if (val.id && !(val instanceof REST.Resource)) {
						var model = REST.models[prop] || REST.Resource;
						if (this.id == val.id && model.prototype == this.constructor.prototype) {
							obj = val;
						} else {
							obj = model.prototype._collection[val.id] || new model();
							obj.updateAttributes(val);							
						}
					}
				}
			}
		}
		return obj;
	},
	_linkReferences: function(){
		for (var prop in this.$){
			var matchData = /^(.+)_id$/.exec(prop);
			if (matchData) {
				var fk = this.$[prop];
				var associate_name = matchData[1];
//				console.log("Associating " + associate_name);
				var model = REST.models[associate_name];
				if (model) {
					var associate = model.prototype._collection[fk];
					if (!fk || associate) {
						this.$[associate_name] = associate;
						delete this.$[prop]; // One or the other, but not both
					} else console.log("Warning: Associate " + associate_name + "[" + fk + "] not loaded.");
				} else console.log("Warning: Model " + associate_name + " missing from map.");
			}
		}
	},
	_graftAssociates: function(){
		for (i = 0; i < this._associations.length; i++) {
			var association = this._associations[i];
			if (association in this.$) this.graft(this.$[association], association);
		}
	},
	toParam: function(){return this.id && this.id.toString()},
	toString: function(){return this.id || "new resource"}
})