(function($) {
	$.extend({
		jsonRPC: {
			// RPC Version Number
			version: '2.0',

			// End point URL, sets default in requests if not
			// specified with the request call
			endPoint: EMEN2WEBROOT+'/jsonrpc/',

			// Default namespace for methods
			namespace: null,

			// Setup the endPoint and namespace params
			setup: function(params) {
				this._validateConfigParams(params);
				this.endPoint = params.endPoint;
				this.namespace = params.namespace;
				return this;
			},

			// Convenience wrapper method to allow you to temporarily set a config parameter
			// (endPoint or namespace) and ensure it gets set back to what it was before
			withOptions: function(params, callback) {
				this._validateConfigParams(params);
				// No point in running if there isn't a callback received to run
				if(typeof(callback) === 'undefined') throw("No callback specified");

				origParams = {endPoint: this.endPoint, namespace: this.namespace};
				this.setup(params);
				callback.call(this);
				this.setup(origParams);
			},

			// Performas a single RPC request
			request: function(method, params, callbacks, url) {
				// Validate or method arguments
				this._validateRequestMethod(method);
				this._validateRequestParams(params);
				this._validateRequestCallbacks(callbacks);

				// Perform the actual request
				return this._doRequest(JSON.stringify(this._requestDataObj(method, params, 1)),
					callbacks,
					url);

				return true;
			},

			call: function(method, params, callback, errback) {
				callback = callback || function(json){console.log("JSON RPC Result:", json)};
				errback = errback || function(error){console.log("Error:", error.message)};
				return this.request(method, params, {success:function(u){callback(u.result)}, error: function(u){errback(u)}});
			},

			// Submits multiple requests
			// Takes an array of objects that contain a method and params
			batchRequest: function(requests, callbacks, url) {
				// Ensure our requests come in as an array
				if(!$.isArray(requests) || requests.length === 0) {
					throw("Invalid requests supplied for jsonRPC batchRequest. Must be an array object that contains at least a method attribute");
				}

				// Make sure each of our request objects are valid
				var self = this;
				$.each(requests, function(i, req) {
					self._validateRequestMethod(req.method);
					self._validateRequestParams(req.params);
				});
				this._validateRequestCallbacks(callbacks);

				var data = [], request;

				// Prepare our request object
				for(var i = 0; i<requests.length; i++) {
					request = requests[i];
					data.push(this._requestDataObj(request.method, request.params, i+1));
				}

				this._doRequest(JSON.stringify(data), callbacks, url);
			},

			// Validate a params hash
			_validateConfigParams: function(params) {
				if(typeof(params) === 'undefined') {
					throw("No params specified");
				}
				else {
					if(params.endPoint && typeof(params.endPoint) !== 'string'){
						throw("endPoint must be a string");
					}
					if(params.namespace && typeof(params.namespace) !== 'string'){
						throw("namespace must be a string");
					}
				}
			},

			// Request method must be a string
			_validateRequestMethod: function(method) {
				if(typeof(method) !== 'string') {
					throw("Invalid method supplied for jsonRPC request")
				}
				return true;
			},

			// Validate request params.  Must be a) empty, b) an object (e.g. {}), or c) an array
			_validateRequestParams: function(params) {
				if(!(params === null ||
					typeof(params) === 'undefined' ||
					typeof(params) === 'object' ||
					$.isArray(params))) {
					throw("Invalid params supplied for jsonRPC request. It must be empty, an object or an array.");
			  	}
			  	return true;
			},

			_validateRequestCallbacks: function(callbacks) {
				// Make sure callbacks are either empty or a function
				if(typeof(callbacks) !== 'undefined') {
					if(typeof(callbacks.error) !== 'undefined' && typeof(callbacks.error) !== 'function') {
						throw("Invalid error callback supplied for jsonRPC request");
					}
					if(typeof(callbacks.success) !== 'undefined' && typeof(callbacks.success) !== 'function') {
						throw("Invalid success callback supplied for jsonRPC request");
					}
				}
				return true;
			},

			// Internal method used for generic ajax requests
			_doRequest: function(data, callbacks, url) {
				var self = this;
				return $.ajax({
					type: 'POST',
					dataType: 'json',
					contentType: 'application/json',
					url: this._requestUrl(url),
					data: data,
					cache: false,
					processData: false,
					error: function(jqXHR, textStatus, errorThrown) {
						self._requestError(callbacks, jqXHR, textStatus, errorThrown);
					},
					success: function(json, textStatus, jqXHR) {
						self._requestSuccess(callbacks, json);
					}
				});
			},

			// Determines the appropriate request URL to call for a request
			_requestUrl: function(url) {
				url = url || this.endPoint;
				return url + '?tm=' + new Date().getTime()
			},

			// Creates an RPC suitable request object
			_requestDataObj: function(method, params, id) {
				return {
					jsonrpc: this.version,
					method: this.namespace ? this.namespace +'.'+ method : method,
					params: params,
					id: id
				}
			},

			// Handles calling of error callback function
			_requestError: function(callbacks, jqXHR, textStatus, errorThrown) {
				if (textStatus == 'abort') {
					// ignore aborted requests.
					// console.log("Aborted JSONRPC request");
					return
				}
				if (typeof(callbacks) !== 'undefined' && typeof(callbacks.error) === 'function') {
					callbacks.error(this._response(jqXHR.responseText));
				}
			},

			// Handles calling of RPC success, calls error callback
			// if the response contains an error
			// TODO: Handle error checking for batch requests
			_requestSuccess: function(callbacks, json) {
				var response = this._response(json);

				// If we've encountered an error in the response, trigger the error callback if it exists
				if(response.error && typeof(callbacks) !=='undefined' && typeof(callbacks.error) !== 'undefined') {
					callbacks.error(response);
					return;
				}

				// Otherwise, successful request, run the success request if it exists
				if(typeof(callbacks) !== 'undefined' && typeof(callbacks.success) !== 'undefined') {
					callbacks.success ? callbacks.success(response) : null;
				}
			},

			// Returns a generic RPC 2.0 compatible response object
			_response: function(json) {
				if (typeof(json) === 'undefined') {
					return {
						error: 'Internal server error',
						version: '2.0'
					}
				}
			
				try {
					if (typeof(json) == 'string') {json = jQuery.parseJSON(json)}				
					// if (($.isArray(json) && json.length > 0 && json[0].jsonrpc != '2.0') || 
					//     (!$.isArray(json) && json.jsonrpc != '2.0')) {
					//   throw 'Version error';
					// }
					return json;
				} catch (e) {
					return {
						error: 'Internal server error: ' + e,
						version: '2.0'
					}
				}
			}
		}
	});
})(jQuery);
