﻿(function() {

	var Event = YAHOO.util.Event,
		Dom = YAHOO.util.Dom,
		Lang = YAHOO.lang,
		UA = YAHOO.env.ua,
		Panel = YAHOO.widget.Panel,
		Tooltip = YAHOO.widget.Tooltip,

		ModulePrototype = YAHOO.widget.Module.prototype,
		fnModuleInitDefaultConfig = ModulePrototype.initDefaultConfig,
		fnModuleInitResizeMonitor = ModulePrototype._initResizeMonitor,

		OverlayPrototype = YAHOO.widget.Overlay.prototype,
		fnOverlayShowIframe = OverlayPrototype.showIframe,

		PanelPrototype,
		fnPanelInitDefaultConfig,

		m_bPanelDocumentListenersAdded = false,
		m_oFocusedElement, // Currently focused element in the DOM
		m_oOverlayManager, // An OverlayManager instance

		m_bToolTipDocumentListenersAdded = false,
		m_oContextElements = {}, // Hash of Tooltip context elements, indexed by Tooltip id
		m_oTooltips = {}, // Hash of Tooltips indexed by context element id


	// Private constants for strings

		_ARIA_PREFIX = "aria-",
		_ROLE = "role",
		_PRESENTATION = "presentation",
		_USE_ARIA = "usearia",
		_BLUR = "blur",
		_FOCUS = "focus",
		_VISIBLE = "visible",
		_BEFORE_HIDE = "beforeHide",
		_DIALOG = "dialog",
		_DESCRIBED_BY = "describedby",
		_CONTEXT = "context",
		_HIDDEN = "hidden",
		_CONTAINER_CLOSE = "container-close",
		_A = "a",
		_HREF = "href",
		_BUTTON = "button",
		_KEY_DOWN = "keydown",
		_BEFORE_SHOW = "beforeShow",
		_CLOSE = "close",
		_ALERT_DIALOG = "alertdialog",
		_LABELLED_BY = "labelledby",
		_XY = "xy",
		_TOOLTIP = "tooltip",
		_DESTROY = "destroy";


	var setARIARole = function(element, role) {

		element.setAttribute(_ROLE, role);

	};


	var setARIAProperty = function(element, property, value) {

		element.setAttribute((_ARIA_PREFIX + property), value);

	};


	// Module ARIA plugin - augments YAHOO.widget.Module

	Lang.augmentObject(ModulePrototype, {

		_initResizeMonitor: function() {

			fnModuleInitResizeMonitor.call(this);

			var oIFrame = this.resizeMonitor;

			if (oIFrame) {
				setARIARole(oIFrame, _PRESENTATION);
				oIFrame.tabIndex = -1;
			}

		},


		configUseARIA: function(type, args) {
			// Stub for subclasses
		},

		configDescribedBy: function(type, args) {

			var sID = args[0];

			if (this.cfg.getProperty(_USE_ARIA) && sID) {
				setARIAProperty(this.element, _DESCRIBED_BY, sID);
			}

		},

		configLabelledBy: function(type, args) {

			var sID = args[0];

			if (this.cfg.getProperty(_USE_ARIA) && sID) {
				setARIAProperty(this.element, _LABELLED_BY, sID);
			}

		},

		initDefaultConfig: function() {

			/**
			* @config usearia
			* @description Boolean indicating if use of the WAI-ARIA Roles and States should 
			* be enabled.
			* @type Boolean
			* @default true for Firefox 3 and IE 8, false for all other browsers.
			*/
			this.cfg.addProperty(
				_USE_ARIA,
				{
					handler: this.configUseARIA,
					value: (UA.gecko && UA.gecko >= 1.9) || (UA.ie && UA.ie >= 8),
					validator: Lang.isBoolean
				}
			 );


			/**
			* @config labelledby
			* @description String representing the id of the element that labels the Module.
			* Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#labelledby">
			* <code>aria-labelledby</code></a> attribute.
			* @type String
			* @default null
			*/
			this.cfg.addProperty(
				_LABELLED_BY,
				{
					handler: this.configLabelledBy,
					validator: Lang.isString
				}
			 );


			/**
			* @attribute describedby
			* @description String representing the id of the element that describes the Module.
			* Maps directly to the <a href="http://www.w3.org/TR/wai-aria/#describedby">
			* <code>aria-describedby</code></a> attribute.
			* @type String
			* @default null
			*/
			this.cfg.addProperty(
				_DESCRIBED_BY,
				{
					handler: this.configDescribedBy,
					validator: Lang.isString
				}
			 );

			fnModuleInitDefaultConfig.call(this);

		}

	}, "initDefaultConfig", "configUseARIA", "configLabelledBy",
		"configDescribedBy", "_initResizeMonitor");



	// Overlay ARIA plugin - augments YAHOO.widget.Overlay

	OverlayPrototype.showIframe = function() {

		fnOverlayShowIframe.call(this);

		var oIFrame = this.iframe;

		if (this.cfg.getProperty(_USE_ARIA) && oIFrame && !oIFrame.getAttribute(_ROLE)) {
			setARIARole(oIFrame, _PRESENTATION);
			oIFrame.tabIndex = -1;
		}

	};



	// Panel ARIA plugin - augments YAHOO.widget.Panel


	var onPanelKeyDown = function(event) {

		var nCharCode = Event.getCharCode(event);

		if (nCharCode === 27) {

			if (this.cancel) {	// Dialog
				this.cancel();
			}
			else {	// Panel
				this.hide();
			}

		}

	};


	var onPanelDOMFocus = function(event) {

		this.fireEvent(_FOCUS, event);

	};


	var onPanelDOMBlur = function(event) {

		this.fireEvent(_BLUR, event);

	};


	var onPanelFocus = function(type, args) {

		var oEvent = args[0];  // DOM event

		if (m_oOverlayManager._manageFocus(this)) {

			if (this.cfg.getProperty(_VISIBLE) && this.focusFirst) {

				// If the event was not sourced from the UI, focus the first element in the Panel

				if (!oEvent) {
					this.focusFirst();
				}

			}

		}

	};


	var onPanelBlur = function(type, args) {

		var oEvent = args[0]; // DOM event

		// If the event was not sourced from the UI fire the blur event

		if (m_oOverlayManager._manageBlur(this) && !oEvent) {
			this.fireEvent(_BLUR);
		}

	};


	var onPanelBeforeHide = function(type, args, element) {

		this.blur();

		if (element && element.focus) {

			try {
				element.focus();
			}
			catch (e) {

			}

		}

		this.unsubscribe(_BEFORE_HIDE, onPanelBeforeHide, element);

	};


	var onPanelBeforeShow = function() {

		this.subscribe(_BEFORE_HIDE, onPanelBeforeHide, m_oFocusedElement);

	};


	var setPanelHiddenRole = function() {

		var oElement = this.cfg.getProperty(_ROLE) === _DIALOG ? this.innerElement : this.body;

		setARIAProperty(oElement, _HIDDEN, !this.cfg.getProperty(_VISIBLE));

	};


	var setRoleForCloseButton = function() {

		Dom.getElementsByClassName(_CONTAINER_CLOSE, _A, this.element, function(element) {

			element.removeAttribute(_HREF);
			setARIARole(element, _BUTTON);
			element.tabIndex = 0;

		});

	};


	var onPanelConfigClose = function(type, args) {

		var bClose = args[0];

		if (bClose) {

			setRoleForCloseButton.call(this);

		}

	};


	if (Panel) {

		PanelPrototype = Panel.prototype;
		fnPanelInitDefaultConfig = PanelPrototype.initDefaultConfig;


		Lang.augmentObject(PanelPrototype, {

			hasFocus: function() {

				return (m_oOverlayManager && this === m_oOverlayManager.getActive());

			},


			configUseARIA: function(type, args) {

				var bUseARIA = args[0];

				if (bUseARIA) {

					if (!m_oOverlayManager) {
						m_oOverlayManager = new YAHOO.widget.OverlayManager();
					}

					m_oOverlayManager.register(this);


					this.focus = function() {

						if (!this.hasFocus()) {
							this.fireEvent(_FOCUS);
						}

					};


					this.blur = function() {

						if (this.hasFocus()) {
							this.fireEvent(_BLUR);
						}

					};


					Event.onFocus(this.element, onPanelDOMFocus, null, this);
					Event.onBlur(this.element, onPanelDOMBlur, null, this);

					this.subscribe(_FOCUS, onPanelFocus);
					this.subscribe(_BLUR, onPanelBlur);

					Event.on(this.element, _KEY_DOWN, onPanelKeyDown, null, this);

					this.subscribe(_BEFORE_SHOW, onPanelBeforeShow);

					setPanelHiddenRole.call(this);

					this.cfg.subscribeToConfigEvent(_VISIBLE, setPanelHiddenRole);
					this.cfg.subscribeToConfigEvent(_CLOSE, onPanelConfigClose);

					if (!m_bPanelDocumentListenersAdded) {

						Event.onFocus(document, function(event) {

							m_oFocusedElement = Event.getTarget(event);

						});


						m_bPanelDocumentListenersAdded = true;

					}

				}

			},

			configDescribedBy: function(type, args) {

				var sID = args[0],
					oElement;

				if (this.cfg.getProperty(_USE_ARIA) && sID) {

					oElement = this.cfg.getProperty(_ROLE) === _DIALOG ?
						this.innerElement : this.body;

					setARIAProperty(oElement, _DESCRIBED_BY, sID);

				}

			},

			configLabelledBy: function(type, args) {

				var sID = args[0],
					oElement;

				if (this.cfg.getProperty(_USE_ARIA) && sID) {

					oElement = this.cfg.getProperty(_ROLE) === _DIALOG ?
						this.innerElement : this.body;

					setARIAProperty(oElement, _LABELLED_BY, sID);

				}

			},

			configRole: function(type, args) {

				var sRole = args[0],
					oElement,
					oHeader,
					sHeaderId;


				if (sRole) {


					switch (sRole) {

						case _DIALOG:

							oElement = this.innerElement;

							break;

						case _ALERT_DIALOG:

							oElement = this.body;

							break;

					}


					if (oElement) {

						setARIARole(oElement, sRole);

						oHeader = this.header;

						sHeaderId = oHeader.id || Dom.generateId(oHeader);

						this.cfg.setProperty(_LABELLED_BY, sHeaderId);

						setRoleForCloseButton.call(this);

					}

				}

			},


			initDefaultConfig: function() {

				fnPanelInitDefaultConfig.call(this);

				this.cfg.addProperty(
					_ROLE,
					{
						handler: this.configRole,
						value: _DIALOG,
						validator: Lang.isString
					}
				 );

			}

		}, "initDefaultConfig", "configRole", "configUseARIA", "configLabelledBy",
			"configDescribedBy", "hasFocus");

	}


	// Tooltip ARIA plugin - augments YAHOO.widget.Tooltip

	var onDocumentFocus = function(event) {

		var oTarget = Event.getTarget(event),
			oTooltip = m_oTooltips[oTarget.id],
			aXY;

		if (oTooltip) {

			aXY = Dom.getXY(oTarget);

			oTooltip.cfg.setProperty(_XY, [aXY[0], (aXY[1] + oTarget.offsetHeight + 5)]);
			oTooltip.show();

		}

	};


	var onDocumentBlur = function(event) {

		var oTarget = Event.getTarget(event),
			oTooltip = m_oTooltips[oTarget.id];

		if (oTooltip && oTooltip.cfg.getProperty(_VISIBLE)) {
			oTooltip.hide();
		}

	};


	var unregisterContextElement = function(element) {

		var sContextElId = element.id,
			oTooltip = m_oTooltips[sContextElId];

		if (oTooltip === this) {
			delete m_oTooltips[sContextElId];
			element.removeAttribute(_DESCRIBED_BY);
		}

	};


	var unregisterContextElements = function() {

		var sId = this.element.id,
			aContextElements = m_oContextElements[sId];

		Dom.batch(aContextElements, unregisterContextElement, this, true);

		m_oContextElements[sId] = null;

	};


	var registerContextElement = function(element) {

		var sContextElId = element.id || Dom.generateId(element);

		m_oTooltips[sContextElId] = this;

		setARIAProperty(element, _DESCRIBED_BY, this.element.id);

	};


	var registerContextElements = function() {

		var aContextElements = this.cfg.getProperty(_CONTEXT);

		Dom.batch(aContextElements, registerContextElement, this, true);

		m_oContextElements[this.element.id] = aContextElements;

	};


	var onTooltipContextChange = function(type, args) {

		unregisterContextElements.call(this);

		var context = args[0];

		if (context) {
			registerContextElements.call(this);
		}

	};


	var setTooltipHiddenRole = function() {

		setARIAProperty(this.body, _HIDDEN, !this.cfg.getProperty(_VISIBLE));

	};


	if (Tooltip) {

		Tooltip.prototype.configUseARIA = function(type, args) {

			var bUseARIA = args[0];

			if (bUseARIA) {

				setARIARole(this.body, _TOOLTIP);

				this.cfg.subscribeToConfigEvent(_CONTEXT, onTooltipContextChange);

				setTooltipHiddenRole.call(this);

				this.cfg.subscribeToConfigEvent(_VISIBLE, setTooltipHiddenRole);

				this.subscribe(_DESTROY, unregisterContextElements);


				if (!m_bToolTipDocumentListenersAdded) {

					Event.onFocus(document, onDocumentFocus);
					Event.onBlur(document, onDocumentBlur);

					m_bToolTipDocumentListenersAdded = true;

				}

			}

		};

	}

} ());
