/**
 * @ignore
 *  plugin constrain region for drag and drop
 * @author yiminghe@gmail.com
 */
KISSY.add('dd/plugin/constrain', function (S, Base, Node) {

    var $ = Node.all,
        CONSTRAIN_EVENT = '.-ks-constrain' + S.now(),
        WIN = S.Env.host;

    /**
     * @class KISSY.DD.Plugin.Constrain
     * @extends KISSY.Base
     * Constrain plugin to provide ability to constrain draggable to specified region
     */
    function Constrain() {
        Constrain.superclass.constructor.apply(this, arguments);
    }

    function onDragStart(e) {
        var self = this,
            drag = e.drag,
            l, t, lt,
            dragNode = drag.get('dragNode'),
            constrain = self.get('constrain');
        if (constrain) {
            if (S.isWindow(constrain[0])) {
                self.__constrainRegion = {
                    left: l = constrain.scrollLeft(),
                    top: t = constrain.scrollTop(),
                    right: l + constrain.width(),
                    bottom: t + constrain.height()
                };
            }
            else if (constrain.getDOMNode) {
                lt = constrain.offset();
                self.__constrainRegion = {
                    left: lt.left,
                    top: lt.top,
                    right: lt.left + constrain.outerWidth(),
                    bottom: lt.top + constrain.outerHeight()
                };
            } else if (S.isPlainObject(constrain)) {
                self.__constrainRegion = constrain;
            }
            if (self.__constrainRegion) {
                self.__constrainRegion.right -= dragNode.outerWidth();
                self.__constrainRegion.bottom -= dragNode.outerHeight();
            }
        }
    }

    function onDragAlign(e) {
        var self = this,
            info = {},
            l = e.left,
            t = e.top,
            constrain = self.__constrainRegion;
        if (constrain) {
            info.left = Math.min(Math.max(constrain.left, l), constrain.right);
            info.top = Math.min(Math.max(constrain.top, t), constrain.bottom);
            e.drag.setInternal('actualPos', info);
        }
    }

    function onDragEnd() {
        this.__constrainRegion = null;
    }

    S.extend(Constrain, Base, {

        pluginId: 'dd/plugin/constrain',

        __constrainRegion: null,

        /**
         * start monitoring drag
         * @param {KISSY.DD.Draggable} drag
         * @private
         */
        pluginInitializer: function (drag) {
            var self = this;
            drag['on']('dragstart' + CONSTRAIN_EVENT, onDragStart, self)
                .on('dragend' + CONSTRAIN_EVENT, onDragEnd, self)
                .on('dragalign' + CONSTRAIN_EVENT, onDragAlign, self);
        },

        /**
         * stop monitoring drag
         * @param {KISSY.DD.Draggable} drag
         * @private
         */
        pluginDestructor: function (drag) {
            drag['detach'](CONSTRAIN_EVENT, {
                context: this
            });
        }
    }, {
        ATTRS: {
            /**
             * constrained container.
             * @type {Boolean|HTMLElement|String}
             * @property constrain
             */

            /**
             * constrained container. true stands for viewport.
             * Defaults: true.
             * @cfg {Boolean|HTMLElement|String} constrain
             */

            /**
             * @ignore
             */
            constrain: {
                value: WIN,
                setter: function (v) {
                    if (v) {
                        if (v === true) {
                            return $(WIN);
                        } else if (v.nodeType || S.isWindow(v) || typeof v == 'string') {
                            return $(v);
                        }
                    }
                }
            }
        }
    });

    return Constrain;
}, {
    requires: ['base', 'node']
});