/**
 * The OverlayContextPanel Utility
 *
 * @module aui-overlay
 * @submodule aui-overlay-context-panel
 */

var L = A.Lang,
	isBoolean = L.isBoolean,
	isString = L.isString,
	isObject = L.isObject,

	ALIGN = 'align',
	ANIM = 'anim',
	ARROW = 'arrow',
	BACKGROUND_COLOR = 'backgroundColor',
	BLANK = '',
	BOUNDING_BOX = 'boundingBox',
	CLICK = 'click',
	CONTENT_BOX = 'contentBox',
	CONTEXTPANEL = 'overlaycontextpanel',
	DEFAULT = 'default',
	DOT = '.',
	END = 'end',
	HIDDEN = 'hidden',
	INNER = 'inner',
	OPACITY = 'opacity',
	POINTER = 'pointer',
	SHOW_ARROW = 'showArrow',
	STATE = 'state',
	STYLE = 'style',
	VISIBLE = 'visible',

	BC = 'bc',
	BL = 'bl',
	BR = 'br',
	CC = 'cc',
	LB = 'lb',
	LC = 'lc',
	LT = 'lt',
	RB = 'rb',
	RC = 'rc',
	RL = 'rl',

	getCN = A.getClassName,

	CSS_CONTEXTPANEL = getCN(CONTEXTPANEL),
	CSS_CONTEXTPANEL_ARROW = getCN(CONTEXTPANEL, ARROW, BLANK),
	CSS_CONTEXTPANEL_HIDDEN = getCN(CONTEXTPANEL, HIDDEN),
	CSS_CONTEXTPANEL_POINTER = getCN(CONTEXTPANEL, POINTER),
	CSS_CONTEXTPANEL_POINTER_INNER = getCN(CONTEXTPANEL, POINTER, INNER),
	CSS_STATE_DEFAULT = getCN(STATE, DEFAULT),

	TPL_POINTER = '<div class="' + [ CSS_STATE_DEFAULT, CSS_CONTEXTPANEL_POINTER ].join(' ') + '"></div>',
	TPL_POINTER_INNER = '<div class="' + CSS_CONTEXTPANEL_POINTER_INNER + '"></div>';

/**
 * <p><img src="assets/images/aui-overlay-context-panel/main.png"/></p>
 *
 * A base class for OverlayContextPanel, providing:
 * <ul>
 *	<li>Widget Lifecycle (initializer, renderUI, bindUI, syncUI, destructor)</li>
 *	<li>Customizable arrow</li>
 *	<li>Optional animation when show or hide</li>
 * </ul>
 *
 * Quick Example:<br/>
 *
 * <pre><code>var instance = new A.OverlayContextPanel({
 *  bodyContent: 'Here s a sample OverlayContextPanel.',
 *  boundingBox: '#overlay-context-panel',
 *  trigger: '#triggerButton',
 *  cancellableHide: true,
 *  hideDelay: 200,
 *  hideOnDocumentClick: false,
 *  anim: true
 * }).render();
 * </code></pre>
 *
 * Check the list of <a href="OverlayContextPanel.html#configattributes">Configuration Attributes</a> available for
 * OverlayContextPanel.
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class OverlayContextPanel
 * @constructor
 * @extends OverlayContext
 */
