/** Page metrics script for timing the server and client-side times for a site. */
var PageMetrics = function (domain, endpoint, debug) {
	var metricsServer, pageMetrics, timers, debugMode;

	/* The URL which the metrics will be posted to. */
	metricsServer = endpoint;

	/* The data which will be posted to the server on calling save(). */
	pageMetrics = {
		"site": domain,
		"time": (new Date()).getTime(),
		"metrics": {
			"cookielength": document.cookie.length
		}
	};

	/* Currently running timers. */
	timers = [];

	/* Set for debug mode. */
	debugMode = debug || false;

	return {
		/* Save the data by posting it to a remote server */
		save: function () {
			var metric, jQueryVersion;

			// Force all the timers to stop.
			this.stopTimer(null);

			// Make sure that jQuery is loaded.
			if (typeof jQuery !== 'function') {
				this.debugMessage("jQuery is not loaded, can't push result to server.");
			}

			// Compatibility hack for jQuery 1.3
			jQueryVersion = jQuery.fn.jquery.split(".");
			if (jQueryVersion[0] === "1" && jQueryVersion[1] === "3") {
				for (metric in pageMetrics.metrics) {
					if (pageMetrics.metrics.hasOwnProperty(metric)) {
						pageMetrics["metrics[" + metric + "]"] = pageMetrics.metrics[metric];
					}
				}
				delete pageMetrics.metrics;
			}

			// Push the object up to the server
			jQuery.ajax({
				data: pageMetrics,
				dataType: "jsonp",
				url: metricsServer
			});
		},

		/* Get the value of a metric.
		 *
		 * @return	The value of the metric in milliseconds. If the metric does
		 *			not exist, then 0 will be returned.
		 */
		get: function (metric) {
			if (typeof pageMetrics.metrics[metric] === 'number') {
				return pageMetrics.metrics[metric];
			}
			this.debugMessage("Returning 0 by default for " + metric);
			return 0;
		},

		/* Set a metric to a specific value.
		 *
		 * @param	metric	The name of the metric to set.
		 * @param	value	The value of the metric in milliseconds.
		 */
		set: function (metric, value) {
			if (typeof metric === 'string' && typeof value === 'number') {
				if (value < 0) {
					value = 0;
				}
				this.debugMessage("Setting " + metric + " to " + value);
				pageMetrics.metrics[metric] = value;
			}
		},

		/* Set the URL that this collection of timers should be credited to.
		 *
		 * @param	url	The URL or description of this page.
		 */
		setURL: function (url) {
			pageMetrics.url = url;
		},

		/* Start a timer. If there is already a timer running, this timer will
		 * be nested inside it and will (when stopped) automatically be
		 * deducted from the outer timer. If a metric with the same name
		 * already exists, this timer will add itself to the current value of
		 * the metric.
		 *
		 * @param	metric	The name of the timer to run. This will also be the
		 *					name of the metric saved when the timer is stopped.
		 */
		startTimer: function (metric) {
			timers.push(
				{
					name: metric,
					start: (new Date()).getTime(),
					additions: this.get(metric),
					deductions: 0
				}
			);
			this.debugMessage("Started timer: " + metric);
		},

		/* Stop a timer. If this timer is nested within another one it will
		 * handle any deductions of time from the outer one. If this timer
		 * contains nested timers it will stop the inner timers before stopping
		 * this one.
		 *
		 * @param	The name of the timer to stop. 
		 */
		stopTimer: function (metric) {
			var startData, duration;
			if (timers.length > 0) {
				if (timers[timers.length - 1].name === metric) {
					startData = timers.pop();
					duration = (new Date()).getTime() - startData.start;

					// Record the net time taken by this timer.
					this.debugMessage("Stopped timer: " + metric + " (" + duration + "ms)");
					this.set(
						metric,
						duration + startData.additions - startData.deductions
					);

					// Deduct this timer from any parent timers.
					if (timers.length > 0) {
						timers[timers.length - 1].deductions += duration;
					}
				} else {
					// Stop any inner-nested timers that haven't already been stopped.
					do {
						this.stopTimer(timers[timers.length - 1].name);
					} while (timers.length > 0 && timers[timers.length - 1].name !== metric);
				}
			}
		},

		/* Write a debug message to the console (if present).
		 *
		 * @param	message	The message to write out.
		 */
		debugMessage: function (message) {
			if (debugMode && typeof console === 'object') {
				console.log("[Page Metrics] " + message);
			}
		}
	};
};


