/**
 * @author Anders Mattson, Creative District
 * @dependencies jquery.js, jquery.rpc.js	
 */
/*
// Makes debugging easier... maybe add Firebug Lite pr something here later on.
if(typeof console == undefined) var console = {
	log: function(){},
	warn: function(){}
}
*/
// Create the JSide namespace
var JSide = {};
window.JSide = JSide;

jQuery.extend(JSide,{
	
	// Standard settings namespace. Some settings are added to this namespace from the server side.
	settings: {},
	
	// The base RPC functions (rpc and query). This is the RPC that all other remote functions are based on.
	rpc: function(obj,fn){
		jQuery.rpc(obj,fn);
		return JSide;
	},
	
	query: function(query, fn){
		jQuery.rpc({
			method: "content.query",
			data:[query],
			callback: fn
		});
	},
	
	/**
	 * @namespace JSide.user Holds all function related to user interaction (login, logout, authorize, set/get user settings)
	 */
	user:{
		
		/**
		 * Makes a call to the server to check if the user is logged in or not.
		 * @param {Function} fn callback for the method. Input argument to callback is a boolean value.
		 */
		auth: function(fn){
			jQuery.rpc({
				method: "user.auth",
				callback: fn
			});
		},
		
		/**
		 * Main user login function.
		 * @param {String} uname Username
		 * @param {String} pass Password
		 * @param {Boolean} remember Wether the system should remember the user or not (implemented?).
		 * @param {Function} fn Callback to execute when the call finished. Input to callback is a boolean value.
		 */
		login: function(uname, pass, remember, fn){
			jQuery.rpc({
				method: "user.login",
				data: [uname, pass, remember],
				callback: fn
			});
		},
		
		/**
		 * Main logout function. This takes no callback since the user is redirected to the startpage on successful logout.
		 */
		logout: function(){
			jQuery.rpc({
				method: "user.logout",
				data:[]
			},function(d){
				if(d) {
					var spl = window.location.href.split("#");
					window.location.href = spl.shift();
				} else
					jQuery.rpc.error({error:890,msg:"An error occured while logging out"});
			});
		},
		
		/**
		 * Gets a user setting value from the server.
		 * @param {String} v The user setting to retreive.
		 * @param {Function} fn callback to execute when the call to the server finishes. Input to callback is an object with the property asked for defined.
		 */
		get: function(v, fn){
			jQuery.rpc({
				method: "user.get",
				data: [v],
				callback: fn
			});
		},
		
		/**
		 * Sets specific user settings.
		 * @param {Object} obj A javascript object containing the user settings to set.
		 * @param {Object} fn callback to execute when the call finishes. Input to callback is a javascript object with the specified user settings set.
		 */
		set: function(obj, fn){
			jQuery.rpc({
				method: "user.set",
				data: [obj],
				callback: fn
			});
		}
	},
	
	util: {
		toggleMore: function(obj){
			jQuery(obj).next().toggle();
			return false;
		}
	},
	
	dev: {
		themeroller: function(){
			if (!/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)){
				alert('Sorry, due to security restrictions, this tool only works in Firefox'); 
				return false; 
			}
			if(window.jquitr){ 
				jquitr.addThemeRoller();
			} 
			else{ 
				jquitr = {}; 
				jquitr.s = document.createElement('script');
				jquitr.s.src = 'http://jqueryui.com/themeroller/developertool/developertool.js.php'; 
				document.getElementsByTagName('head')[0].appendChild(jquitr.s);
			} 
		}
	}
});

/**
 * The model object is used for manipulating models in JSide
 * 
 * @param {Object} name The name of the model
 * @param {Object} id (Optional) id of the model item to load at initiation
 * @param {Object} fn (Optional) a callback to use when loading an initial model item
 * @returns {JSide.model}
 */
JSide.model = function ( name, id, fn ) {
	if ( !( this instanceof arguments.callee ) ) {
		return new arguments.callee( name, id, fn ); 
	}
	this.__model__ = name;
	if( id )
		this.get( id, fn );
	return this;
};

