/**
 * JSON RPC client
 * @param string url The location of the webservice
 */
PBJSONRPCClient = function (url, methods, asyncMethods)
	{
		this.url = url;

		if (methods) {
			var methods = $A(methods);

			this.genericMethod = function ()
			{
				return this.call.apply(this, arguments);
			};

			methods.each((function (method)
			{
				this[method] = this.genericMethod.curry(method);
			}).bind(this));
		}

		if (asyncMethods) {
			var asyncMethods = $A(asyncMethods);

			this.genericASyncMethod = function (method, callbackFunction)
			{
				// method, callback should be reversed before calling 'callAsync'
				var args = $A(arguments);
				args.splice(0, 2, callbackFunction, method);
				return this.callAsync.apply(this, args);
			};

			asyncMethods.each((function (aSyncMethod)
			{
				this[aSyncMethod] = this.genericASyncMethod.curry(aSyncMethod);
			}).bind(this));
		}
	};

/**
 * Initiates a remote procedure call to the specified method
 * @param string method The method to invoke
 * @param mixed arg Every next argument will be passed as an argument to the invoked method
 */
PBJSONRPCClient.prototype.realCall = function (aSync, callbackFunction, method)
	{
		aSync = Object.isUndefined(aSync) ? false : aSync;
		var args = $A(arguments).slice(3);

		// Create the request object
		var content = {
			'jsonrpc': '2.0',
			'method': method,
			'params': args,
			'id': new Date().getTime().toString()
		};

		// Initiate the request and return the result
		var response;
		var callbackCalled = false;
		new Ajax.Request(this.url, {
			'asynchronous': aSync,
			'contentType': 'application/json',
			'method': 'post',
			'postBody': Object.toJSON(content),
			'onSuccess': function (transport) {
				if (transport.responseJSON) {
					response = transport.responseJSON;
				} else {
					response = transport.responseText.evalJSON();
				}

				if (Object.isUndefined(response.result)) {
					callbackFunction(null);
				} else {
					callbackFunction(response.result);
				}
				callbackCalled = true;
			},
			'onFailure': function () {
				callbackFunction(null);
				callbackCalled = true;
			}
		});

		if (!aSync && !callbackCalled) {
			callbackFunction(null);
		}
	};

PBJSONRPCClient.prototype.callAsync = function (callbackFunction, method)
	{
		if (Object.isUndefined(callbackFunction) || !Object.isFunction(callbackFunction)) {
			// 'blind' call (fire-and-forget method)
			callbackFunction = function (result) {};
		}

		var args = $A(arguments);
		args.splice(0, 1, true, callbackFunction);

		PBJSONRPCClient.prototype.realCall.apply(this, args);
	};

PBJSONRPCClient.prototype.call = function (method)
	{
		var finalResult;

		var callbackFunction = function (result)
			{
				finalResult = result;
			};

		var args = $A(arguments);
		args.splice(0, 0, false, callbackFunction);

		PBJSONRPCClient.prototype.realCall.apply(this, args);

		return finalResult;
	};
