/**
 * This loader plugin module determines the the dependencies we need to load the configure JS environment with the necessary
 * features expected by the rest of the codebase, i.e. polyfills.
 *
 * It is intended that this loader plugin will be a loader dependency <http://requirejs.org/docs/api.html#config-deps>
 * it **must** run before other modules because it loads the compatibility modules and fixes required for
 * this browser to handle the rest of the codebase.
 *
 * You **must not** load anything here that needs to wire up events (those are fixes, not compatibility
 * code). This is for basic scripting API support.
 *
 * Many of the tests are written by us for our own specific needs however some are also lifted with little or no
 * change from the has project: <https://github.com/phiggins42/has.js/>
 *
 * Read the source Luke!
 *
 * @module
 * @private
 * @param {Object} global The global namespace, e.g. 'window'.
 */
(function(global) {  // at this point window.require is the config object, not actually require itself.
	"use strict";
	define(["wc/has"],
		function(has) {
			var result = ["dojo/sniff"];

			(function(addtest) {
				// This block taken from tests from hasjs project. Didn't want to load the whole script.
				addtest("bug-getelementsbyname", function(g, d) {
					var buggy,
						script = d.createElement("script"),
						id = "__test_" + Number(new Date()),
						root = d.getElementsByTagName("script")[0].parentNode;

					script.id = id;
					script.type = "text/javascript";
					root.insertBefore(script, root.firstChild);
					buggy = d.getElementsByName(id)[0] === script;
					root.removeChild(script);
					return buggy;
				});

				// true for IE < 9
				// http://msdn.microsoft.com/en-us/library/ms536389(VS.85).aspx vs
				// http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-2141741547
				addtest("dom-create-attr", function(g, d) {
					var input,
						supported = false;
					try {
						input = d.createElement("<input type='hidden' name='hasjs'>");
						supported = input.type === "hidden" && input.name === "hasjs";
					}
					catch (e) {
						// Do nothing
					}
					return supported;
				});

			})(has.add);

			(function(addtest) {

				addtest("ie-compat-mode", function(g) {
					var isCompatModeRe = /MSIE 7\..+Trident\/\d/;
					return isCompatModeRe.test(g.navigator.userAgent);
				});

				addtest("activex", function(g) {
					return !!("ActiveXObject" in g);
				});

				addtest("bug-button-value", function(g, d) {
					var button, value = "hi";
					button = d.createElement("button");
					button.value = value;
					button.innerHTML = "<span>howdy</span>";
					return button.value !== value;
				});

				addtest("formdata", function(g) {
					return "FormData" in g;
				});

				addtest("draganddrop", function(g, d, el) {
					return "draggable" in el;
				});

				addtest("native-console", function(g) {
					return ("console" in g);
				});

				addtest("native-console-debug", function(g) {
					return (has("native-console") && "debug" in g.console);
				});

				addtest("native-console-time", function(g) {
					return (has("native-console") && "time" in g.console);
				});

				addtest("global-node", function(g) {
					return ("Node" in g);
				});

				addtest("global-nodefilter", function(g) {
					return ("NodeFilter" in g);
				});

				addtest("global-keyevent", function(g) {
					return ("KeyEvent" in g);
				});

				addtest("global-performance", function(g) {
					return (("performance" in g) && !!g.performance);  // will be present but null in FF 17 if page generated by XSLT
				});

				addtest("global-performance-marking", function(g) {
					return (has("global-performance") && "mark" in g.performance);
				});


				addtest("dom-createtreewalker", function(g, d) {
					return ("createTreeWalker" in d);
				});

				addtest("dom-comparedocumentposition", function(g, d, el) {
					return ("compareDocumentPosition" in el);
				});

				addtest("function-bind", function(g) {
					return !!g.Function.prototype.bind;
				});

				addtest("promise-es6", function(g) {
					return ("Promise" in g);
				});

				addtest("date-now", function(g) {
					return !!g.Date.now;
				});

				addtest("object-defineproperty", function(g) {
					return !!g.Object.defineProperty;
				});

				addtest("object-defineproperty-dom", function(g, d, el) {
					return hasWorkingObjectDefineProperty(g, el);
				});

				addtest("object-defineproperty-pojo", function(g) {
					return hasWorkingObjectDefineProperty(g, {});
				});

				addtest("object-definegetter", function(g) {
					return (typeof g.Object.__defineGetter__ !== "undefined");
				});

				addtest("object-definesetter", function(g) {
					return (typeof g.Object.__defineSetter__ !== "undefined");
				});


				addtest("object-getownpropertydescriptor", function(g) {
					return !!g.Object.getOwnPropertyDescriptor;
				});

				addtest("object-keys", function(g) {
					return !!g.Object.keys;
				});

				addtest("string-trim", function() {
					/* jshint -W053 */
					/* eslint-disable */
					var s = new String(" "),
						result = ("trim" in s);
					/*eslint-enable */
					// Safari (5, Windows) has String.prototype.trim() but it is incompatible with strict mode
					if (result) {
						try {
							s.trim();
						}
						catch (e) {
							result = false;  // not good enough to count
						}
					}
					return result;
				});

				addtest("element-datalist", function() {
					return "list" in document.createElement("input");
				});

				addtest("element-details", function() {
					return "open" in document.createElement("details");
				});

				addtest("native-dateinput", function() {
					var el, d = "date", result = false;
					try {
						el = document.createElement("input");
						el.type = d;
						result = d === el.type;
					}
					catch (e) {
						result = false;
					}
					finally {
						el = null;
						return result;
					}
				});

				function hasWorkingObjectDefineProperty(g, obj) {
					var result = has("object-defineproperty");
					if (result) {  // it has defineProperty but does it work?
						try {
							g.Object.defineProperty(obj, "id", { get: function() {
								return "c";
							}});
						}
						catch (ex) {
							result = false;  // this is not a working defineProperty (i.e. perhaps Safari 5 which does not support defineProperty on DOM objects)
						}
					}
					return result;
				}
			})(has.add);

			/*
			 * Q. Why do we patch the Array prototype instead of providing a set of array library functions?
			 * A. Many reasons, one of the main ones is that once you have provided library functions they are
			 * very hard to take away. So even when ES5 array methods are completely taken for granted and available
			 * in even the lowest of the low you will still be stuck with the overhead of your library functions.
			 *
			 * This has already happened, we used to ship with many more array fixes but they are no longer necessary
			 * so we deleted the code. That simple, we did not need to change any application code because everything
			 * simply kept working using native methods that are now ubiquitous.
			 *
			 * Q. Extending the DOM is bad isn't it?
			 * A. Yes. But we never extend it, we "standardize" it. At no time do we ever add any feature to the DOM that is
			 * not 100% standard.
			 *
			 */
			(function(Array, addtest) {

				addtest("array-every", function() {
					return !!Array.prototype.every;
				});

				addtest("array-filter", function() {
					return !!Array.prototype.filter;
				});

				addtest("array-foreach", function() {
					return !!Array.prototype.forEach;
				});

				addtest("array-indexof", function() {
					return !!Array.prototype.indexOf;
				});

				addtest("array-isarray", function() {
					return !!Array.isArray;
				});

				addtest("array-lastindexof", function() {
					return !!Array.prototype.lastIndexOf;
				});

				addtest("array-map", function() {
					return !!Array.prototype.map;
				});

				addtest("array-reduce", function() {
					return !!Array.prototype.reduce;
				});

				addtest("array-reduceright", function() {
					return !!Array.prototype.reduceRight;
				});

				addtest("array-some", function() {
					return !!Array.prototype.some;
				});

				addtest("array-es5", function() {
					return has("array-every") && has("array-filter") && has("array-foreach") &&
						has("array-indexof") && has("array-isarray") && has("array-lastindexof") &&
						has("array-map") && has("array-reduce") && has("array-reduceright") &&
						has("array-some");
				});
			})(global.Array, has.add);

			// ALWAYS FETCH
			// as little as possible

			// CONDITIONALLY FETCH
			if (!has("object-defineproperty-dom") && has("object-definegetter")) {
				result.push("wc/ecma5/Object.defineProperty");
			}

			if (!has("object-getownpropertydescriptor") && has("object-definesetter")) {
				result.push("wc/ecma5/Object.getOwnPropertyDescriptor");
			}

			if (!has("string-trim")) {
				result.push("wc/ecma5/String.prototype.trim");
			}

			if (!has("object-keys")) {
				result.push("wc/ecma5/Object.keys");
			}

			if (!has("date-now")) {
				result.push("wc/ecma5/Date.now");
			}

			if (!has("promise-es6")) {
				result.push("Promise");
			}

			if (!has("function-bind")) {
				/*
				 * NOTE: this is an ugly fix for an IE8 race condition exacerbated by
				 * the memory leak issue fixed by a MS hotfix: see KB2032595.
				 * result.push("wc/ecma5/Function.prototype.bind");
				 */
				global.Function.prototype.bind = function (obj) {
					var slice = [].slice,
						args = slice.call(arguments, 1),
						self = this,
						Nop = function() {
							this.toString = function() {
								return self.toString();
							};
						},
						bound = function() {
							return self.apply((Nop.prototype && this instanceof Nop) ? this : ( obj || (global || {} )),
									args.concat(slice.call(arguments)));
						};
					Nop.prototype = self.prototype;
					bound.prototype = new Nop();
					return bound;
				};
			}
			if (!(has("native-console") && has("native-console-debug") && (has("native-console-time")))) {
				result.push("wc/compat/console");
			}
			if (!has("global-node")) {
				result.push("wc/compat/Node");
			}
			if (!has("global-nodefilter") || !has("dom-createtreewalker")) {
				result.push("wc/compat/TreeWalker");
			}
			if (!has("global-keyevent")) {
				result.push("wc/compat/KeyEvent");
			}
			if (!has("dom-createtreewalker")) {
				result.push("wc/compat/TreeWalker");
			}
			if (!has("dom-comparedocumentposition")) {
				result.push("wc/compat/compareDocumentPosition");
			}
			if (!has("array-every")) {
				result.push("wc/ecma5/Array.prototype.every");
			}
			if (!has("array-filter")) {
				result.push("wc/ecma5/Array.prototype.filter");
			}
			if (!has("array-foreach")) {
				result.push("wc/ecma5/Array.prototype.forEach");
			}
			if (!has("array-indexof")) {
				result.push("wc/ecma5/Array.prototype.indexOf");
			}
			if (!has("array-isarray")) {
				result.push("wc/ecma5/Array.isArray");
			}
			if (!has("array-lastindexof")) {
				result.push("wc/ecma5/Array.prototype.lastIndexOf");
			}
			if (!has("array-map")) {
				result.push("wc/ecma5/Array.prototype.map");
			}
			if (!has("array-reduce")) {
				result.push("wc/ecma5/Array.prototype.reduce");
			}
			if (!has("array-reduceright")) {
				result.push("wc/ecma5/Array.prototype.reduceRight");
			}
			if (!has("array-some")) {
				result.push("wc/ecma5/Array.prototype.some");
			}
			if (has("bug-getelementsbyname")) {
				result.push("wc/fix/getElementsByName_ie9");
			}
			if (has("activex")) {
				// while not strictly a fix classes can't put this in their dependency lists so we need to load it really early for conditional loading.
				result.push("wc/fix/getActiveX_ieAll");
			}

			/*
			 * The polyfill for global performance gets loaded up too late to attach load event listeners.
			 * Putting it into wc/fixes gets loaded up too late to attach load event listeners. They have
			 * to be here whether we like it or not. This fix is ONLY needed for IE versions which do not
			 * have globel performance.
			 */
			if (!(has("global-performance") || has("dom-addeventlistener"))) {
				window.attachEvent("onload", function() {
					if (window.requirejs) {
						window.requirejs.config({config: {"wc/compat/navigationTiming": {
							"loadEventStart": ((new Date()) * 1),/* NOTE: our polyfill of Date.now() has not yet loaded */
							"loadEventEnd": ((new Date()) * 1)}}});
					}
				});
			}

			result.load = function (id, parentRequire, callback) {
				parentRequire(result, callback);
			};
			return result;
		});
})(this || window);
