/**
 * @module Net
 * @author Naren
 */

/** Wrapper around AjaxConnection specifically for talking to any Simulate API
 * @class APIConnection
 * @constructor
 * @extends AjaxConnection
 * @requires AjaxConnection, F, F.APIUtils
 * @param {String} url API location
 * @param {String|Array|Object|HTMLElem|Mixed} params  URLParams to be included as part of the url string
 * @param {Object} settings Additional settings to be passed to ajax conn object. Use to define error handlers
 */
var APIConnection = function(url, params, settings){
	if(!url){
		throw new Error("APIConnection: no url provided");
	}
	
	var dataType = "json"; //Api returns json unless diff target file specified
	var defaults = {
		/** Return this file on success
		 * @config target
		 * @type String
		 */
		target: ""
	}
	$.extend(defaults, params);
	
	if(defaults.target){
		var absURL = "/" + F.APIUtils.userPath + "/" + F.APIUtils.simPath + "/" + params.target;
		params.target = encodeURIComponent(absURL);
		
		dataType = "text"; //We don't know what target file content will be any longer
	}
	url += "?" + F.makeQueryString(params, {encode:false}); // Make escaped target a part of url
	
	//TODO: Find someway to specify default error handlers
	var defaultErrorHandler = function(message){
		//$("body").html(message);
	};
	var authErrorHandler = function(message){
		window.location = "index.html";
	};
	
	var handleError = function(errorMessage, errorThrown){
		var status = errorMessage.status;
		var message = errorMessage.message;
		
		switch(status){
			case 401: 
				authErrorHandler(message);
				break;
			default:
				defaultErrorHandler(message);
				break;
		}
	}
	
	var defaultSettings = {
		onError: handleError,
		parameterParser: F.makeQueryString
	}
	$.extend(defaultSettings, settings);
	
	var ac =  new AjaxConnection(url, defaultSettings, {dataType: dataType});
		ac.put = function(params, callback, options){
			var defaults = {
				type: (params) ? 'POST' : 'GET',
				data: F.makeQueryString(params) + "&method=put",
				parameterParser: null
			}
			$.extend(defaults, options);
			$.extend(defaultSettings, defaults);
			this.connect(defaults, callback);
		},
		ac.del = function(params, callback, options){
			var defaults = {
				type: (params) ? 'POST' : 'GET',
				data: F.makeQueryString(params) + "&method=delete",
				parameterParser: null
			}
			$.extend(defaults, options);
			$.extend(defaultSettings, defaults);
			this.connect(defaults, callback);
		}
	return ac;
};


/** Adpators to perform operations on all Forio APIs.
 * @module API
 * @see http://sites.google.com/a/forio.com/documentation/api-documentation
 */
F.API = {};
/** Utility functions for the API adaptors
 *  @static
 *  @class APIUtils
 *  @namespace F
 */
F.APIUtils =  (function(){
	
	/* We need the following because the simulate URL structure changed (9/2010).  
	 * First we need to determine whether we need to use the old URL regexp or the new URL regexp.
	 * Next we need to use the proper regexp to breakup the url of the current page
	 */
	var urlRegExp;
	
	//http://forio.com/simulate/simulation/cdc/health-bound/abc.swf
	var simRegExpOld = /(http|https|file):\/\/([^\/]+)\/([^\/]+)\/simulation\/([^\/]+)\/([^\/]+)\/?/;
	//http://forio.com/simulate/cdc/health-bound/simulation/abc.swf
	var simRegExpNew = /(http|https|file):\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\/([^\/]+)\/simulation\/?/;
	//http://forio.com/simulate
	var managerRegExp = /(http|https|file):\/\/([^\/]+)\/([^\/]+)\/?/;
	
	var usingManagerRegExp = false
	//need to flag when using manager one because it will not return a userPath or simPath
	if(simRegExpNew.test(window.location))
	{
		urlRegExp = simRegExpNew
	}
	else if(simRegExpOld.test(window.location))
	{
		urlRegExp = simRegExpOld
	}
	else
	{
		usingManagerRegExp= true;
		urlRegExp = managerRegExp;
	}

	var result = urlRegExp.exec(window.location) || [];
	return{
		/** Protocol used
		 * @property protocol
		 * @type String 
		 */
		protocol: result[1],
		
		basePath : result[1] + "://" + result[2],
		
		/** Domain of the sim
		 * @property domain
		 * @type String
		 */
		domain: result[2],
		
		/** section of URL referencing simulate 
		 * @property simulatePath
		 * @type String 
		 */
		simulatePath : result[3],
		
		/** Simulation author
		 * @property userPath
		 * @type String 
		 */
		userPath : usingManagerRegExp ? "" : result[4],
		
		/** Simulation name
		 * @property simPath
		 * @type String Name of sim 
		 */
		simPath : usingManagerRegExp ? "" : result[5],
		
		/** Enter in api type to get url
		 * @param {String} apiType  Currently run||archive||data||auth
		 * @return {String} absolute path to API
		 */
		getURL: function(apiType){
			// TODO: this API needs to be refactored as now it assumes it is only being used with Sim Urls
			//if(usingManagerRegExp)
				//throw "You are trying to get a URL that requires being in a simulation when you are only in the manager";
			
			var me = this;
			var url = me.protocol + "://" + me.domain + "/" + me.simulatePath + "/api/" + apiType + "/" + me.userPath + "/" + me.simPath;
			return url;
		},
	
		getNonSimURL: function(apiType){
			var me = this;
			var url = me.protocol + "://" + me.domain + "/" + me.simulatePath + "/api/" + apiType;
			return url;
		}
	}
}());