jQuery.extend( JSide.model.prototype, {

	/**
	 * Get (makes a call to the server) a specific model item
	 * @param {Object} id
	 * @param {Object} fn
	 */
	get: function ( id, template, fn ){
		var self = this;
		if( jQuery.isFunction( id ) ) {
			fn = id;
			id = 0;
			template = '';
		}
		else if ( jQuery.isFunction( template ) ) {
			fn = template;
			template = '';
		}
		
		id = id || this.id || 0;
		
		jQuery.rpc({
			method: "model.get",
			data: [
				this.__model__,
				id,
				template
			],
			callback: function( d ) {
				jQuery.each( d, function( i, n ) {
					self[ i ] = n;
				});
				if ( fn )
					fn.apply( self, [ d ] );
			}
		});
		return this;
	},
	
	/**
	 * Puts the data in the model to the server
	 * @param {Object} update
	 * @param {Object} fn
	 */
	put: function( update, fn ){
		if ( jQuery.isFunction( update ) ) {
			fn = update;
			update = false;
		}
		var self = this;
		var values = this.toArray();

		jQuery.rpc({
			method: "model.put",
			data: [ this.__model__, values ]
		}, function( d ) {
			jQuery.each( d, function( i, n ) {
				self[ i ] = n;
			});
			if ( update )
				self.updatePage( d );
			if ( fn )
				fn.apply( self, [ d ] );
		});
		return this;
	},
	
	/**
	 * Gets a form for the model, based on a template
	 * @param {Object} template the name of the template to use
	 * @param {Object} fn callback for when the form is received
	 * 
	 * TODO: Populate input values in the resulting form with the values for the current model.
	 */
	form: function( template, fn ) {
		var self = this;
		if( !fn ) {
			fn = template;
			template = "";
		}
		
		JSide.query( "{% form:" + this.__model__ + "|id:" + ( this.id || 0 ) + "|template:" + template + " %}", fn );
		return this;
	},
	
	/**
	 * Retrieves a list from the current model
	 * @param {Object} obj settings for the list
	 * @param {Object} fn callback for when the list has been received
	 */
	list: function( obj, fn ) {
		
		var options = jQuery.extend({
			name: "default", 
			filter: null, 
			page: null, 
			template: null, 
			appendTo: null
		}, obj);
		var opts = [];
		var list = options.name;
		options.name = null;
		
		var appendTo = options.appendTo || null;
		
		fn = fn || options.callback || null;
		
		var cb = ( appendTo ) ? function( d ) {
			jQuery( d ).appendTo( appendTo );
			if(fn)
				fn();
		} : fn;
		
		options.callback = null;
		options.appendTo = null;
		
		jQuery.each( options, function( i, n ){
			if( n !== null )
				opts.push( i + ":" + n );
		});
		
		JSide.query( "{% list:" + this.__model__ + "[" + list + "]|" + opts.join("|") + " %}", cb );
		
		return this;
	},
	
	/**
	 * Set single or multiple values
	 * @param {String || Object} data
	 * @param {Object} val
	 */
	set: function( data, val ){
		if( data && typeof data == "object" ){
			var self = this;
			jQuery.each( data, function( i, n ) {
				self[ i ] = n;
			});
		}
		else if( data && data.constructor == String && val !== undefined )
			this[ data ] = val;

		return this;
	},
	
	toArray: function(){
		var values = {};
		jQuery.each( this, function( i, n ) {
			if ( !jQuery.isFunction( n ) && i.indexOf( "__" ) !== 0 )
				values[ i ] = n;
		});
		return values;
	},
	
	/**
	 * Updates any fields in the current page to the values in 'values'
	 * @param {Object} values
	 */
	updatePage: function( values ) {
		JSide.model.updatePage(this.__model__, this.id || 0, values || this.toArray());
	}

});

/**
 * Defines some direct methods for the JSide.model object.
 */
