/**
 * @Copyright Franson Technology AB, Sweden, 2011
 * http://gpsgate.com
 *
 * @fileoverview
 * low-level methods to interact with GpsGate Server WS
 * (The service API proxies depend on this)
 *
 * @author Fredrik Blomqvist
 *
 * @module GpsGate
 *
 */

goog.provide('GpsGate'); // just to get a root. ok?
/** @const */
var GpsGate = GpsGate || {};

goog.provide('GpsGate.Server');
/** @const */
GpsGate.Server = GpsGate.Server || {};

goog.require('MochiKit.Base');
goog.require('MochiKit.Async');


/**
 * difference between the client clock and server clock.
 * > 0 means that server time is ahead of client time and vice versa.
 * I.e add it to the current time to get the server reference time.
 *
 * Value is calculated (estimated) when calling
 * server methods with timestamps so
 * we become independent of invalid time(&zone)
 * setting at the client side.
 * @private @type {integer} ms
 */
GpsGate.Server._serverTimeOffset = Number.POSITIVE_INFINITY; // initally set to largest value. todo: change! better that it just returns/matches the local time in the situation no server-call has been made!

/**
 * Convenience to get adjusted, "correct" time (using _serverTimeOffset estimate)
 * return {!Date}
 */
GpsGate.Server.getServerTime = function()
{
	// todo: check if called before _serverTimeOffset has been set?
	return new Date((new Date()).getTime() + GpsGate.Server._serverTimeOffset);
};


/**
 * todo: rename getGpsGateServerRelativePath?
 * todo: create method to create absolute path from relative?
 * todo: deprecate? (only used in BT and webmap)
 * @deprecated
 * @method getServerName
 * @return {string} server name url (including protocol and port number)
 */
GpsGate.Server.getServerName = function()
{
	var wl = window.location;
	var path = wl.href;

	// drop two dir levels from the end (our relative installation/setup depth)
	// todo: create a regexp instead..
	var idx = path.lastIndexOf('/');
	if (idx != -1) {
		path = path.substring(0, idx);

		var proto = wl.protocol + '//';
		idx = path.lastIndexOf('/');
		if (idx != (path.lastIndexOf(proto) + proto.length)) { // make sure we don't go as far as the protocol
			path = path.substring(0, idx);
		}
	}
	return path;
};

/**
 * same as Franson.Util.getUniqueId but added here to avoid dependency
 * @private
 * @return {integer}
 */
GpsGate.Server._getCallId = function()
{
	// lazy init to avoid load-order dependency on MochiKit
	var self = arguments.callee;
	if (!self._counter)
		self._counter = MochiKit.Base.counter();
	return self._counter();
};

/**
 * support native JSON serialization (FF 3.1, IE8, Chrome, Safari) (and possibly if injecting json(2).js in the page)
 * (we assume MochiKit.Base.serializeJSON already has a Date formatter registered that converts to ISO stamps)
 * (todo: could rather add this kind of code to MochiKit I guess?)
 * @param {Object} obj
 * @return {string}
 */
GpsGate.Server._toJSON = function(obj)
{
	// cache
	var serialize = arguments.callee._serializeJSON;
	if (typeof serialize != 'function') {
		serialize = arguments.callee._serializeJSON = (typeof JSON != 'undefined' && typeof JSON.stringify == 'function') ? JSON.stringify : MochiKit.Base.serializeJSON;
	}
	return serialize(obj);
};

// todo: _parseJSON also (currently we "cheat" with dates but we can acchieve same effect since parse supports a post-processor function) (todo: profile speed in this case)
/**
 * @param {?string} str JSON compatible string
 * @return {Object}
 */
GpsGate.Server._evalJSON = function(str)
{
	// todo: use native JSON.parse if exists (needs special case for Dates though)
//	return eval('(' + str + ')');
	return (new Function("return " + str))(); // test alternative. slightly "safer" and should apparently work better with FF+Firebug (jQuery uses this for example)
};


/**
 * standard WS-call method (used by the Service proxies)
 * todo: allow choosing between GET/POST calls?
 * todo: inject appId (from Context.Session.ApplicationId) in params by default? (could simplify the RPC proxies and make them better cachable)
 * @method call
 * @param {string} url
 * @param {string} method
 * @param {Object=} [params]
 * @return {!Deferred}
 */