/** Perform operations on the Data API
 *  @static
 *  @class Data
 *  @namespace F.API
 */
F.API.Data = (function(){
	var isKeyValid = function(key){
		//A-Z and a-z, digits 0-9, an underscore, or a dash
		//TODO: Regex this
		return true;
	}
	
	var url = F.APIUtils.getURL("data");
	return{
		/** Save values to the data API. Assume object is single tuple as in "a=b" or "{a:b}" which posts 'b' to <URL>/a
		 * @param {Mixed} params stuff to save
		 */
		save: function(params, callback, options){
			params = F.makeObject(params);
			var dataKey, dataVal;
			for(var prop in params){ //Assume object just has the one key
				dataKey = prop;
				dataVal = param[prop];
			}
			this.saveAs(dataKey, dataVal, callback, options);
		},
		
		//TODO: simulate auto unescapes stuff before passing it on- replace " with /"
		/** Saves values to the specified key. Supports complicated object structures
		 * @param {String} key Key to save data under
		 * @param {*} value things to save
		 * @param {Function} callback function (optional)
		 * @param {*} options (optional)
		 * 
		 */
		saveAs: function(key, value, callback, options){
			if(!isKeyValid(key)){
				throw new Error("Data.save: Invalid key " + key );
			}
			
			var dataVal;
			try{
				dataVal = F.Object.serialize( F.makeObject(value) ); //Are we posting an object
			}
			catch(e){
				if(F.isString(value)){ //WE're just posting the value directly
					dataVal = value;
				}
				else{
					throw new Error("Data API: unknown value format");
				}
			}
			//UGH: simulate gotcha no.12312: What? You want case insensitive url params? surely you jest
			var val =  "data_action=SETPROPERTY&value=" + dataVal;
			var ac = new APIConnection(url + "/"+ key, options, {parameterParser: null});
				ac.post(val, callback);
		},
		
		/** Load data from the API
		 * @param {String} key location to load data from
		 * @param {Function} callback - Gets called with data object
		 * @param {*} options
		 */
		load: function(key, callback, options){
			var ac = new APIConnection(url+ "/" + key, options);
				ac.getJSON("", function(response){
					var data = response.data;
					(callback || $.noop)(data);
				});
		},
		
		/** Removes items from the data API
		 * @param {String} key location to delete
		 * @param {Function} callback 
		 * @param {*} options
		 */
		remove: function(key, callback, options){
			var ac = new APIConnection(url + "/" + key, options);
				ac.post("method=Delete", function(response){
					var data = response.data;
					(callback || $.noop)(data);
				});
		},
		
		/** Generic connection handler, does no params by default
		 * @param {String} key
		 * @param {*} params params to post
		 * @param {Function} callback 
		 * @param {*} options
		 */
		connect: function(key, params, callback, options){
			var ac = new APIConnection(url + "/" + key, options);
				ac.post(params, function(response){
					(callback || $.noop)(response);
				});
		}
	}
}());

/** Perform operations on runs
 *  @static
 *  @class Run
 *  @namespace F.API
 */
F.API.Run = (function(){
	var url = F.APIUtils.getURL("run");

	return {
		/** Save Decisions
		 * @param {Mixed} values values to save
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		saveValues: function(values, callback, options){
			var ac = new APIConnection(url, options);
				ac.post(values, callback);
		},
		
		/** Set properties of current run; name, desc, etc
		 * @param {Mixed} properties properties to set, can take multiple
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		setProperties:function(properties, callback, options){
			properties = F.makeQueryString(properties, {seperator: ":"});
			var propQs= {
				"run_set" : properties.split("&")
			}
			var ac = new APIConnection(url, options);
				ac.post(propQs, callback);
		},
		
		/** Perform run actions; step, clone etc
		 * @param {String||Array} actions one or more actions to set
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		doActions: function(actions, runid, callback, options){
			var actionsQs= {
				"run_action" : [].concat(actions),
				"run": runid
			}
			
			var ac = new APIConnection(url, options);
				ac.post(actionsQs, callback);
		},
		
		/** Get Information about current run
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return callback({Object}) the run object
		 */
		getInfo:function(callback, options){
			var ac = new APIConnection(url, options );
				ac.getJSON("", callback);
		},
		
		/** Generic connection handler, does no params by default
		 * @param {String} key
		 * @param {*} params params to post
		 * @param {Function} callback 
		 * @param {*} options
		 */
		connect: function(params, callback, options){
			var ac = new APIConnection(url, options);
				ac.post(params, callback);
		},
		
		//Common Actions
		/** Clone run. Use 'target' param to point to a file with $Run.RunId to make it return new RunId
		 * @param {String} runId the run to clone
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		clone: function(runId, callback, options){
			if(!runId){
				throw new Error("Run.clone: No source run provided");
			}
			this.doActions("clone", runId, callback, options);
		},
		
		/** Reset run to initial
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		reset: function(callback, options){
			this.doActions("reset", "", callback, options);
		},
		
		/** Advance Run. Just do doActions("step") if you just want to step once. Same as "doActions('step_to_x')"
		 * @param {String || Number} step the step to advance to
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 */
		stepTo: function(step, callback, options){
			if(!step){
				throw new Error("Run.stepTo: No step provided");
			}
			this.doActions("step_to_"+ step, "", callback, options);
		}
	}
}());