jQuery.extend( JSide.model, {

	/**
	 * Quick access function to get a model from the server
	 * 
	 * @param {Object} key a string in the format model_name:id, for example 'contact:10'
	 * @param {Object} fn a callback to execute when the request finishes
	 */
	get: function( key, fn ) {
		var spl = key.split( ":" );
		return new JSide.model( spl[ 0 ], spl[ 1 ] * 1, fn );
	},
	
	/**
	 * **JSide.model.form**
	 * Quick access function to retreive a populated form for a model.
	 * @param {Object} obj
	 * @param {Object} fn
	 */
	form: function( obj, fn ) {
	
		var options = jQuery.extend({
			model: "", 
			template: "", 
			id: 0, 
			callback: function(){}
		}, obj);
	
		fn = fn || options.callback || null;
	
		var m = new JSide.model( options.model );
		m.set( "id", options.id );
	
		m.form( options.template, fn );
		
		return m;
	},
	
	/**
	 * **EXPERIMENTAL** Creates a global object from a model, 
	 * for instance JSide.model.make('contact', 'Contact') 
	 * would make the constructor "Contact" accessible for 
	 * 
	 * 	var harry = new Contact()
	 * 
	 * but I'm not sure this is memory efficient (what is, anyway? :)).
	 * 
	 * @param {Object} model
	 * @param {Object} name
	 */
	make: function( model, name ){
		window[ name ] = function( ){
			return new JSide.model( model );
		};
	},
	
	submitForm: function(form){
		var self = jQuery(form);
		
		if(JSide.validate(self)){
			self.trigger('beforesubmit', []);
			jQuery("input:submit", self).attr('disabled', 'disabled');
			JSide.model( self.attr('rel').split(".")[1] )
			     .set( self.getValues() )
				 .put(true,function(d){
						jQuery("input:submit", self).removeAttr('disabled');
						self.trigger('aftersubmit', [d]);
				 });
		}
	},

	select: function(link, model, uC){
		var input = jQuery('<input type="text"></input>').val(jQuery(link).text());
		jQuery(link).hide().after(input);
		input.autocomplete("none",{
			autoFill: false,
			matchContains: true,
			mustMatch: true,
			rpc: {
				model: model
			},
			parse: function(data){
				var ret = [];
				jQuery.each(data,function(i,n){
					ret.push({
						data: "<span rel=\"" + n.id + "\" class=\"pickmodel_text\">" + n.text + "</span><br />" + (n.extra ? '<span class="pickmodel_extra">' + n.extra + '</span>' : ''),
						value: n.text,
						result: n.text
					});
				});
				return ret;
			},
			formatItem: function(row, pos, total, term){
				return row;
			},
			formatResult: function(data, pos, total){
				return data.text;
			}
		}).result(function(data, a){
			var span = jQuery(a).filter("span.pickmodel_text");
			var d = span.attr("rel");
			jQuery("input."+uC).val(d);
			jQuery(link).text(span.text()).show();
			jQuery(this).hide();
			
		}).focus().blur(function(){
			input.remove();
			if(jQuery(link).show)
				jQuery(link).show();
		}).select();
		return false;
	},
	
	suggest: function(field, model){
		field.autocomplete("none",{
			autoFill: false,
			matchContains: true,
			rpc: {
				model: model
			},
			parse: function(data){
				var ret = [];
				jQuery.each(data,function(i,n){
					ret.push({
						data: "<span rel=\"" + n.id + "\" class=\"pickmodel_text\">" + n.text + "</span><br />" + (n.extra ? '<span class="pickmodel_extra">' + n.extra + '</span>' : ''),
						value: n.text,
						result: n.text
					});
				});
				return ret;
			},
			formatItem: function(row, pos, total, term){
				return row;
			},
			formatResult: function(data, pos, total){
				return data.text;
			}
		}).result(function(data, a){
			var span = jQuery(a).filter("span.pickmodel_text");
		}).focus();
		return false;
	},
	
	facelist: function(field, model, data){
		field.facelist(data || "none", {
			autoFill: true,
			matchContains: true,
			rpc: {
				model: model
			},
			parse: function(data){
				var ret = [];
				jQuery.each(data,function(i,n){
					ret.push({
						data: n.text,
						value: n.id,
						result: n.text
					});
				});
				return ret;
			},
			result_field: field.attr('name')
		});
	},
	
	updatePage: function( model, id, values, extra ){
		extra = extra || '';
		jQuery.each( values, function( i, n ) {
			if ( typeof n == "object" )
				JSide.model.updatePage( model, id, n, i + '.' );
			else
				jQuery( "span[rel=" + model + "(" + id + "):" + extra + i + "]" ).text( n );
		});
	}
});