GpsGate.Server.call = function(url, method, params)
{
	params = params || {};
	// params.appId = Context.Session.ApplicationId; // ?

	var d = MochiKit.Async.doXHR(url, {
		method: 'POST',
		headers: {
			'Content-Type': 'application/json; charset=utf-8',
			'X-JSON-RPC': method
		},
		sendContent: GpsGate.Server._toJSON({
			id: GpsGate.Server._getCallId(),
			method: method,
			params: params
		})
	});

/*	// same as above but using GET
	// (could use loadJSONDoc also but that uses a slightly less efficient pre eval-check)
	// (now this is same as doSimpleXMLHttpRequest)
	var d = MochiKit.Async.doXHR(url + '/' + method, {
		'method': 'GET',
		'headers': {
			'Content-Type': 'application/json; charset=utf-8',
			'X-JSON-RPC': method
		},
		// ! note that this currently relies on a patched queryString that
		// formats DateTimes in same way as our json-spec (ISO-stamp) (isn't that REST "standard"?)
		// (todo: create a queryStringJSON that uses the same logic as Mochi.serializeJSON?)
		'queryString': params // todo: do we need an id? (or other no-cache stuff?)
	});
*/
	var callTime = new Date(); // for server time offset calc
	return d.addCallback(
		function(request) {
			// eval JSON and forward possible JayRock error
			var response = GpsGate.Server._evalJSON(request.responseText);

			// todo: handle session expired errors here directly?
			if (typeof response.error == 'undefined') {
				// calc (estimate) server time offset (ok to do here?)
				var result = response.result;
				if (result !== null && typeof result.queryTimeStamp != 'undefined') {
					// estimate using the min magnitude (but maintain sign)
					// todo: use a min threshold? (example: ofs < 5 ms -> 0 ?)
					// todo: ! hmm, can caching confuse this to give artificially low value?
					var delta = result.queryTimeStamp - callTime;
					GpsGate.Server._serverTimeOffset = (delta < 0 ? -1 : 1) * Math.min(Math.abs(GpsGate.Server._serverTimeOffset), Math.abs(delta));
				}
				return result;
			} else {
				var error = response.error;
				var errorMsg = null;

				if (typeof localize == 'function' && typeof error.langKey != 'undefined') {
					errorMsg = localize(error.langKey);
				} else {
					errorMsg = error.message;
				}
				logError(errorMsg);

				var e = new Error(errorMsg); // todo: can('t) we set the .name property?
				e.nativeError = error;
				return e; // could throw also
			}
		}
	);
};


// test. mostly for use in the new jquery-biased Mobile project.
// same as .call but using jQuery's xhr and jQuery's Deferred.
// todo: currently returns a MK deferred for compatibility but return jquery's deferred later(?)
// might be better for compatibility(?)
// todo: if this works out ok we could create a separate proxy generator that uses this by default (or simply patch in as we did here)
GpsGate.Server.jqCall = function(url, method, params)
{
	params = params || {};
	params.appId = Context.Session.ApplicationId;

	// access jQuery via window to enable compilation..
	var d = window.jQuery.ajax({
		type: 'POST',
		url: url,
		//-----
		dataType: '*', // ! can't 'text' or 'json', see below..
		accepts: '*', // seems to be necessary for us
		// ! need to disable this. jquery tries to be "smart" and sniff for json anyway. we use "invalid" json and need consistency in the responseText..
		converters: { "text json": null },
		//-----
		cache: false,
		headers: {
			'Content-Type': 'application/json; charset=utf-8',
			'X-JSON-RPC': method
		},
		data: GpsGate.Server._toJSON({
			id: GpsGate.Server._getCallId(),
			method: method,
			params: params
		})
	});

	// for now, wrap in MK to test
	var mkD = new MochiKit.Async.Deferred();

	var callTime = new Date(); // for server time offset calc
	d.always(
		function(request) {
			// eval JSON and forward possible JayRock error
			// ! observe that we accept slightly malformed json (Dates..) so we can't use the strict json parser (+ have to disable jQuery's "magic", see above)
			var response = GpsGate.Server._evalJSON(request.responseText);

			// todo: handle session expired errors here directly?
			if (typeof response.error == 'undefined') {
				// calc (estimate) server time offset (ok to do here?)
				var result = response.result;
				if (result != null && typeof result.queryTimeStamp != 'undefined') {
					// estimate using the min magnitude (but maintain sign)
					// todo: use a min threshold? (example: ofs < 5 ms -> 0 ?)
					// todo: ! hmm, can caching confuse this to give artificially low value?
					var delta = result.queryTimeStamp - callTime;
					GpsGate.Server._serverTimeOffset = (delta < 0 ? -1 : 1) * Math.min(Math.abs(GpsGate.Server._serverTimeOffset), Math.abs(delta));
				}

				mkD.callback(result);
			} else {
				var error = response.error;
				var errorMsg = null;

				if (typeof localize == 'function' && typeof error.langKey != 'undefined') {
					errorMsg = localize(error.langKey);
				} else {
					errorMsg = error.message;
				}
				logError(errorMsg);

				var e = new Error(errorMsg); // todo: can('t) we set the .name property?
				e.nativeError = error;

				mkD.errback(e);
			}
		}
	);
	return mkD;
};

// test! (or always?)
if (typeof window.jQuery != 'undefined')
	GpsGate.Server.call = GpsGate.Server.jqCall;

