YUI.add('aui-options-data-editor', function (A, NAME) {

/**
 * The Options Data Editor Component
 *
 * @module aui-options-data-editor
 */

var CSS_EDITOR = A.getClassName('options', 'data', 'editor'),
    CSS_EDITOR_ADD = A.getClassName('options', 'data', 'editor', 'add'),
    CSS_EDITOR_OPTION = A.getClassName('options', 'data', 'editor', 'option'),
    CSS_EDITOR_OPTION_HANDLE = A.getClassName('options', 'data', 'editor', 'option', 'handle'),
    CSS_EDITOR_OPTION_REMOVE = A.getClassName('options', 'data', 'editor', 'option', 'remove'),
    CSS_EDITOR_OPTION_TEXT = A.getClassName('options', 'data', 'editor', 'option', 'text'),
    CSS_EDITOR_OPTIONS = A.getClassName('options', 'data', 'editor', 'options'),

    SOURCE_UI = 'ui';

/**
 * A base class for Options Data Editor.
 *
 * @class A.OptionsDataEditor
 * @extends A.DataEditor
 * @param {Object} config Object literal specifying widget configuration
 *     properties.
 * @constructor
 */
A.OptionsDataEditor = A.Base.create('options-data-editor', A.DataEditor, [], {
    TPL_EDITOR_CONTENT: '<div class="' + CSS_EDITOR + '">' +
        '<div class="' + CSS_EDITOR_OPTIONS + '"></div>' +
        '<button class="' + CSS_EDITOR_ADD + '">{addOption}</button></div>',
    TPL_EDITOR_OPTION: '<div class="' + CSS_EDITOR_OPTION + '">' +
        '<span class="' + CSS_EDITOR_OPTION_HANDLE + ' glyphicon glyphicon-sort"></span>' +
        '<input class="' + CSS_EDITOR_OPTION_TEXT + ' type="text"" value="{text}"></input>' +
        '<button class="' + CSS_EDITOR_OPTION_REMOVE + '">X</button></div>',

    /**
     * Constructor for the `A.OptionsDataEditor`. Lifecycle.
     *
     * @method initializer
     * @protected
     */
    initializer: function() {
        var node = this.get('node');

        this._uiSetEditedValue(this.get('editedValue'));

        node.one('.' + CSS_EDITOR_ADD).after('click', A.bind(this._afterClickAddButton, this));
        node.delegate('click', A.bind(this._onClickRemoveButton, this), '.' + CSS_EDITOR_OPTION_REMOVE);
        node.delegate('valuechange', A.bind(this._onValueChange, this), '.' + CSS_EDITOR_OPTION_TEXT);
        this.after('editedValueChange', this._afterEditedValueChange);

        this._setUpDrag();
    },

    /**
     * Returns `true` if this edited value has no elements.
     *
     * @method isEmpty
     * @return {Boolean}
     */
    isEmpty: function() {
        return !this.get('editedValue').length;
    },

    /**
     * If the Option Data Editor has a String in each option field  this will return true.
     *
     * @method isValid
     * @return {Boolean}
     */
    isValid: function() {
        var instance = this,
            i,
            options = this.get('editedValue'),
            optionsLength = options.length;

        if (A.OptionsDataEditor.superclass.isValid.call(instance)) {
            for (i = 0; i < optionsLength; i++) {
                if (!A.Lang.trim(options[i])) {
                    return false;
                }
            }
            return true;
        }
        else {
            return false;
        }
    },

    /**
     * Fired after the button for adding options is clicked.
     *
     * @method _afterClickAddButton
     * @protected
     */
    _afterClickAddButton: function() {
        var editedValue = this.get('editedValue'),
            optionsContainer = this.get('node').one('.' + CSS_EDITOR_OPTIONS);

        optionsContainer.append(this._createOptionNode(''));

        editedValue.push('');
        this.set('editedValue', editedValue, {
            src: SOURCE_UI
        });
    },

    /**
     * Fired after the `editedValue` attribute is set.
     *
     * @method _afterEditedValueChange
     * @param {CustomEvent} event The fired event
     * @protected
     */
    _afterEditedValueChange: function(event) {
        if (event.src !== SOURCE_UI) {
            this._uiSetEditedValue(this.get('editedValue'));
        }
    },

    /**
     * Creates an option node.
     *
     * @method _createOptionNode
     * @param {String} text
     * @return {Node}
     * @protected
     */
    _createOptionNode: function(text) {
        var optionNode = A.Node.create(A.Lang.sub(this.TPL_EDITOR_OPTION, {
            text: text
        }));

        optionNode.plug(A.Plugin.Drop);
        return optionNode;
    },

    /**
     * Gets the option node's index in the options list.
     *
     * @method _getOptionNodeIndex
     * @param {Node} node
     * @protected
     */
    _getOptionNodeIndex: function(node) {
        var optionNode = node.ancestor('.' + CSS_EDITOR_OPTION, true),
            optionNodes = this.get('node').all('.' + CSS_EDITOR_OPTION);

        return optionNodes.indexOf(optionNode);
    },

    /**
     * Moves the item from an index to another in the items array.
     *
     * @method _moveItem
     * @param {Number} from
     * @param {Number} to
     * @protected
     */
    _moveItem: function(from, to) {
        var editedValue = this.get('editedValue'),
            item = editedValue[from];

        editedValue.splice(from, 1);
        if (from < to) {
            to--;
        }
        editedValue.splice(to, 0, item);

        this.set('editedValue', editedValue, {
            src: SOURCE_UI
        });
    },

    /**
     * Fired when a button for removing an option is clicked.
     *
     * @method _onClickRemoveButton
     * @param {EventFacade} event
     * @protected
     */
    _onClickRemoveButton: function(event) {
        var editedValue = this.get('editedValue'),
            index = this._getOptionNodeIndex(event.currentTarget),
            optionNode = event.currentTarget.ancestor('.' + CSS_EDITOR_OPTION);

        this._removeOptionNode(optionNode);

        editedValue.splice(index, 1);
        this.set('editedValue', editedValue, {
            src: SOURCE_UI
        });
    },

    /**
     * Fired when the value of one of the editors text fields changes.
     *
     * @method _onValueChange
     * @param {EventFacade} event
     * @protected
     */
    _onValueChange: function(event) {
        var editedValue = this.get('editedValue'),
            index = this._getOptionNodeIndex(event.currentTarget);

        editedValue[index] = event.currentTarget.get('value');
        this.set('editedValue', editedValue, {
            src: SOURCE_UI
        });
    },

    /**
     * Fired after the `drag:drag` event is triggered.
     *
     * @method _onDrag
     * @param {EventFacade} event
     * @protected
     */
    _onDrag: function(event) {
        if (event.target.lastXY[1] < this._lastY) {
            this._draggingUp = true;
        } else {
            this._draggingUp = false;
        }

        this._lastY = event.target.lastXY[1];
    },

    /**
     * Fired after the `drag:over` event is triggered.
     *
     * @method _onDragOver
     * @param {EventFacade} event
     * @protected
     */
    _onDragOver: function(event) {
        var dragNode = event.drag.get('node'),
            dropNode = event.drop.get('node'),
            indexDrag = this._getOptionNodeIndex(dragNode),
            indexDrop;

        if (!this._draggingUp) {
            dropNode = dropNode.get('nextSibling');
        }
        indexDrop = this._getOptionNodeIndex(dropNode);

        event.drop.get('node').get('parentNode').insertBefore(dragNode, dropNode);
        event.drop.sizeShim();

        this._moveItem(indexDrag, indexDrop);
    },

    /**
     * Removes the given option node.
     *
     * @method _removeOptionNode
     * @param {Node} optionNode
     * @protected
     */
    _removeOptionNode: function(optionNode) {
        optionNode.unplug(A.Plugin.Drop);
        optionNode.remove();
    },

    /**
     * Sets up everything needed for dragging options to different positions.
     *
     * @method _setUpDrag
     * @protected
     */
    _setUpDrag: function() {
        this._delegateDrag = new A.DD.Delegate({
            container: this.get('node'),
            handles: ['.' + CSS_EDITOR_OPTION_HANDLE],
            nodes: '.' + CSS_EDITOR_OPTION
        });
        this._delegateDrag.dd.plug(A.Plugin.DDConstrained, {
            stickY: true
        });
        this._delegateDrag.dd.plug(A.Plugin.DDProxy, {
            cloneNode: true,
            moveOnEnd: false
        });

        this._delegateDrag.after('drag:drag', A.bind(this._onDrag, this));
        this._delegateDrag.after('drag:over', A.bind(this._onDragOver, this));
    },

    /**
     * Updates the ui according to the value of the `editedValue` attribute.
     *
     * @method _uiSetEditedValue
     * @param {Array} editedValue
     * @protected
     */
    _uiSetEditedValue: function(editedValue) {
        var instance = this,
            optionsContainer = this.get('node').one('.' + CSS_EDITOR_OPTIONS);

        optionsContainer.all('.' + CSS_EDITOR_OPTION).each(function(optionNode) {
            instance._removeOptionNode(optionNode);
        });

        A.Array.each(editedValue, function(option) {
            optionsContainer.append(instance._createOptionNode(option));
        });
    }
}, {
    /**
     * Static property used to define the default attribute configuration
     * for the `A.OptionsDataEditor`.
     *
     * @property ATTRS
     * @type Object
     * @static
     */
    ATTRS: {
        /**
         * The value after edition.
         *
         * @attribute editedValue
         * @default []
         * @type Boolean
         */
        editedValue: {
            value: []
        },

        /**
         * The value to be edited.
         *
         * @attribute originalValue
         * @default []
         * @type Array
         */
        originalValue: {
            value: []
        },

        /**
         * Collection of strings used to label elements of the UI.
         *
         * @attribute strings
         * @type {Object}
         */
        strings: {
            value: {
                addOption: 'Tap to add an option',
                required: 'REQUIRED'
            }
        }
    }
});


}, '3.1.0-deprecated.112', {
    "requires": [
        "aui-data-editor",
        "dd-constrain",
        "dd-delegate",
        "dd-drop-plugin",
        "dd-proxy",
        "event-valuechange",
        "node-event-delegate"
    ],
    "skinnable": true
});