var OverlayContextPanel = A.Component.create(
	{
		/**
		 * Static property provides a string to identify the class.
		 *
		 * @property OverlayContextPanel.NAME
		 * @type String
		 * @static
		 */
		NAME: CONTEXTPANEL,

		/**
		 * Static property used to define the default attribute
		 * configuration for the OverlayContextPanel.
		 *
		 * @property OverlayContextPanel.ATTRS
		 * @type Object
		 * @static
		 */
		ATTRS: {
			/**
			 * Enable or disable the animation for hide and show. Used as the
			 * <a href="Anim.html">Anim</a> configuration attributes.
			 *
			 * <pre><code>anim: {
			 *  show: {
			 *  	duration: .9
			 *  },
			 *  hide: {
			 *  	duration: .2
			 *  }
			 * }
			 * </code></pre>
			 *
			 * @attribute anim
			 * @default { show: false }
			 * @type Object
			 */
			anim: {
				lazyAdd: false,
				value: {
					show: false
				},
				setter: function(v) {
					return this._setAnim(v);
				}
			},

			/**
			 * Position where the arrow will be placed. See
			 * <a href="OverlayContextPanel.html#config_showArrow">showArrow</a>. If it's
			 * not set, it will get the value set on the
			 * <a href="OverlayContext.html#config_align">align</a> point. Here is a
			 * list of valid arrows 'bc', 'bl', 'br', 'cc', 'lb', 'lc', 'lt', 'rb',
			 * 'rc', 'rl'.
			 *
			 * @attribute arrow
			 * @default null
			 * @type String
			 */
			arrow: {
				value: null,
				validator: isString
			},

			/**
			 * See <a href="OverlayContext.html#config_hideOn">hideOn</a>.
			 *
			 * @attribute hideOn
			 * @default click
			 * @type String
			 */
			hideOn: {
				value: CLICK
			},

			/**
			 * See <a href="OverlayContext.html#config_showOn">showOn</a>.
			 *
			 * @attribute showOn
			 * @default click
			 * @type String
			 */
			showOn: {
				value: CLICK
			},

			/**
			 * If true the OverlayContextPanel will show an arrow positioned on the
			 * <a href="OverlayContextPanel.html#config_arrow">arrow</a> point.
			 *
			 * @attribute showArrow
			 * @default true
			 * @type boolean
			 */
			showArrow: {
				lazyAdd: false,
				value: true,
				validator: isBoolean
			},

			/**
			 * Gives stacking habilities to the OverlayContextPanel.
			 *
			 * @attribute stack
			 * @default true
			 * @type boolean
			 */
			stack: {
				lazyAdd: false,
				value: true,
				setter: function(v) {
					return this._setStack(v);
				},
				validator: isBoolean
			}
		},

		EXTENDS: A.OverlayContext,

		prototype: {
			/**
			 * Bind the events on the OverlayContextPanel UI. Lifecycle.
			 *
			 * @method bindUI
			 * @protected
			 */
			bindUI: function() {
				var instance = this;

				instance.after('showArrowChange', instance._afterShowArrowChange);

				instance.before('show', instance._beforeShow);

				OverlayContextPanel.superclass.bindUI.apply(instance, arguments);
			},

			/**
			 * Create the DOM structure for the OverlayContextPanel. Lifecycle.
			 *
			 * @method renderUI
			 * @protected
			 */
			renderUI: function() {
				var instance = this;

				instance._renderElements();
			},

			/**
			 * Sync the OverlayContextPanel UI. Lifecycle.
			 *
			 * @method syncUI
			 * @protected
			 */
			syncUI: function() {
				var instance = this;

				OverlayContextPanel.superclass.syncUI.apply(instance, arguments);

				instance._syncElements();
			},

			/**
			 * Aligns the OverlayContextPanel to the provided node (or viewport) using the
			 * provided points. Inherited from
			 * <a href="Overlay.html#method_align">Overlay</a>.
			 *
			 * @method align
			 * @param {Node | String | null} node A reference (or selector string) for
			 * the Node which with the OverlayContextPanel is to be aligned.
			 * @param {Array[2]} points A two element array, specifying the points on
			 * the OverlayContextPanel and node/viewport which need to be aligned.
			 */
			align: function (node, points) {
				var instance = this;

				OverlayContextPanel.superclass.align.apply(this, arguments);

				instance._syncElements();
			},

			/**
			 * OverlayContextPanel uses a imageless arrow, which involves some CSS technics.
			 * This method is meant to fix the color of the borders of the arrow.
			 *
			 * @method fixPointerColor
			 */
			fixPointerColor: function() {
				var instance = this;
				var contentBox = instance.get(CONTENT_BOX);
				var pointer = contentBox.one(DOT+CSS_CONTEXTPANEL_POINTER_INNER);

				pointer.removeAttribute(STYLE);

				var bColor = contentBox.getStyle(BACKGROUND_COLOR);
				var border = 'borderBottomColor';

				var right = [
					DOT+CSS_CONTEXTPANEL_ARROW+RB,
						DOT+CSS_CONTEXTPANEL_ARROW+RC,
							DOT+CSS_CONTEXTPANEL_ARROW+RL
				]
				.join(',');

				var bottom = [
					DOT+CSS_CONTEXTPANEL_ARROW+BR,
						DOT+CSS_CONTEXTPANEL_ARROW+BC,
							DOT+CSS_CONTEXTPANEL_ARROW+BL
				]
				.join(',');

				var left = [
					DOT+CSS_CONTEXTPANEL_ARROW+LB,
						DOT+CSS_CONTEXTPANEL_ARROW+LC,
							DOT+CSS_CONTEXTPANEL_ARROW+LT
				]
				.join(',');

				if (contentBox.test(right)) {
					border = 'borderLeftColor';
				}
				else if (contentBox.test(bottom)) {
					border = 'borderTopColor';
				}
				else if (contentBox.test(left)) {
					border = 'borderRightColor';
				}

				pointer.setStyle(border, bColor);
			},

			/**
			 * Normalize the align point value. The align point 'cc' is not a valid
			 * position for the arrow and then it's normalized to the 'bc' point.
			 *
			 * @method getAlignPoint
			 * @return {String}
			 */
			getAlignPoint: function() {
				var instance = this;
				var overlayPoint = instance.get(ALIGN).points[0];

				if (overlayPoint == CC) {
					// CC is not a valid position for the arrow
					overlayPoint = BC;
				}

				return instance.get(ARROW) || overlayPoint;
			},

			/**
			 * Hides the OverlayContextPanel.
			 *
			 * @method hide
			 * @param {EventFacade} event
			 */
			hide: function(event) {
				var instance = this;

				if(instance._hideAnim) {
					var visible = instance.get(VISIBLE);

					if (visible) {
						instance._hideAnim.once(END, function() {
							OverlayContextPanel.superclass.hide.apply(instance, arguments);
						});

						instance._hideAnim.run();
					}
				}
				else {
					OverlayContextPanel.superclass.hide.apply(instance, arguments);
				}
			},

			/**
			 * Render DOM elements for the OverlayContextPanel.
			 *
			 * @method _renderElements
			 * @protected
			 */
			_renderElements: function() {
				var instance = this;
				var contentBox = instance.get(CONTENT_BOX);
				var align = instance.get(ALIGN);
				var overlayPoint = align.points[0];

				contentBox.addClass(CSS_STATE_DEFAULT);

				instance._pointerNode = A.Node.create(TPL_POINTER).append(TPL_POINTER_INNER);

				contentBox.append(
					instance._pointerNode
				);
			},

			/**
			 * Sync the UI of the OverlayContextPanel elements.
			 *
			 * @method _syncElements
			 * @protected
			 */
			_syncElements: function() {
				var instance = this;
				var contentBox = instance.get(CONTENT_BOX);
				var pointerNode = instance._pointerNode;
				var overlayPoint = instance.getAlignPoint();

				if (instance.get(SHOW_ARROW)) {
					pointerNode.removeClass(CSS_CONTEXTPANEL_HIDDEN);
					contentBox.removeClass(CSS_CONTEXTPANEL_ARROW + instance._lastOverlayPoint);
					contentBox.addClass(CSS_CONTEXTPANEL_ARROW + overlayPoint);

					instance.fixPointerColor();
				}
				else {
					pointerNode.addClass(CSS_CONTEXTPANEL_HIDDEN);
				}

				instance._lastOverlayPoint = overlayPoint;
			},

			/**
			 * Setter for the
			 * <a href="OverlayContextPanel.html#config_stack">stack</a> attribute.
			 *
			 * @method _setStack
			 * @param {boolean} value
			 * @protected
			 * @return {boolean}
			 */
			_setStack: function(value) {
				var instance = this;

				if (value) {
					A.OverlayContextPanelManager.register(instance);
				}
				else {
					A.OverlayContextPanelManager.remove(instance);
				}

				return value;
			},

			/**
			 * Setter for the
			 * <a href="OverlayContextPanel.html#config_anim">anim</a> attribute.
			 *
			 * @method _setAnim
			 * @param {Object} value
			 * @protected
			 * @return {Object}
			 */
			_setAnim: function(value) {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);

				if (value) {
					var defaults = {
						node: boundingBox,
						duration: 0.1
					};

					var showOptions = A.merge(defaults, {
						from: { opacity: 0 },
						to: { opacity: 1 }
					});

					var hideOptions = A.merge(defaults, {
						from: { opacity: 1 },
						to: { opacity: 0 }
					});

					if (isObject(value)) {
						// loading user settings for animation
						showOptions = A.merge(showOptions, value.show);
						hideOptions = A.merge(hideOptions, value.hide);
					}

					instance._showAnim = new A.Anim(showOptions);
					instance._hideAnim = new A.Anim(hideOptions);

					// if anim.show or anim.hide === false, cancel respective animation
					if (isObject(value)) {
						if (value.show === false) {
							instance._showAnim = null;
						}

						if (value.hide === false) {
							instance._hideAnim = null;
						}
					}
				}

				return value;
			},

			/**
			 * Fires before show the OverlayContextPanel.
			 *
			 * @method _beforeShow
			 * @param {EventFacade} event
			 * @protected
			 */
			_beforeShow: function(event) {
				var instance = this;
				var boundingBox = instance.get(BOUNDING_BOX);
				var visible = instance.get(VISIBLE);

				if(!visible && instance._showAnim) {
					boundingBox.setStyle(OPACITY, 0);

					instance._showAnim.run();
				}
				else {
					boundingBox.setStyle(OPACITY, 1);
				}
			},

			/**
			 * Fires after showArrow attribute changes.
			 *
			 * @method _afterShowArrowChange
			 * @param {EventFacade} event
			 * @protected
			 */
			_afterShowArrowChange: function() {
				var instance = this;

				instance._syncElements();
			}
		}
	}
);

A.OverlayContextPanel = OverlayContextPanel;

/**
 * A base class for OverlayContextPanelManager:
 *
 * @param config {Object} Object literal specifying widget configuration properties.
 *
 * @class OverlayContextPanelManager
 * @constructor
 * @extends OverlayManager
 * @static
 */
A.OverlayContextPanelManager = new A.OverlayManager({
	zIndexBase: 1000
});