/**
 * API - root namespace for all API calls
 * @namespace
 */
var API = ( function ( ) {

	// Stores all the listener functions, against the method name, indexed by ID
	// eg. listeners['some.method.name'][<id>] => function ( ) { ... }
	var listeners = {};

	// The base of the api structure
	// Holds all API method calls, and the 'add' function
	var api = {
		 // The only required parameter is the method name
		 // You can list method arguments as further parameters
		 // eg. API.add ( 'some.method.name', ... )
		add : function ( name /*, argument, argument, ... */ ) {
			addMethod.apply ( null, arguments );
		}
	};

	// Connection to server - is a Request object
	var connection;

	// This turns a plain text response into JSON
	var parseResponse = function ( rsp ) {
		// TO DO - make this a little less vulnerable
		return eval ( "(" + rsp + ")" );
	}

	// This is the business bit of the code
	var doCall = function ( method, data, callback ) {
		// Add method name into data
		data.method = method;

		var finish = function ( response ) {
			if ( typeof callback == "function" )
				callback ( response, data );
			if ( listeners[method] )
				for ( var l in listeners[method] )
					listeners[method][l] ( response, data );
		}

		if ( listeners["pre_"+method] ) {
			for ( var pid in listeners["pre_"+method] ) {
				var rsp = listeners["pre_"+method][pid](data);
				if ( rsp !== undefined ) {
					finish ( rsp );
					return;
				}
			}
		}

		// Set up global connection, if not already established
		if ( !connection ) connection = new Request ( "/api" );

		// Send the data to the API
		connection.send ( data, function ( response, success, headers ) {
			if ( success ) {
				response = parseResponse ( response );
				// TO DO - should we deal with API failures here?
				finish ( response );
			} else {
				// TO DO - work out what to do with HTTP failures
			}
		} );
	}

	// The real version of api.add
	// Same argument situation as above
	var addMethod = function ( name /*, argument, argument, ... */ ) {
		// Convert function arguments into real array
		var args = Array.prototype.slice.call ( arguments, 0 );
		name = args.shift();

		// Make sure that every argument is a string
		for ( var i = 0; i < args.length; i ++ )
			args[i] = "" + args[i];

		// Break up the name on ',' to build the API namespacing
		var parts = name.split(".");
		// Assign the base parent to the API object
		var parent = api;
		while ( parts.length ) {
			var part = parts.shift();
			if ( parts.length ) {
				// If there are more parts after this, then just extend namespace
				if ( !parent[part] )
					parent[part] = {}
				parent = parent[part];
			} else {
				// Otherwise, we need to make a function available
				var fn = function ( a, cb ) {
					var data = {};
					// This function either takes a data object (and possibly a callback)...
					// fn ( { parameter : value, ... }, callback )
					if ( Object.prototype.toString.call(a) == "[object Object]" ) {
						// Pull out the appropriate variables from the data object...
						for ( var i = 0; i < args.length; i ++ ) {
							// ...and throw an error if that parameter isn't set
							if ( a[args[i]] === undefined )
								throw "Method " + name + " requires argument '" + args[i] + '"';
							data[args[i]] = a[args[i]];
						}
					// ... or it takes a sequential list of parameters (and possibly a callback)
					} else {
						// Turn the function arguments into a real array
						a = Array.prototype.slice.call ( arguments, 0 );
						// And if a callback is present, remove it
						cb = ( typeof a[a.length-1] == "function" ) ? a.pop() : null;
						// If we've not been given enough parameters, throw an error
						if ( a.length < args.length )
							throw "Method " + name + " requires " + args.length + " argument(s)";
						for ( var i = 0; i < args.length; i ++ )
							data[args[i]] = a[i];
					}
					doCall ( name, data, cb );
				}
				if ( parent[part] ) {
					// This function is already part of a namespace for another method
					// so we need to recreate it on this function
					// Hopefully the namespace doesn't clash with (args|listen|unlisten)!
					for ( var i in parent[part] ) {
						fn[i] = parent[part][i];
					}
				}
				// The args function can be called on an API method to get the required parameters
				fn.args = function ( ) {
					return args.slice(0);
				}
				// You can catch an API call, and potentially hack the call...
				fn.hijack = function ( fn ) {
					if ( typeof fn != "function" )
						return null;

					if ( !listeners["pre_"+name] ) listeners["pre_"+name] = {};
					var id = (""+Math.random()).substr(2);
					listeners["pre_"+name][id] = fn;
					return id;
				}
				// You can listen to an API method, if you want to do something every time it's called
				fn.listen = function ( fn ) {
					if ( typeof fn != "function" )
						return null;

					if ( !listeners[name] ) listeners[name] = {};
					var id = (""+Math.random()).substr(2);
					listeners[name][id] = fn;
					return id;
				}
				// And inversely you can unlisten, if you're fed up with all the notifications
				fn.unlisten = function ( id ) {
					if ( listeners[name] )
						delete listeners[name][id];
				}
				// Finally, assign the function into the namespace
				parent[part] = fn;
			}
		}
	}

	// set up the various standard API calls
	addMethod ( 'smokescreen.mission.start', 'mission_index' );
	addMethod ( 'smokescreen.mission.end', 'mission_index' );
	addMethod ( 'smokescreen.task.start', 'mission_index', 'task_index' );
	addMethod ( 'smokescreen.task.end', 'mission_index', 'task_index' );
	addMethod ( 'smokescreen.action.do', 'action_hash' );

	return api;
} )();
