/**
 * Provides a mechanism to get elements which are grouped by WAI-ARIA roles or by being aria-owned.
 *
 * @module
 * @requires module:wc/array/toArray
 * @requires module:wc/dom/aria
 * @requires module:wc/dom/Widget
 * @requires module:wc/dom/role
 *
 * @todo document private members, clean up code.
 */
define(["wc/array/toArray",
	"wc/dom/aria",
	"wc/dom/Widget",
	"wc/dom/role"],
function(toArray, aria, Widget, $role) {
	"use strict";

	/**
	 * @constructor
	 * @private
	 * @alias module:wc/dom/ariaGroup~AriaGroup
	 */
	function AriaGroup() {
		/**
		* Gets the element that "aria-owns" another element.
		*
		* @function module:wc/dom/ariaGroup.getOwner
		* @param {Element} element The element to test.
		* @returns {Element} The element which owns the passed in element.
		*/
		this.getOwner = function(element) {
			var id = element.id,
				ownerWd,
				result;
			if (id) {
				ownerWd = new Widget("", "", { "aria-owns": id });
				result = ownerWd.findDescendant(document.body);  // something may not be aria-owned by more than one element at a time
			}
			return result;
		};

		/**
		 * Gets elements that are indirectly owned by a DOM element with "aria-owns".
		 *
		 * @function module:wc/dom/ariaGroup.getOwned
		 * @param {Element} element The start element.
		 * @returns {array} An array of elements owned by the element. If the element does not own any then the
		 * array is empty.
		 */
		this.getOwned = function(element) {
			var ids = element.getAttribute("aria-owns"),
				result = [],
				i,
				len,
				owned;
			if (ids) {
				ids = ids.split(/\s+/);
				for (i = 0, len = ids.length; i < len; ++i) {
					owned = document.getElementById(ids[i]);
					if (owned) {
						result.push(owned);
					}
				}
			}
			return result;
		};

		/**
		 * Get all elements in the group which contains or is defined by a particular element. If you are using
		 * owned groups then this should be called *after* testing for owned groups.
		 *
		 * @function module:wc/dom/ariaGroup.getGroup
		 * @public
		 * @param {Element} element The reference element.
		 * @param {String} role The element role (we have already calculated before we get here).
		 * @param {Boolean} [ignoreInnerGroups] If true then all members of the group will be included even if they
		 *    are also members of another group nested within the current one. This is most commonly found in menus
		 *    where we may want all items (for example in a tree) whether or not they are also in a submenu.
		 * @returns {Array} An array of group members.
		 */
		this.getGroup = function (element, role, ignoreInnerGroups) {
			var container,
				_role = role || $role.get(element, true),
				containerWd,
				scopedRoles,
				candidates,
				result,
				widgets,
				rescope = true;

			if (_role) {
				scopedRoles = aria.getMustContain(_role);
			}
			if (!scopedRoles.length) {
				scopedRoles = aria.getScopedTo(_role);
			}

			// a group is defined by an element if the element role has something "scopedTo" it
			if ((scopedRoles && scopedRoles.length)) {
				container = element;
				rescope = false;
			} else {
				container = this.getContainer(element, null, true);
			}

			if (container) {

				if (rescope) {
					_role = $role.get(container, true);
					scopedRoles = aria.getMustContain(_role);
					if (!scopedRoles.length) {
						scopedRoles = aria.getScopedTo(_role);
					}
				}

				if (scopedRoles.length) {
					containerWd = new Widget("", "", { role: _role });
					widgets = buildWidgetArrayFromRoles(scopedRoles);

					if (widgets.length) {
						candidates = toArray(Widget.findDescendants(container, widgets));

						if (candidates && candidates.length) {
							if (ignoreInnerGroups) {
								result = candidates;
							} else {
								result = candidates.filter(function(next) {
									return this.getContainer(next, containerWd, true) === container;
								}, this);
							}
						}
					}
				} else {
					console.log("could not find any scoped roles");
				}
			}
			return result;
		};


		/**
		 * Get the element which has the role which defines/contains the group in which the current element is in.
		 *
		 * @function module:wc/dom/ariaGroup.getContainer
		 * @public
		 * @param {Element} element the reference element
		 * @param {module:wc/dom/Widget} [containerWd] a Widget describing the container, if any, for the
		 *    subclass of {@link module:wc/dom/AriaAnalog}
		 * @param {Boolean} [ignoreOwner] If true then do not look for a WAI-ARIA owner (aria-owns) element.
		 * @returns {Element} The group container element, if any.
		 */
		this.getContainer = function(element, containerWd, ignoreOwner) {
			var scope, widgets, result, role;

			if (!ignoreOwner) {
				result = this.getOwner(element);
			}
			if (!result) {
				if (containerWd) {
					result = containerWd.findAncestor(element);
				} else if ((role = $role.get(element, true))) {
					scope = aria.getScope(role);
					if (!(scope && scope.length)) {
						scope = aria.getScopedBy(role);
					}
					if (scope && scope.length) {
						widgets = buildWidgetArrayFromRoles(scope);
						if (widgets.length === 0) {
							widgets = null;
						} else if (widgets.length === 1) {
							widgets = widgets[0];
						}

						if (widgets) {
							if (Array.isArray(widgets)) {
								result = Widget.findAncestor(element, widgets);
							} else {
								result = widgets.findAncestor(element);
							}
						}
					}
				}
			}
			return result;
		};

		/**
		 * Helper function to build an array of widget descriptors from an array of roles.
		 *
		 * @function
		 * @private
		 * @param {String[]} roles An array of role strings.
		 * @returns {module:wc/dom/Widget[]} An array of Widgets or an empty array if no Widgets are constructed.
		 */
		function buildWidgetArrayFromRoles(roles) {
			var widgets = [],
				uselessRole = "group",  // this role is not used to build a group as it is a
				uselessIndex;

			if (roles.length > 1 && ~(uselessIndex = roles.indexOf(uselessRole))) {
				roles.splice(uselessIndex, 1);
			}

			if (roles.length) {
				widgets = roles.map(function(next) {
					return new Widget("", "", { role: next });
				});
			}
			return widgets;
		}
	}
	return /** @alias module:wc/dom/ariaGroup */ new AriaGroup();
});
