define(["wc/has", "wc/config"], function(has, wcconfig) {
	"use strict";
	/**
	 * @constructor
	 * @alias module:wc/loader/style~StyleLoader
	 * @private
	 */
	function StyleLoader() {
		var
			/**
			 * The supported versions of IE below 10. The versions are of the form 'ie#' and are comma separated. The
			 * default can be overridden using module.config().ie.
			 *
			 * The default is generated through the build process by looking for SASS/CSS files with the name pattern
			 * .*\.ie[0-9]+\.css. This String is then converted to a String Array and sorted so that later versions of
			 * IE have their CSS applied earlier than older versions so, for example *.ie9.css is appied before *.ie8.css
			 * allowing for granular override.
			 *
			 * @var
			 * @type {String}
			 * @private
			 */
			ieVersionsToSupport = "${ie.css.list}",
			/**
			 * The list of platform and browser specific CSS files generated during build. This is used to populate
			 * the object screenStylesToAdd if that object is not instantiated in module.config().
			 *
			 * @var
			 * @type String
			 * @private
			 */
			platformCSS = "${css.pattern.list}",
			/**
			 * A JSON object containing a list of file name 'extensions' which are to be included. This is obtained
			 * from a module config if you want implementation specific styles. The default/fallback includes only the
			 * Firefox fixes and some bits of ios specific CSS for demo purposes. If you use a config override it must
			 * include any of these defaults you want to keep because the config will replace the defaults, not add to
			 * them.
			 *
			 * **DO NOT include IE specific files here**. IE versions (e.g. ie8 or ie9) are included in
			 * {@link module:wc/loader/style~ieVersionsToSupport}.
			 *
			 * Some popular tests (see lib/dojo/sniff for more):
			 *
			 * * has("ios")
			 * * has("android")
			 * * has("safari")
			 * * has("mac")
			 *
			 * Hard coded file name extensions used in WComponents default theme include:
			 *
			 * * `pattern_edge` for Edge;
			 * * `pattern_safari` for Safari; and
			 * * `pattern_ff` for Firefox.
			 *
			 * @var
			 * @type {module:wc/loader/style~config}
			 * @private
			 */
			stylesToAdd = null,

			/* NOTE TO SELF: the vars below which are only used once are used in a function which is called many times. Leave them here you twit! */

			/**
			 * The BASE URL for the CSS
			 * @constant
			 * @type {String}
			 * @private
			 */
			CSS_BASE_URL = null,
			/**
			 * The query string of the XSLT url is used as the query string for the CSS as it contains the version number and cache buster.
			 * @constant
			 * @type {String}
			 * @private
			 */
			CACHEBUSTER = null,
			/**
			 * The part of the CSS url which comes after the browser specific 'extension'.
			 * @var
			 * @type {String}
			 * @private
			 */
			cssFileNameAndUrlExtension = ".css",
			/**
			 * Used to access keys in the screenStylesToAdd JSON object.
			 * @var
			 * @type {String}
			 * @private
			 */
			ext,
			/**
			 * The common file name used to build the CSS files with an additional DOT suffix.
			 * The individual 'extension' extends this.;
			 * @var
			 * @type {String}
			 * @private
			 */
			CSS_FILE_NAME = "${css.target.file.name}.";

		initialise();

		/**
		 * Create a link element for a CSS file in the head element unless we already have one for this URL.
		 *
		 * @function
		 * @private
		 * @param {String} url The CSS url to add.
		 * @param {String} [media] A CSS media query for the link element.
		 */
		function addLinkElement(url, media) {
			var head = document.head || document.getElementsByTagName("head")[0],
				el, sibling, mainCss;
			if (!head) { // you gotta be kidding me ...
				return;
			}

			if (document.querySelector && document.querySelector("link[href='url']")) {
				// Do not add the same link element twice. If the browser does not support querySelector then we do not
				// really care if we add the link more than once but it is better to not do so.
				return;
			}
			mainCss = instance.getMainCss();
			sibling = mainCss ? mainCss.nextSibling : null;
			el = document.createElement("link");
			el.type = "text/css";
			el.setAttribute("rel", "stylesheet");
			if (media) {
				el.setAttribute("media", media);
			}
			el.setAttribute("href", url);
			if (sibling) {
				head.insertBefore(el, sibling);
			} else {
				head.appendChild(el);
			}
		}

		/**
		 * Create a link element for a particular stylesheet.
		 * @function
		 * @private
		 * @param {String} shortName The css file name without extension.
		 * @param {String} [media] An optional media query.
		 */
		function addStyle(shortName, media) {
			addLinkElement(CSS_BASE_URL + shortName + cssFileNameAndUrlExtension, media);
		}

		/**
		 * Write link elements for all CSS files required by specific desktop browsers.
		 * @private
		 * @function
		 */
		function loadStyle() {
			var key,
				value,
				media;
			for (ext in stylesToAdd) {
				key = value = media = null;

				if (typeof stylesToAdd[ext] === "string") {
					if (has(stylesToAdd[ext])) {
						addStyle(CSS_FILE_NAME + ext);
					}
				} else {
					key = stylesToAdd[ext].test;
					value = stylesToAdd[ext].version;
					media = stylesToAdd[ext].media;
					if (value || value === 0) {
						if (has(key) <= value) {
							addStyle(CSS_FILE_NAME + ext, media);
						}
					} else if (has(key)) {
						addStyle(CSS_FILE_NAME + ext, media);
					}
				}
			}
		}

		/**
		 * Write link elements for all CSS files required by the world's most "special" browser.
		 * @private
		 * @function
		 */
		function loadIE() {
			var IE_PREFIX = "ie",
				i,
				next,
				vNum,
				j,
				version,
				_v;  // I hate IE8! All these vars are for the array iteration because I cannot rely on forEach being loaded in time.;
			/*
			 * This module is loaded very early via XSLT and we cannot guarantee that IE8 has received, parsed and
			 * processed the whole compat layer. This makes it hard to catch some things but mostly foreEach is
			 * unreliable so I had to replace it with a simple iteration.
			 */
			for (i = 0; i < ieVersionsToSupport.length; ++i) {
				next = ieVersionsToSupport[i];
				vNum = next.match(/[0-9]{1,2}$/);
				if (vNum) {
					for (j = 0; j < vNum.length; ++j) {
						version = vNum[j];
						if (isNaN(version)) {
							break;
						}
						_v = version * 1;
						if (has("ie") && has("ie") <= _v) {
							addStyle(CSS_FILE_NAME + IE_PREFIX + version);
						} else if (_v >= 10) {
							/*
							 * WARNING... DANGER WILL ROBINSON
							 * ie10+ use trident version, which is non-linear compared to ieVerion but we are going
							 * to assume ONLY ie10 and maybe 11 need special CSS... This is a BAD assumption.
							 *
							 * Later... turns out to be not so bad since MS Edge does not identify as trident.
							 */
							if (has("trident") < 7) {
								addStyle(CSS_FILE_NAME + IE_PREFIX + "10");
							} else if (has("trident") <= _v - 4) {
								addStyle(CSS_FILE_NAME + IE_PREFIX + version);
							} else if (_v >= 11) {
								addStyle(CSS_FILE_NAME + IE_PREFIX + "11");
							}
						}
					}
				}
			}
		}

		function initialise() {
			var config = wcconfig.get("wc/loader/style"), i, next;
			if (config) {
				stylesToAdd = config.screen ? config.css : null;
				CSS_BASE_URL = config.cssBaseUrl;
				CACHEBUSTER = config.cachebuster;
				cssFileNameAndUrlExtension = ".css" + (CACHEBUSTER ? ("?" + CACHEBUSTER) : "");
				if (config.ie) {
					ieVersionsToSupport = config.ie;
				}
			}

			// We want to sort the IE versions so that we apply fixes for older versions AFTER fixes for newer ones.
			if (ieVersionsToSupport) {
				ieVersionsToSupport = ieVersionsToSupport.split(",");
				if (ieVersionsToSupport.length > 1) {
					ieVersionsToSupport = ieVersionsToSupport.sort(function (a,b) {
						var RX = /(\d+)$/,
							aVer = parseInt(a.match(RX)[0]),
							bVer = parseInt(b.match(RX)[0]);
						return bVer - aVer;
					});
				}
			}

			if (platformCSS.length && !stylesToAdd) {
				platformCSS = platformCSS.split(",");
				/* if(platformCSS.length > 1) {
					// damn
					// we want genericRenderingEngine then SpecificBrowser then SpecificPlatform
					// for example: .webkit THEN .safari THEN .ios
					// but .ff before .ios so reverse alphabet is not useful.
					// which means we would be relying on case sensitivity to do unicode ordering - which is BAD!!
				} */
				stylesToAdd = {};
				for (i = 0; i < platformCSS.length; ++i) { // again cannot rely on forEach in IE
					next = platformCSS[i];
					stylesToAdd[next] = next;
				}
			}
		}

		/**
		 * Get the main CSS link element produced in the XSLT. The browser specific CSS is added after this.
		 * @param {boolean} urlOnly If true returns only the URL of the main CSS.
		 * @returns {Element|String} The main CSS.
		 */
		this.getMainCss = function(urlOnly) {
			var mainCss = document.getElementById("wc_css_screen");
			if (mainCss && urlOnly) {
				return mainCss.getAttribute("href");
			}
			return mainCss;
		};

		/**
		 * Write link elements for all required CSS files. Should only be called from ui:root XSLT. To add CSS from a
		 * module use {@link module:wc/loader/style.add}.
		 *
		 * @function module:wc/loader/style.load
		 * @public
		 */
		this.load = function() {

			if (has("ie") || has("trident")) {
				loadIE();
			}
			if (stylesToAdd && !has("ie")) { // IE needs its own 'special' styles.
				loadStyle();
			}
		};

		/**
		 * Allow any module to load a CSS file. If your module wants to add custom CSS use this function.
		 *
		 * @function module:wc/loader/style.add
		 * @public
		 * @param {String} nameOrUrl The file name (with or without extension) or URL to a CSS file.
		 *
		 *   1. Supported URLs are of the form "//blah", "/blah", "http[s]://blah" or ".[.]/blah".
		 *   2. If the String is not in one of the URL patterns we assume you are getting a CSS file built from yhour
		 *     theme in the /style/ directory.
		 *     1. If the String contains ".css" we do not add the extension or cache-buster.
		 *     2. If the file name is not a URL and does not contain .css we add the extension (including the debug
		 *       name extension if in debug mode) and the cache-buster.
		 *
		 *   Therefore we suggest using a URL (and _I recommend_ the `//blah` form) or a simple file name if you are
		 *   building CSS files which are not able to be implemented using the _pattern and auto-loader mechanisms
		 *   (including the ability to override the style loadre config). So in reality this is almost always going to
		 *   be a URL unless you are particularly odd. Being particularly odd I tested this function using the debug CSS
		 *   and loading it from {@link module:wc/debug/a11y}.
		 *
		 * @param {String} [media] A CSS media query appropriate to the link element.
		 */
		this.add = function(nameOrUrl, media) {
			var isUrl = nameOrUrl.indexOf("/") === 0 || nameOrUrl.indexOf("http") === 0 || nameOrUrl.indexOf(".") === 0;

			if (isUrl) {
				// Huzzah we have a URL! Simply write the link element.
				addLinkElement(nameOrUrl, media);
			} else if (nameOrUrl.indexOf(".css") > 0) {
				// Name already has extension so we cannot add it using addStyle; it still needs the path though.
				addLinkElement(CSS_BASE_URL + nameOrUrl, media);
			} else {
				addStyle(nameOrUrl, media);
			}
		};
	}

	/**
		 * Provides a means to load CSS files for particular user agents/platforms etc.
		 *
		 * You may be asking why I split IE out from "screen". It was simply to make changing the defaults easier.
		 * For most browsers which aren't IE you only need one (or no) CSS overrides because old versions fall out of
		 * usage pretty quickly and have been generally pretty good at CSS for a long time. This allows us to use simple
		 * has tests for most major modern browsers (has("ff"), has("chrome") etc) without bothering too much about
		 * versions. Safari may be a candidate for version testing though, and that is why the "screen" config property
		 * allows an extension to have an object value.
		 *
		 * ##### Configuration
		 *
		 * There is a default set of supported browsers including IE versions. These may be overridden using module
		 * config.
		 *
		 * <pre><code>"ie": [string array of required ie versions],
		 * "screen": {
		 *     "ext": "hasTest",
		 *     "ext": {
		 *         "test": "hasTest",
		 *         "version": versionInteger,
		 *         "media": "css media selector"}}</code></pre>
		 *
		 * Go take a look at {@link module:wc/loader/style~config} and {@link module:wc/loader/style~configValueObject}.
		 *
		 * @example
		 * // The module config object is like this if we support Custom CSS
		 * //  only for ie10, ie11, Firefox, and Safari 8:
		 * "wc/loader/style": {
		 *    ie: ["ie11", "ie10"],
		 *    css: {
		 *        "ff": "ff",
		 *        "saf8": {
		 *            "test": "safari",
		 *            "version": 8
		 *        }
		 *    }
		 * }
		 *
		 * @module
		 * @requires module:wc/has
		 * @todo Maybe allow load to accept an Object or Object[] arg so it can be called from within another module?
		 */
	var instance = new StyleLoader();
	return instance;


	/**
	 * @typedef {Object} module:wc/loader/style~configValueObject
	 * @property {String} test The string arg passed to has to sniff user agent, eg "safari" or "ff".
	 * @property {int} [version] The version of the browser to test. If set then the has test is compared to this
	 *    and is deemed successful if the browser version is <= version.
	 * @property {String} [media] A CSS media selector. If set then the CSS link will include this media selector
	 * @example
	 * // To test for Safari 8 or below and a screen with a lot of horizontal pixels:
	 * {
	 *   "test": "safari",
	 *   "version": 8,
	 *   "media": "@media only screen and (min-device-width:2560px)"
	 * }
	 */

	/**
	 * @typedef {Object} module:wc/loader/style~config
	 * @property {String} key The file name extension used in the CSS build. This is the bit immediately before the
	 *    '.css' part of the built artifact's file name (eg 'ff').
	 * @property {(String|module:wc/loader/style~configValueObject)} value The has test argument and optional comparison
	 *    value(s). If this property is a string then it is a simple has test. Otherwise see
	 *    {@link module:wc/loader/style~configValueObject}
	 * @example
	 * // the following includes Firefox of any version, Safari version 6, print styles for any
	 * // mac and styles for safari version 8 including a media selector for large screens:
	 * {
	 *   "ff": "ff",
	 *   "safari6": {
	 *      "test": "safari",
	 *      "version": 6
	 *   },
	 *   "macprint": {
	 *      "test": "mac",
	 *      "media": "print"
	 *   },
	 *   "saf8big": {
	 *      "test": "safari",
	 *      "version": 8,
	 *      "media": "@media only screen and (min-device-width:2560px)"
	 *   }
	 * }
	 */
});