/** Handles Authentication.
 *  @class Auth
 *  @static
 *  @namespace F.API
 */
F.API.Auth = (function(){
	var url = F.APIUtils.getURL("authentication");
	return{
		/** Login to the simulation
		 * @param {String} email 
		 * @param {String} password
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return {}
		 */
		login: function(email,password,callback, options){
			var params = "user_action=login&email=" + encodeURIComponent(email) + "&password=" + password;
			
			var defaults = {
				parameterParser: null,
				onError: function(errorMess, errorThrown, responseText){
					var response = YAHOO.lang.JSON.parse(responseText);
					callback(response);
				} //Call the login handler anyway with the status code
			};
			$.extend(defaults, options);
			
			var ac = new APIConnection(url, defaults.params , defaults);
				ac.post(params , callback);
		},
		
		/** Logout from the simulation
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return {}
		 */
		logout: function(callback, options){
			var params = "user_action=logout";
			var defaults = {parameterParser: null};
			$.extend(defaults, options);
			var ac = new APIConnection(url, defaults.params, defaults);
				ac.post(params , callback);
		},
		
		loginGroup: function(grpName, callback, options){
			var params = "user_action=changeGroup&userGroup=" + grpName;
			var defaults = {
				parameterParser: null
			};
			$.extend(defaults, options);
			
			var ac = new APIConnection(url, defaults.params , defaults);
				ac.post(params , callback);
		},
		/** Check if you're currently logged in
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return callback({Boolean})
		 */
		isUserLoggedIn: function(callback, options){
			var ac = new APIConnection(url);
				ac.getJSON("", function(response){
					(callback || $.noop)(response && response.canRunSim);				
				});
		},
		
		/** Information about currently logged-in user
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return callback({Object}) Object representing user info
		 */
		getUserInfo: function(callback, options){
			var ac = new APIConnection(url);
				ac.getJSON("",callback);
		},
		
		/** Emails the password to the registered email. 410 status code if not found.
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return callback({Object}) Object representing user info
		 */
		sendPassword: function(loginid, callback, options){
			var qs = "user_action=emailUserPassword&email=" + loginid;
			var ac = new APIConnection(url, options);
				ac.post(qs , callback);
		}
	}
}());

/**  Archive API operations. 
 *  See http://sites.google.com/a/forio.com/documentation/api-documentation/api-archive for list of supported params
 *  @static
 *  @class Archive
 *  @namespace F.API
 */
F.API.Archive = (function(){
	var url = F.APIUtils.getURL("archive");
	return{
		/** Remove a run from Archive. Same as setting the "Saved" property of the run to true through Run API
		 * @param {String|Array} runId runs to remove
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return {}
		 */
		remove:function(runId, callback, options){
			var params =  {
				method: "DELETE",
				run: [].concat(runId)
			}
			
			var ac = new APIConnection(url, options);
				ac.post(params , callback);
		},
		
		/** Get all archived runs. 
		 * @param {Mixed} filter Filter runs from the api (Optional)
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return {} 
		 */
		getRuns:function(filter , callback, options){
			var ac = new APIConnection(url, options);
				ac.getJSON(filter , callback);
		},
		
		/** Generic connection handler, does no params by default
		 * @param {String} key
		 * @param {*} params params to post
		 * @param {Function} callback 
		 * @param {*} options
		 */
		connect: function(params, callback, options){
			var ac = new APIConnection(url, options);
				ac.post(params, callback);
		},
		
		setProperties: function(runId, properties, callback, options){
			var ac = new APIConnection(url, options);
			properties = F.makeQueryString(properties, {seperator: ":"});
			var propQs= {
				"run_set" : properties.split("&"),
				"run": [].concat(runId)
			}
			
			ac.post(propQs, callback);
		}
	}
}());

/**  Simulation API operations. 
 *  See http://sites.google.com/a/forio.com/documentation/api-documentation/api-simulation for list of supported params
 *  @static
 *  @class Simulation
 *  @namespace F.Simulation
 */
F.API.Simulation = (function(){
	var url = F.APIUtils.getNonSimURL("simulation");
	return{

		/** Get simulations matching the filters specified. 
		 * @param {Mixed} filter Filter runs from the api (Optional)
		 * @param {Function} callback (optional)
		 * @param {Object} options (optional)
		 * @return {} 
		 */
		getSimulations:function(filter , callback, options){
			var ac = new APIConnection(url, options);
				ac.getJSON(filter , callback);
		}
	}
}());
