YUI.add('aui-widget-position-align-suggestion', function (A, NAME) {

var getClassName = A.getClassName;

/**
 * Widget extension, which can be used to suggest alignment points based on
 * position attribute to base Widget class, through the
 * [Base.build](Base.html#method_build) method. It also tries to find
 * the best position in case the widget doesn't fit it's constrainment node.
 *
 * @class A.WidgetPositionAlignSuggestion
 * @param {Object} The user configuration object
 */

function PositionAlignSuggestion() {}

/**
 * Static property used to define the default attribute
 * configuration.
 *
 * @property ATTRS
 * @type Object
 * @static
 */
PositionAlignSuggestion.ATTRS = {
    /**
     * Determine the position of the tooltip.
     *
     * @attribute position
     * @default top
     * @type {String}
     */
    position: {
        getter: '_getPosition',
        validator: '_validatePosition',
        value: 'top'
    }
};

A.mix(PositionAlignSuggestion.prototype, {
    /**
     * Property defining the align points based on the suggested `position`.
     *
     * @property POSITION_ALIGN_SUGGESTION
     * @type {}
     */
    POSITION_ALIGN_SUGGESTION: {
        bottom: [A.WidgetPositionAlign.TC, A.WidgetPositionAlign.BC],
        left: [A.WidgetPositionAlign.RC, A.WidgetPositionAlign.LC],
        right: [A.WidgetPositionAlign.LC, A.WidgetPositionAlign.RC],
        top: [A.WidgetPositionAlign.BC, A.WidgetPositionAlign.TC]
    },

    _hasAlignmentPoints: false,

    _lastPosition: null,

    /**
     * Construction logic executed during WidgetPositionAlignSuggestion
     * instantiation. Lifecycle.
     *
     * @method initializer
     * @protected
     */
    initializer: function(config) {
        var instance = this;

        if (config && config.align && config.align.points) {
            instance._hasAlignmentPoints = true;
            instance._setPositionAccordingPoints();
        }

        A.on(instance._onUISetAlignPAS, instance, '_uiSetAlign');

        A.after(instance._afterRenderUIPAS, instance, 'renderUI');

        instance.after('positionChange', instance._afterPositionChangePAS);
    },

    /**
     * Suggest alignment for the node based on the `position` suggestion.
     *
     * @method suggestAlignment
     * @attribute alignNode
     */
    suggestAlignment: function(alignNode) {
        var instance = this,
            align;

        align = instance.get('align') || {};

        if (alignNode) {
            align.node = alignNode;
        }

        if (!instance._hasAlignmentPoints) {
            align.points = instance._getAlignPointsSuggestion(
                instance.get('position'));
        }

        instance.set('align', align);
    },

    /**
     * Fire after `boundingBox` position changes.
     *
     * @method _afterPositionChangePAS
     * @param event
     * @protected
     */
    _afterPositionChangePAS: function(event) {
        var instance = this;

        instance._uiSetPosition(event.newVal, event.prevVal);
    },

    /**
     * Fire after `renderUI` method.
     *
     * @method _afterRenderUIPAS
     * @param event
     * @protected
     */
    _afterRenderUIPAS: function() {
        var instance = this;

        instance._uiSetPosition(instance.get('position'));
    },

    /**
     * Returns true if the widget can fit inside it's constrainment node.
     *
     * @method _canWidgetAlignToNode
     * @param node
     * @param position
     * @protected
     */
    _canWidgetAlignToNode: function(node, position) {
        var instance = this,
            constrainedXY,
            points = instance._getAlignPointsSuggestion(position),
            xy = instance._getAlignedXY(node, points);

        constrainedXY = instance.getConstrainedXY(xy);

        return (constrainedXY[0] === xy[0] && constrainedXY[1] === xy[1]);
    },

    /**
     * Finds the position in which the widget fits without having to have its
     * coordinates changed due to its constrainment node.
     *
     * @method _findBestPosition
     * @param node
     * @protected
     */
    _findBestPosition: function(node) {
        var instance = this,
            position = instance.get('position'),
            testPositions = [position, 'top', 'bottom', 'right', 'left'],
            trigger = A.one(node);

        if (trigger && !trigger.inViewportRegion()) {
            return instance._findBestPositionOutsideViewport(trigger);
        } else {
            testPositions = A.Array.dedupe(testPositions);

            A.Array.some(testPositions, function(testPosition) {
                if (instance._canWidgetAlignToNode(trigger, testPosition)) {
                    position = testPosition;
                    return true;
                }
            });
        }

        return position;
    },

    /**
     * Finds the better widget's position when its anchor is outside
     * the view port.
     *
     * @method _findBestPositionOutsideViewport
     * @param node
     * @protected
     */
    _findBestPositionOutsideViewport: function(node) {
        var instance = this,
            nodeRegion = instance._getRegion(node),
            region = instance._getRegion();

        if (nodeRegion.top < region.top) {
            return 'bottom';
        }
        else if (nodeRegion.bottom > region.bottom) {
            return 'top';
        }
        else if (nodeRegion.right > region.right) {
            return 'left';
        }
        else if (nodeRegion.left < region.left) {
            return 'right';
        }
    },

    /**
     * Guess alignment points for the `position`.
     *
     * @method _getAlignPointsSuggestion
     * @attribute position
     * @protected
     */
    _getAlignPointsSuggestion: function(position) {
        return this.POSITION_ALIGN_SUGGESTION[position];
    },

    /**
     * Set the `position` attribute.
     *
     * @method _getPosition
     * @param {Number} val
     * @protected
     */
    _getPosition: function(val) {
        if (A.Lang.isFunction(val)) {
            val = val.call(this);
        }

        return val;
    },

    /**
     * Fire before `_uiSetAlign` method.
     *
     * @method _onUISetAlignPAS
     * @param node
     * @protected
     */
    _onUISetAlignPAS: function(node) {
        var instance = this,
            position;

        if (!instance.get('constrain')) {
            return;
        }

        position = instance._findBestPosition(node);

        instance._syncPositionUI(
            position, instance._lastPosition || instance.get('position'));

        instance._lastPosition = position;

        return new A.Do.AlterArgs(
            null, [node, instance._getAlignPointsSuggestion(position)]);
    },

    /**
     * Sets the position according to the align points initially defined.
     *
     * @method _setPositionAccordingPoints
     * @protected
     */
    _setPositionAccordingPoints: function() {
        var instance = this,
            points = instance.get('align').points;

        A.Object.some(instance.POSITION_ALIGN_SUGGESTION, function(value, key) {
            if (points[0] === value[0] && points[1] === value[1]) {
                instance.set('position', key);
                return true;
            }
        });
    },

    /**
     * Sync the `boundingBox` position CSS classes.
     *
     * @method _syncPositionUI
     * @param val
     * @param prevVal
     * @protected
     */
    _syncPositionUI: function(val, prevVal) {
        var instance = this,
            boundingBox = instance.get('boundingBox');

        if (prevVal) {
            boundingBox.removeClass(getClassName(prevVal));
        }
        boundingBox.addClass(getClassName(val));
    },

    /**
     * Set the `boundingBox` position on the UI.
     *
     * @method _uiSetPosition
     * @param val
     * @param prevVal
     * @protected
     */
    _uiSetPosition: function(val, prevVal) {
        var instance = this;

        instance._syncPositionUI(val, prevVal);

        instance.suggestAlignment();
    },

    /**
     * Validates the value of `position` attribute.
     *
     * @method _validatePosition
     * @param value
     * @protected
     * @return {Boolean} True only if value is 'bottom', 'top', 'left'
     *     or 'right'.
     */
    _validatePosition: function(val) {
        if (A.Lang.isFunction(val)) {
            val = val.call(this);
        }

        return (val === 'bottom' || val === 'top' || val === 'left' || val === 'right');
    }
});

A.WidgetPositionAlignSuggestion = PositionAlignSuggestion;

}, '3.1.0-deprecated.112', {"requires": ["widget-position-align", "widget-stdmod"]});
