define(["wc/dom/classList",
	"wc/dom/Widget"],
	function (classList, Widget) {
		"use strict";

		/**
		 * The descriptor of the icon element.
		 * @type module:wc/dom/Widget
		 */
		var ICON = new Widget("", "fa", {"aria-hidden": "true"});

		function getHTML(icon) {
			return "<i class='fa " + icon + "' aria-hidden='true'></i>";
		}

		/**
		 * Type checker for public functions.
		 * @function
		 * @private
		 * @param {Eleement} element the element arg to test
		 * @returns {Boolean} `true` if element is an Element
		 * @throws {TypeError} if element is not an Element
		 */
		function testElementArg(element) {
			if (!(element && element.nodeType === Node.ELEMENT_NODE)) {
				throw new TypeError("element must be an HTML element");
			}
			return true;
		}

		/**
		 * Get an icon from an element which may contain one.
		 * @function
		 * @private
		 * @param {Element} element the element to test
		 * @returns {?Element} the icon if found
		 */
		function getIcon(element) {
			testElementArg(element);
			return ICON.isOneOfMe(element) ? element : ICON.findDescendant(element);
		}

		/**
		 * Helper to add/remove classes from an icon.
		 * @function
		 * @private
		 * @param {Element} element the element which may be or contain an icon
		 * @param {String} icon the class to change
		 * @param {boolean} [add] if `true` add the class, otherwise remove it
		 * @returns {boolean} `true` if an icon element is found, otherwise `false`
		 * @throws {TypeError} if element is not a HTML element
		 * @throws {TypeError} if icon is not a non-empty String
		 */
		function addRemoveIcon(element, icon, add) {
			var func, iconElement;
			if (!(element && icon)) {
				throw new TypeError("arguments must be defined");
			}
			if (icon.constructor !== String) {
				throw new TypeError("icon to " + (add ? "add" : "remove") + " argument must be a String");
			}

			iconElement = getIcon(element);
			if (iconElement) {
				func = add ? "add" : "remove";
				classList[func](iconElement, icon);
				return true;
			}
			return false;
		}

		/**
		 * @constructor
		 * @private
		 * @alias module:wc/ui/icon~Icon
		 */
		function Icon() {
		}

		/**
		 * Swap one icon class for another. May be used to add or remove an icon class.
		 * @function
		 * @public
		 * @param {Element} element The element which may contain an icon. If there is no icon then this function does nothing.
		 * @param {String} add the icon className to add
		 * @param {String} remove the icon className to remove
		 */
		Icon.prototype.change = function(element, add, remove) {
			var icon;
			if (!(add || remove)) {
				return;
			}
			if (!(icon = getIcon(element))) {
				return;
			}
			if (remove) {
				classList.remove(icon, remove);
			}
			if (add) {
				classList.add(icon, add);
			}
		};

		/**
		 * Remove a class from an icon.
		 * @function
		 * @public
		 * @param {Element} element the element which may contain an icon
		 * @param {String} remove the class to remove
		 */
		Icon.prototype.remove = function(element, remove) {
			var icon;
			if (addRemoveIcon(element, remove)) {
				icon = getIcon(element);
				if (classList.getLength(icon) === 1) {
					// only `fa` left
					icon.parentNode.removeChild(icon);
				}
			}
		};

		/**
		 * Add a class to an existing icon _or_ add a new icon as the first child of an element
		 * @function
		 * @public
		 * @param {Element} element the icon element or an element to which we add an icon
		 * @param {String} add the icon className to add
		 */
		Icon.prototype.add = function(element, add) {
			if (!addRemoveIcon(element, add, true)) {
				element.insertAdjacentHTML("afterbegin", getHTML(add));
			}
		};

		/**
		 * Get the {@link module:wc/dom/Widget} that describes an icon.
		 * @returns {iconL#3.Widget|module:wc/dom/Widget}
		 */
		Icon.prototype.getWidget = function() {
			return ICON;
		};

		Icon.prototype.get = function(element) {
			return getIcon(element);
		};

		/**
		 * Allows for manipulation of icons.
		 * @module
		 * @requires module:wc/dom/classList
		 * @requires module:wc/dom/Widget
		 */
		return new Icon();
	});
