var API = ( function ( ) {

	DEFAULT_OPTIONS = {
		key : "-",
		onStart : function ( ) { },
		onComplete : function ( obj ) { },
		onError : function ( err ) { alert ( "Error (" + err.code + "): " + err.message ); },
		override_cache : true //false
	};

	// this is the root for API XHR requests... we're using purely JSON right now
	function getPathRoot ( ) {
		return ""; //"/api/" + API.options.key + "/json";
	}

	// cache object - store XHR responses, as well as function internals
	var Cache = ( function ( ) {

		// this is ultimately where we're putting *everything*!
		var cache = { };

		function getCache ( k, c ) {
			if ( !c ) return undefined;
			var key = k.replace(/(^\/|\/$)/,'').split('/');
			k = key.shift();
			if ( key.length ) {
				return getCache ( key.join('/'), c[k] );
			} else {
				return c[k];
			}
		}

		function setCache ( k, v, c ) {
			var key = k.replace(/(^\/|\/$)/,'').split('/');
			k = key.shift();
			if ( key.length ) {
				if ( !c[k] ) c[k] = {};
				return setCache ( key.join('/'), v, c[k] );
			} else {
				var o = c[k];
				c[k] = v;
				return o;
			}
		}

		function dropCache ( k, c ) {
			var key = ( k ? k.replace(/(^\/|\/$)/g,'').split('/') : [] );
			k = key.shift();
			if ( key.length ) {
				if ( !c[k] ) return false;
				return dropCache ( key.join('/'), c[k] );
			} else {
				if ( !k ) {
					delete c;
					c = {};
				} else {
					delete c[k];
				}
				return true;
			}
		}

		// return a simplified reference for the outside world
		return {
			get : function ( key ) {
				return getCache ( key, cache );
			},
			set : function ( key, value ) {
				return setCache ( key, value, cache );
			},
			drop : function ( key ) {
				return dropCache ( key, cache );
			}
		};
	} )();

	var XHR2 = ( function ( ) {

		var requestpath;

	    var createXHRObj = ( function ( ) {
	        if ( typeof XMLHttpRequest != "undefined" ) {
	            return function ( ) {
	            	return new XMLHttpRequest();
	            }
	        } else if ( typeof ActiveXObject != "undefined" ) {
            	try {
              		return function ( ) {
              			return new ActiveXObject("Microsoft.XMLHTTP");
              		}
              	} catch ( e ) {
              		return function ( ) {
              			return null;
              		}
              	}
	        } else {
	        	return function ( ) {
	        		return null;
	        	}
	        }
	    } )();

		var respond = function ( response, cb_function, err_function ) {
			try {
				eval ( "var response = " + response);
				var obj = $("axjaxLogs");
				if (response["timeLog"]) {
					if (obj) { obj.innerHTML += response["timeLog"]; }
					if (response["sql"].length) {
						print_r( response["sql"]);
					}
					delete response["timeLog"];
				}
				if (response['debugConsole']) {
					if (response['debugConsole'].length > 0) {
						print_r(response['debugConsole']);
					}
					delete response["debugConsole"];
				}

			} catch ( e ) {
				if ( typeof err_function == "function" ) {
					return err_function ( e )
				}
				return false;
			}
			var emsg = response.data['emsg'];
			var ul = $$("div.pageError").getElement("ul")[0];
			for (var i in emsg) {
				if (parseInt(i) == i) {
					MessageAPI.addErrorMessage(emsg[i]["htmlID"], emsg[i]["message"]);
				}
			}
			delete response.data['emsg'];
			if ( response.status == 200 ) {
				if (response && response['login']) {
					document.location = "/";
					return true;
				}
				if ( typeof cb_function == "function" ) {
					if (response.data) {
						return cb_function ( response.data );
					}
				} else if ( typeof API.options.default_cb == "function" ) {
					return API.options.default_cb ( response.data );
				}
				return true;
			} else {
				var err = { message : response.message, code : response.status };
				if ( typeof err_function == "function" ) {
					return err_function ( err );
				}
				return false;
			}
		}

		var escape = function (value) {
//			print_r($type(value));
			var ret = value;
			if ($type(value) == "object") {

				for (var i in value) {
					ret[i] = escape(value[i]);
				}
			} else if ($type(value) == "string") {
				ret = value.split("\\").join("\\\\");
			}
			return ret;
		}

		var request = function ( path, body, options, response_function ) {
			var arr = new Array();
			if ($("start_debug").value == 1) {
				arr[arr.length] = "start_debug=1";
			}

			if ($("start_profile").value == 1) {
				arr[arr.length] = "start_profile=1";
			}

			path += (arr.length) ? "?" + arr.join("&") : "";

			if ( !response_function ) response_function = respond;
			var o = options || {};
			for ( field in API.options ) {
				o[field] = o[field] || API.options[field];
			}
			var cb_function = o.onComplete;
			var err_function = o.onError;
			var oc = o.override_cache;
			var cached = oc ? undefined : Cache.get ( path );
			if ( cached != undefined ) {
				return response_function ( cached, cb_function, err_function );
			}

			try {
				( function ( ) {
					var req = createXHRObj();
					req.onreadystatechange = function ( ) {
						if ( req.readyState == 4 ) {
							if ( req.status == 200 ) {
								var encodedResponse = req.responseText.trim();
								Cache.set ( path, req.responseText );
								if ( path.substring( 7, 16 ) == "fragments" || encodedResponse.substring( 0, 1 ) == "{" ) {
									return response_function ( encodedResponse.replace(/(^\s|\s$)/g,''), cb_function, err_function );
								} else {
									alert(encodedResponse.replace(/(\<b\>|\<\/b\>|\<br \/\>)/g,''));
									return respond ( '{ "status" : "' + req.status + '", "message" : "' + req.statusText + '",  "data" : { } }', cb_function, err_function );
								}
							} else {
								var response = '{ "status" : "' + req.status + '", "message" : "' + req.statusText + '", "data" : null }';
								return respond ( response, cb_function, err_function );
							}
							return false;
						}
					}
					if (securityKey.length > 0) {
						if (path.contains("?")) {
							path += "&k=" + securityKey;
						} else {
							path += "?k=" + securityKey;
						}
					}

					body = toJSON ( {body : body, options : {"showSql" : o["showSQLLog"], "showTimeLog" : ($("axjaxLogs") ? true : false)} } );
					req.open ( "POST", path, true );
					req.setRequestHeader("Method", "POST " + path + " HTTP/1.1");
					req.setRequestHeader("Content-Type", "text/json; charset=iso-8859-1");
					req.setRequestHeader("Content-Length", body.length);
					req.send ( body );
				} ) ();
			} catch ( e ) {
				if ( typeof err_function == "function" ) {
					return err_function ( e );
				} else {
					print_r(e);
				}
				return false;
			}
			return true;
		}

		return {

			request : function ( path, body, options ) {
				return request ( getPathRoot() + path, body, options );
			}
		};

	} )();

	// returns a function to make a basic API XHR request
	function Method ( path, args, hasBody, options ) {
		options = options || {};
		return function ( ) {
			var p = path;
			var a = args;
			var pa = [];
			for ( var i = 0; i < a; i ++ ) {
				p += "/" + arguments[i];
				pa[i] = arguments[i];
			}
			var b = null;
			if ( hasBody ) {
				b = arguments[args];
				a += 1;
			}
			var o = arguments[a] || {};
			for ( field in API.options ) {
				o[field] = o[field] || options[field];
			}
			var ok = (o.onStart) ? o.onStart.apply(o.onStart, pa) : {};
			var e = false;
			if ( ok === false ) {
				e = { "code" : 500, "message" : "Unknown error" };
			} else if ( typeof ok == "object" && ok.message && ok.code ) {
				e = ok;
			}
			if ( e ) {
				return o.onError ( e );
			}
			return XHR2.request ( p, b, o );
		}
	}

	// returns a Method collection
	// r - root path
	// m - method list
	// o - call options
	function Methods ( r, m, o ) {
		var methods = {};
		// if m is an array, use the listed method names, and default args
		if ( m instanceof Array ) {
			for ( var i = 0, mn; mn = m[i]; i ++ ) {
				methods[mn] = Method ( r + "/" + mn, 0, false, o );
				methods[mn].__root__ = r;
			}
		// otherwise, m is a hash, listing method names and options
		} else {
			for ( mn in m ) {
				if ( typeof m[mn] == "function" ) {
					// if the specification is a function, use it as such...
					methods[mn] = m[mn];
				} else {
					// else, if the specification is an object, extract the
					// parts, otherwise, assume it's an args definition
					var isObj = ( typeof m[mn] == "object" );
					var args = ( isObj ? m[mn].args : m[mn] );
					var body = ( isObj ? m[mn].body : false );
					var options = ( isObj ? m[mn].options || {} : {} );
					o = o || {};
					var ao = API ? API.options : DEFAULT_OPTIONS;
					for ( field in ao || DEFAULT_OPTIONS ) {
						o[field] = o[field] || options[field];
					}
					methods[mn] = Method ( r + "/" + mn, args, body, o );
				}
				methods[mn].__root__ = r;
			}
		}
		return methods;
	}

	// returns a set of basic API XHR requests
	// namespace 		- the base name of the method set; doesn't necessarily
	//						have to be the same as the method set name
	// methods 			- an array of method names, or a hash of method names
	//						and function options
	// argument_count 	- the number of arguments this method set takes; for
	//						example, a method set might have an associated id
	// default_options	- the default option set to pass through with this
	//						method set
	function MethodSet ( namespace, methods, argument_count, default_options ) {
		var f = function ( root, options ) {
			var key = root.replace(/\/+$/,'') + ":ms";
			var m = Cache.get ( key );
			if ( !m ) {
				m = Methods ( root, methods, options );
				m.dropCache = function ( mName ) {
					Cache.drop ( getPathRoot() + root + ( mName ? "/" + mName : "" ) );
				}
				Cache.set ( key, m );
			}
			return m;
		}
		argument_count = argument_count || 0;
		var b = "/" + namespace;
		var rf = function ( ) {
			var r = ( arguments.callee.__root__ || "" ) + b;
			for ( var i = 0; i < argument_count; i ++ ) {
				r += "/" + arguments[i];
			}
			var options = {};
			var option_set = [
				default_options || {},
				arguments[argument_count] || {}
			];
			for ( var i = 0, os; os = option_set[i]; i++ ){
				for ( var property in os ){
					options[property] = os[property];
				}
			}
			return f ( r, options );
		}
		if ( !argument_count ) {
			return rf();
		} else {
			return rf;
		}
	}

	// this is the actual API reference
	return {

		// function to allow us to make arbitrary requests
		__call : function ( path, cb, err, oc ) {
			return doRequest ( path, cb, err, oc );
		},

		// generic XHR requests
		request : XHR2.request,

		// JSON encoding
		toJSON : toJSON,

		// config options
		options : DEFAULT_OPTIONS,

		// if you want to drop all API responses, call this method
		dropCache : function ( ) {
			Cache.drop( getPathRoot() );
		},

		// API.getFragment(<fragment>,<page>,...)
		// -> /api/<key>/fragments/<fragment>/page<page>
		// where <fragment> is: <controller>/[<id>/]<fragment id>
		getFragment : function ( fragment, page, options, data ) {
			XHR2.fragment ( fragment, page, options, data );
		},

		// API.advisor(<id>).<method>()
		// -> /api/<key>/<output>/advisors/<id>/<method>
		advisor : MethodSet ( "advisors", [
			"profile"
		], 1 ),

		// API.client(<id>).<method>()
		// -> /api/<key>/<output>/clients/<id>/<method>
		client : MethodSet ( "clients", [
			"profile"
		], 1 ),

		// API.pm(<id>).<method>()
		// -> /api/<key>/<output>/pms/<id>/<method>
		pm : MethodSet ( "pms", [
			"profile"
		], 1 ),

		search : MethodSet ( "search", {
			"advisors" : 1,
			"clients" : 1,
			"pms" : 1
		} )

	};

} )();