JSide.layout = function ( name, fn ) {
	if ( !( this instanceof arguments.callee ) )
		return new arguments.callee( name, fn ); 
	this.__layout__ = name;
	if( fn )
		return this.get( fn );
	return this;
};

jQuery.extend( JSide.layout.prototype, {
	
	get: function( fn ) {
		JSide.query( "{% layout:" + this.__layout__ + " %}" ,fn );
		return this;
	}
	
});

JSide.widget = function ( name, args, fn ) {
	if ( !( this instanceof arguments.callee ) ) {
		return new arguments.callee( name, args, fn ); 
	}

	this.__args__ = args;
	if (name.constructor === Object) {
		var a = {};
		var self = this;
		jQuery.each(name, function(i, n){
			if (i == "name")
				self.__name__ = n;
			else if (i == "callback")
				fn = n;
			else
				a[i] = n;
		});
		this.__args__ = a;
	}
	else if (jQuery.isFunction(args)) {
		fn = args;
		this.__args__ = {};
	}
	else {
		this.__name__ = name;
		this.__args__ = args || {};
	}
	if( fn )
		return this.get( fn );
	return this;
};

jQuery.extend( JSide.widget.prototype, {
	
	get: function( fn ) {
		var a = [];
		
		jQuery.each( this.__args__, function( i, n ) {
			a.push( i + ":" + n );
		});
		var args = ( a.length ? "|" : "" ) + a.join( "|" );
		JSide.query( "{% widget:" + this.__name__ + args + " %}" ,fn );
		return this;
	}

});

JSide.loadScript = function ( name, args, fn ) {
	if ( !( this instanceof arguments.callee ) ) {
		return new arguments.callee( name, args, fn ); 
	}

	this.__args__ = args;
	
	if (name.constructor === Object) {
		var a = {};
		var self = this;
		jQuery.each( name, function( i, n ) {
			if (i == "name")
				self.__name__ = n;
			else if (i == "callback")
				fn = n;
			else
				a[i] = n;
		});
		this.__args__ = a;
	}
	else if (jQuery.isFunction(args)) {
		fn = args;
		this.__args__ = {};
	}
	else {
		this.__name__ = name;
		this.__args__ = args || {};
	}
	return this.get( fn || null );
};

jQuery.extend( JSide.loadScript.prototype, {
	
	get: function( fn ) {
		var a = [];
		
		jQuery.each( this.__args__, function( i, n ) {
			a.push( i + ":" + n );
		});
		var args = ( a.length ? "|" : "" ) + a.join( "|" );
		JSide.query( "{% script:" + this.__name__ + args + " %}" ,function(d){
			EXECUTE_ONLOAD(d);
			if (fn) fn(d);
		});
		return this;
	},
	
	poll: function( interval, fn ) {
		// TODO: Implement.
	}

});

JSide.go = function(hash, redo) {
	if( document.location.hash == JSide.currenthash && redo) JSide.loadHash(hash);
	document.location.hash = hash;
};

/**
 * Validates all input fields in a form.
 */
JSide.validate = function(form){
	var retVal = [];
	return true;
	jQuery(':input',form).each(function(i,n){
		var self = jQuery(this);
		if(self.is('.required') && self.val() == '') retVal = JSide.displayValidation(self, 'Required field');
	});

	var ret = form.trigger('validation',retVal);
	return ret || false;

// ret = null
}

JSide.displayValidation = function(obj, msg) {
	
	return false;
}