/**
 * A best-effort polyfill for {@link http://www.w3.org/TR/navigation-timing/}. This code is expected to only run in IE8
 * because all modern browsers support this API natively. Alas, FF (well version 17 anyway) does not initialize
 * window.performance for a page generated by XSLT, so now this polyfill needs to work in FF too. Lame. The timings are
 * necessarily approximate and are pretty rubbish in IE8 to be honest.
 *
 * NOTE: whilst a compat layer module this must NOT be loaded in wc/compat/compat but in wc/fixes because it requires a
 * whole slew of DOM knowledge and access to require().
 *
 * @module
 * @private
 * @requires module:wc/has
 * @requires module:wc/dom/event
 * @requires module:wc/dom/storage
 * @requires module:wc/dom/initialise
 * @requires module:wc/isNumeric
 * @requires module:wc/config
 */
define(["wc/has", "wc/dom/event", "wc/dom/storage", "wc/dom/initialise", "wc/isNumeric", "wc/config"],
	/** @param has wc/has @param event wc/dom/event @param storage wc/dom/storage @param initialise wc/dom/initialise @param isNumeric wc/isNumeric @param wcconfig wc/config @ignore */
	function(has, event, storage, initialise, isNumeric, wcconfig) {
		"use strict";
		var HISTORY_CHECK_PARAM = "last_history_length",
			ORIGIN_CHECK_PROP = "wcOriginCheck",
			ORIGIN_CHECK = {
				bad: -1,
				good: 0,
				unknown: 1
			},
			storeKey = {
				UNLOAD_START: "wc_unloadStart",
				UNLOAD_END: "wc_unloadEnd",
				BEFOREUNLOAD_END: "wc_beforeunloadEnd"
			},
			performance = {};


		/**
		 * Determine if the fix must be applied and apply it if necessary.
		 * @function checkApplyFix
		 * @private
		 */
		function checkApplyFix() {
			var timing;

			/**
			 * Initialise any timers that depend directly or indirectly on timers that we have persisted
			 * from one page to the next.
			 * If you call this when the origin check is "bad" you will clear any meaningful data from
			 * these timers and they will be in their default state (set to zero).
			 */
			function initialiseCrossPageTimers() {
				if (window.performance[ORIGIN_CHECK_PROP] !== ORIGIN_CHECK.bad) {
					try {
						timing.unloadEventStart = storage.get(storeKey.UNLOAD_START, true) || 0;
						timing.unloadEventEnd = storage.get(storeKey.UNLOAD_END, true) || 0;
						timing.fetchStart = storage.get(storeKey.BEFOREUNLOAD_END, true) || 0;  // The closest we can get for "fetchStart" is the end of the beforeunload event.

					} finally {
						storage.erase(storeKey.UNLOAD_START, true);
						storage.erase(storeKey.UNLOAD_END, true);
						storage.erase(storeKey.BEFOREUNLOAD_END, true);
					}
				} else {  // the origin check found the previous page was not in the same origin, clear the timers
					timing.unloadEventStart = 0;
					timing.unloadEventEnd = 0;
					timing.fetchStart = 0;
				}
				/*
				 * The closest we can get for "responseStart" is unloadEventStart. You may think this should be
				 * unloadEventEnd but our observations show that Internet Explorer 8 fires the unload event when it
				 * receives the first byte of the response.
				 */
				timing.responseStart = timing.unloadEventStart;
				timing.navigationStart = timing.fetchStart;  // if not known is meant to be same value as fetchStart
				timing.domainLookupStart = timing.fetchStart;  // if not known is meant to be same value as fetchStart
				timing.domainLookupEnd = timing.fetchStart;  // if not known is meant to be same value as fetchStart
				timing.connectStart = timing.domainLookupEnd;  // if not known is meant to be same value as domainLookupEnd
				timing.connectEnd = timing.domainLookupEnd;  // if not known is meant to be same value as domainLookupEnd
				timing.requestStart = timing.fetchStart;  // not in spec but the best we can do
			}

			/**
			 * Timers that span page unload/load boundaries only make sense when navigation is from one
			 * page to another in the same origin. We need to detect if the user navigates to another page
			 * in a different origin and discard any timers that cross page-load boundaries.
			 *
			 * IE8 has a really annoying bug where window.history is not correctly initialised
			 * until about 8 to 10 seconds after the page has loaded. This makes our task particularly
			 * difficult.
			 */
			function checkOrigin() {
				var lastHistoryLength,
					currentHistoryLength;
				if (window.performance[ORIGIN_CHECK_PROP] === ORIGIN_CHECK.unknown) {
					lastHistoryLength = storage.get(HISTORY_CHECK_PARAM, true);
					if (isNumeric(lastHistoryLength)) {
						lastHistoryLength *= 1;
						currentHistoryLength = window.history.length;
						if (currentHistoryLength !== (lastHistoryLength + 1)) { // to be less strict use (currentHistoryLength > (lastHistoryLength + 1))
							// another page (outside our control) has come atwixt us (or in strict mode we have reloaded same page)
							window.performance[ORIGIN_CHECK_PROP] = ORIGIN_CHECK.bad;
							console.info("Detected 'foreign origin' navigation. Nulling cross page timers.");
							initialiseCrossPageTimers();  // this will reset all the timers to zero
						} else {
							window.performance[ORIGIN_CHECK_PROP] = ORIGIN_CHECK.good;
							console.info("Detected 'same origin' navigation. Allowing cross page timers.");
						}
					} else { // we are not able to check the origin, best to err on the side of caution
						window.performance[ORIGIN_CHECK_PROP] = ORIGIN_CHECK.bad;
						console.info("Could not check origin. Nulling cross page timers.");
						initialiseCrossPageTimers();  // this will reset all the timers to zero
					}
				}
			}

			function beforeunloadEnd() {
				storage.put(storeKey.BEFOREUNLOAD_END, Date.now(), true);
				// if the origin check result is not "unknown" we have been on the page long enough to trust the history length
				if (window.performance[ORIGIN_CHECK_PROP] !== ORIGIN_CHECK.unknown) {
					storage.put(HISTORY_CHECK_PARAM, window.history.length, true);
				} else {
					storage.erase(HISTORY_CHECK_PARAM, true); // can't trust window.history at this point so null it
				}
			}

			if (!has("global-performance")) {
				if (has("object-defineproperty")) {  // FF 17 will not let you set window.performance any other way.
					Object.defineProperty(window, "performance", {get: function() {
						return performance;
					}});
				} else {
					window.performance = performance;
				}

				window.performance[ORIGIN_CHECK_PROP] = ORIGIN_CHECK.unknown;  // flag that we have not done an origin check yet
				timing = window.performance.timing = new PerformanceTiming();
				initialiseCrossPageTimers();
				if (has("ie") === 8) {
					console.info("Queuing 'origin check' for profiling to work around IE8 bugs.");
					window.setTimeout(checkOrigin, 10000);  // ie8 bugs mean we have to wait 10 secs to do origin check
				} else {
					checkOrigin();  // everyone else can do origin check immediately
				}
				event.add(window, event.TYPE.unload, unloadStart, -101);
				event.add(window, event.TYPE.unload, unloadEnd, 101);
				event.add(window, event.TYPE.load, loadStart, -101);
				event.add(window, event.TYPE.load, loadEnd, 101);
				event.add(window, event.TYPE.beforeunload, beforeunloadEnd, 101);
				event.add(window, event.TYPE.readystatechange, beforeunloadEnd, 101);

				initialise.addCallback(function() {
					// could simply change this to the end (+101) of the readystatechange "complete" event
					timing.domContentLoadedEventEnd = Date.now();
				});

				initialise.addInitRoutine(function() {
					var wctiming, config = wcconfig.get("wc/compat/navigationTiming");
					wctiming = config.timing;
					timing.domLoading = wctiming["loading"] || wctiming["interactive"];  // in IE8 we don't always get "loading" so "interactive" will have to do
					timing.domInteractive = wctiming["interactive"];
					/* NOTE: timing.loadEventStart and loadEventEnd should be milliseconds of now().
					 * In IE without native performance these have to be set by a single handler in the onload
					 * event which is included in wc/compat/js. In Firefox these can be dealt with (slightly)
					 *  more accurately in the load event handlers in this file. */
					timing.loadEventStart = config.loadEventStart || 0;
					timing.loadEventEnd = config.loadEventEnd || 0;
					timing.responseEnd = timing.domLoading;  // hopefully close enough
					window.setTimeout(function() {  // FF needs this to be delayed
						timing.domComplete = wctiming["complete"];
						timing.domContentLoadedEventStart = wctiming["complete"];  // this is the closest equivalent to the domContentLoadedEvent in IE8
					}, 0);
				});
			}
		}

		/**
		 * Provide a polyfill for PerformanceTiming and set initial performance timing properties to 0.
		 * @constructor
		 * @alias module:wc/compat/navigationTiming~PerformanceTiming
		 * @private
		 */
		function PerformanceTiming() {
			/**
			 * The start of the unload event
			 * @var
			 * @type Number
			 * @public
			 */
			this.unloadEventStart = 0;
			/**
			 * The end of the unload event
			 * @var
			 * @type Number
			 * @public
			 */
			this.unloadEventEnd = 0;
			/**
			 * The start of a redirect. Never going to be able to fake this (returning zero is a legitimate value anyway).
			 * @var
			 * @type Number
			 * @public
			 */
			this.redirectStart = 0;
			/**
			 * The end of a redirect. Never going to be able to fake this (returning zero is a legitimate value anyway).
			 * @var
			 * @type Number
			 * @public
			 */
			this.redirectEnd = 0;
			/**
			 * The start of a secure connection. Never going to be able to fake this (returning zero is a legitimate value anyway).
			 * @var
			 * @type Number
			 * @public
			 */
			this.secureConnectionStart = 0;
			/**
			 * The start of the load event
			 * @var
			 * @type Number
			 * @public
			 */
			this.loadEventStart = 0;

			/**
			 * The end of the load event
			 * @var
			 * @type Number
			 * @public
			 */
			this.loadEventEnd = 0;
		}

		/**
		 * Fake the time of the start of the load event by setting it to now.
		 * @function loadStart
		 * @private
		 */
		function loadStart() {
			window.performance.timing.loadEventStart = Date.now();
		}

		/**
		 * Fake the time of the end of the load event by setting it to now.
		 * @function loadEnd
		 * @private
		 */
		function loadEnd() {
			window.performance.timing.loadEventEnd = Date.now();
		}

		/**
		 * Fake the time of the start of the unload event by setting it to now.
		 * @function unloadStart
		 * @private
		 */
		function unloadStart() {
			storage.put(storeKey.UNLOAD_START, Date.now(), true);
		}

		/**
		 * Fake the time of the start of the unload event by setting it to now.
		 * @function unloadEnd
		 * @private
		 */
		function unloadEnd() {
			storage.put(storeKey.UNLOAD_END, Date.now(), true);
		}

		checkApplyFix();
		return performance;
	});
