/*
 JUG library - Client interface

 Copyright (c) 2010, Simon Wiseman
 
 This software is provided 'as-is', without any express or implied
 warranty. In no event will the authors be held liable for any damages
 arising from the use of this software.
 
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it
 freely, subject to the following restrictions:
 
    1. The origin of this software must not be misrepresented; you must not
    claim that you wrote the original software. If you use this software
    in a product, an acknowledgment in the product documentation would be
    appreciated but is not required.
 
    2. Altered source versions must be plainly marked as such, and must not be
    misrepresented as being the original software.
 
    3. This notice may not be removed or altered from any source distribution.
*/
    
//************************************************************************************************************
// The JUG Client interface for Javascript allows applications to call methods of services running on a server
//************************************************************************************************************

//==============================================================================================================
//	SessionExpiry - class representing a session-expired exception
//		::session	boolean whose existence indicates that this is a SessionExpiry
//==============================================================================================================

// class SessionExpiry - thrown as an exception after handling server calls when the session has expired and been recovered
function SessionExpiry()
{
}

//==============================================================================================================
//	JugShowException - function for displaying an exception in an alert dialog
//==============================================================================================================

// Function for alerting user to an exception
function JugShowException( 
	e		// the exception that is to be shown 
	)
{
	if( e instanceof SessionExpiry )
	{
		// The exception is a session-expiry so no need to display an alert
	}
	else if( e.lineNumber == undefined )
	{
		alert( "Exception: " + e.message + " (" + e.FileName + "#" + e.line + ")" );
	}
	else
	{
		alert( "Exception: " + e.message + " (" + e.FileName + "#" + e.lineNumber + ")" );
	}
}

//==============================================================================================================
//	ServerRequest - class for making service calls
//		::call - call a method of the service, passing a request object and receiving a response object
//		::callXD - call a method of the service, which can be in another domain, asynchronously
//		::callAsync - call a method of the service asynchronously
//==============================================================================================================

// ServerRequest constructor
function ServerRequest( 
	url, 				// address to which requests are posted
	onSessionExpiry 	// optional function that is called if the session expires - by default an exception is thrown
	)
{
	this.url = url;
	this.onSessionExpiry = onSessionExpiry;
}

// call a method of the service, passing a request object and receiving a response object
ServerRequest.prototype.call = function( 
	actionName, 			// name of action to invoke
	params, 				// object giving parameters of the action
	sessionRecoveryAction 	// optional function that is called after a session is recovered
	// RETURNS					object giving any result
	// EXCEPTION				thrown if server cannot be reached or action on server throws an exception
	)
{
	var responseText;	// set to the text of the response from the server
	try{
		// form up the request as an XML string representing the action and its parameters
		var request = { action:actionName, param:params };
		var requestText = JugXMLToText( JugObjectAsXML( request ) );
		
		// post the request to the server
		var http = new HTTP;
		responseText = http.Post( this.url, requestText );
	}
	catch( ex )
	{
		// generally failed to communicate
		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );
	}
	
	// check if we got an empty response, which usually means the PHP file exists but is failing to respond as a JUG server
	if( responseText == "" )
	{
		JugLog( "Request=" + requestText + "; No response" );
		throw new Error( "No response calling " + this.url + " with " + actionName );
	}

	// display the request and results on the console for debugging purposes
	JugLog( "Request=" + requestText + "; Result=" + responseText );

	// form up the response as an object
	var responseObject;	// set to the object received from the server
	try{
		var responseXML = JugMakeXMLDoc( responseText );
		responseObject = JugXMLAsObject( responseXML );
	}
	catch( e )
	{
		// failed to construct the object - either XML is invalid or does not describe an object
		throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );
	}
	if( responseObject.outcome == "success" )
	{
		// call succeeded
		return responseObject.result;	// return the result object
	}
	else if( responseObject.outcome == "exception" )
	{
		// call failed
		throw new Error( "Error returned calling " + this.url + " with " + actionName + ": " + responseObject.error );
	}
	else if( responseObject.outcome == "expired" )
	{
		// session has expired
		if( this.onSessionExpiry && sessionRecoveryAction )
		{
			this.onSessionExpiry( sessionRecoveryAction );
			throw new SessionExpiry();	// special exception that is ignored by JugShowException
		}
		else
		{
			throw new Error( "Session expired doing " + actionName );
		}
	}
	else
	{
		// unexpected response
		throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );
	}
}

ServerRequest.prototype.callXD = function( 
	actionName, 		// string giving name of action to invoke
	params, 			// object giving any parameters required by the action
	resultCallback 		// function that is called with the result of the server call once it is retrieved
	)
{
	try{
		// The cross-domain call is made by loading a script into a dynamically created script tag.
		// The source URL of the script tag passes the parameters of the request and the id given to the script tag.
		// The script tag is given a callback function as an attribute. The server returns a javascript function that calls this function
		//	with the text representation of the call's result, using the script tag's id to find it. 

		// form up the request as an XML string representing the action and its parameters
		var request = { action:actionName, param:params };
		var requestText = JugXMLToText( JugObjectAsXML( request ) );
		
		// allocate a unique ID for the script tag
		var id = JugGetUniqueId( "SCRIPT" );

		// Function called when cross-domain result is retreived
		function callback( 
			responseText 	// XML text description of response object
			)
		{
			// display the request on the console for debugging purposes
			JugLog( "XD Response " + id + " = " + responseText );
		
			// form up the response as an object
			var responseObject;	// set to the object received from the server
			try{
				var responseXML = JugMakeXMLDoc( responseText );
				responseObject = JugXMLAsObject( responseXML );
			}
			catch( e )
			{
				// failed to construct the object - either XML is invalid or does not describe an object
				throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );
			}
			if( responseObject.outcome == "success" )
			{
				// call succeeded
				resultCallback( responseObject.result );	// deliver the result object
			}
			else if( responseObject.outcome == "exception" )
			{
				// call failed
				throw new Error( "Error returned from cross domain call: " + responseObject.error );
			}
			else
			{
				// unexpected response
				throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );
			}
		
			// Delete the script tag
			var script = JugElem( id );
			script.parentNode.removeChild( script );
		}

		// pass the request as the source of a SCRIPT tag
		var scriptTag = document.createElement('SCRIPT');
		scriptTag.type = 'text/javascript';
		scriptTag.src = this.url + "?request=" + encodeURIComponent( requestText ) + "&id=" + id;
		scriptTag.id = id;
		scriptTag.callback = callback;
		document.body.appendChild( scriptTag );	// start to load the script and so make the request
		
		// display the request on the console for debugging purposes
		JugLog( "XD Request=" + requestText );
	}
	catch( ex )
	{
		// generally failed to communicate
		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );
	}
}

ServerRequest.prototype.callAsync = function( 
	actionName, 		// string giving name of action to invoke
	params, 			// object giving any parameters required by the action
	resultCallback 		// function that is called with the result of the server call once it is retrieved
	)
{
	var responseText;	// set to the text of the response from the server
	try{
		// form up the request as an XML string representing the action and its parameters
		var request = { action:actionName, param:params };
		var requestText = JugXMLToText( JugObjectAsXML( request ) );
		
		// Function called when async result is retreived
		function completion( responseText )
		{
			if( responseText )
			{
				// POST completed successfully
				// display the request on the console for debugging purposes
				JugLog( "Async Response = " + responseText );

				// form up the response as an object
				var responseObject;	// set to the object received from the server
				try{
					var responseXML = JugMakeXMLDoc( responseText );
					responseObject = JugXMLAsObject( responseXML );
				}
				catch( e )
				{
					// failed to construct the object - either XML is invalid or does not describe an object
					throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );
				}
				if( responseObject.outcome == "success" )
				{
					// call succeeded
					if( resultCallback )
					{
						resultCallback( responseObject.result );	// deliver the result object
					}
				}
				else if( responseObject.outcome == "exception" )
				{
					// call failed
					throw new Error( "Error returned from async call: " + responseObject.error );
				}
				else
				{
					// unexpected response
					throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );
				}
			}
			else
			{
				// POST failed
				throw new Error( "Post of async call failed" );
			}
		}

		// post the request to the server
		this.asyncHttp = new HTTP;	// kill off any existing async post
		responseText = this.asyncHttp.PostAsync( this.url, requestText, completion );
		
		// display the request and results on the console for debugging purposes
		JugLog( "Async request = " + requestText );
	}
	catch( ex )
	{
		// generally failed to communicate
		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );
	}
}

