YUI.add('aui-layout-builder-move', function (A, NAME) {

/**
 * The Layout Move Component
 *
 * @module aui-layout-move
 */

var CSS_MOVING = A.getClassName('layout', 'builder', 'moving'),
    CSS_MOVE_CANCEL = A.getClassName('layout', 'builder', 'move', 'cancel'),
    CSS_MOVE_COL_TARGET = A.getClassName('layout', 'builder', 'move', 'col', 'target'),
    CSS_MOVE_CUT_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'button'),
    CSS_MOVE_CUT_ROW_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'row', 'button'),
    CSS_MOVE_CUT_COL_BUTTON = A.getClassName('layout', 'builder', 'move', 'cut', 'col', 'button'),
    CSS_MOVE_ROW_TARGET = A.getClassName('layout', 'builder', 'move', 'row', 'target'),
    CSS_MOVE_TARGET = A.getClassName('layout', 'builder', 'move', 'target'),
    CSS_MOVE_TARGET_LABEL = A.getClassName('layout', 'builder', 'move', 'target', 'label'),
    CSS_ROW_CONTAINER_ROW = A.getClassName('layout', 'row', 'container', 'row'),

    SELECTOR_COL = '.col',
    SELECTOR_ROW = '.layout-row',

    TPL_MOVE_CUT = '<div class="' + CSS_MOVE_CUT_BUTTON + '" tabindex="0"></div>',
    TPL_MOVE_TARGET = '<div class="' + CSS_MOVE_TARGET + '"><span class="' + CSS_MOVE_TARGET_LABEL + '">{pasteHere}<span></div>';

/**
 * A base class for Layout Move.
 *
 * @class A.LayoutBuilderMove
 * @param {Object} config Object literal specifying layout configuration
 *     properties.
 * @constructor
 */
function LayoutBuilderMove() {}

LayoutBuilderMove.prototype = {

    /**
     * Col that will be moved.
     *
     * @property _colToBeMoved
     * @type {Node}
     * @protected
     */
    _colToBeMoved: null,

    /**
     * Row that will be moved.
     *
     * @property _rowToBeMoved
     * @type {Node}
     * @protected
     */
    _rowToBeMoved: null,

    /**
     * Construction logic executed during instantiation.
     * Lifecycle.
     *
     * @method initializer
     * @protected
     */
    initializer: function() {
        this._eventHandles.push(
            this.after('enableMoveColsChange', A.bind(this._afterEnableMoveColsChange, this)),
            this.after('enableMoveRowsChange', A.bind(this._afterEnableMoveRowsChange, this)),
            this.after('layout-row:colsChange', A.bind(this._afterMoveColsChange, this)),
            this.after('layout-row:movableChange', A.bind(this._afterMoveMovableChange, this)),
            this.after('layout:rowsChange', A.bind(this._afterMoveRowsChange, this)),
            this.after('layout:isColumnModeChange', A.bind(this._afterMoveIsColumnModeChange, this)),
            this.after('layoutChange', A.bind(this._afterMoveLayoutChange, this)),
            this.after('moveStart', A.bind(this._addMovingClass, this)),
            this.after('moveEnd', A.bind(this._afterMoveEnd, this)),
            A.one('doc').on('key', this._onEscKey, 'esc', this)
        );

        this._uiSetEnableMoveRows(this.get('enableMoveRows'));
        this._uiSetEnableMoveCols(this.get('enableMoveCols'));
    },

    /**
     * Destructor implementation for the `A.LayoutBuilderMove` class. Lifecycle.
     *
     * @method destructor
     * @protected
     */
    destructor: function() {
        this._unbindMoveColEvents();
        this._unbindMoveRowEvents();
    },

    /**
     * Cancels the ongoing move operation.
     *
     * @method cancelMove
     */
    cancelMove: function() {
        this.fire('moveEnd');
    },

    /**
     * Default value for `addColMoveButton` attribute.
     *
     * @method _addColMoveButton
     * @param {Node} colNode
     * @param {Node} rowNode
     * @protected
     */
    _addColMoveButton: function(colNode, rowNode) {
        var cutButton = A.Node.create(TPL_MOVE_CUT);

        cutButton.setData('node-row', rowNode);
        cutButton.setData('node-col', colNode);
        cutButton.addClass(CSS_MOVE_CUT_COL_BUTTON);
        colNode.append(cutButton);
    },

    /**
     * Default value for `addColMoveTarget` attribute.
     *
     * @method _addColMoveTarget
     * @param {A.LayoutCol} col
     * @param {Number} index
     * @protected
     */
    _addColMoveTarget: function(col, index) {
        var target = A.Node.create(A.Lang.sub(TPL_MOVE_TARGET, {
            pasteHere: this.get('strings').pasteHere
        }));

        target.setData('col-index', index);
        target.addClass(CSS_MOVE_COL_TARGET);
        col.get('node').append(target);
    },

    /**
     * Adds CSS class to indicate that any element is moving.
     *
     * @method _addMovingClass
     * @protected
     */
    _addMovingClass: function() {
        this._layoutContainer.addClass(CSS_MOVING);
    },

    /**
     * Fired after the `enableMoveCols` attribute changes.
     *
     * @method _afterEnableMoveColsChange
     * @protected
     */
    _afterEnableMoveColsChange: function() {
        this._resetMoveUI();
    },

    /**
     * Fired after the `enableMoveRows` attribute changes.
     *
     * @method _afterEnableMoveRowsChange
     * @protected
     */
    _afterEnableMoveRowsChange: function() {
        this._resetMoveUI();
    },

    /**
    * Fired after `layout-row:movable` attribute changes.
    *
    * @method _afterMoveMovableChange
    * @param {EventFacade} event
    * @protected
    */
    _afterMoveMovableChange: function(event) {
        var row = event.target.get('node');

        if (event.newVal) {
            this._insertCutButtonOnRow(row.one(SELECTOR_ROW));
        }
        else {
            this._removeCutButtonFromRow(row);
        }
    },

    /**
     * Fired after `cols` attribute changes.
     *
     * @method _afterMoveColsChange
     * @protected
     */
    _afterMoveColsChange: function() {
        this._resetMoveUI();
    },

    /**
    * Fired after `layout:isColumnMode` attribute changes.
    *
    * @method _afterMoveIsColumnModeChange
    * @param {EventFacade} event
    * @protected
    */
    _afterMoveIsColumnModeChange: function(event) {
        var instance = this,
            rows = this._layoutContainer.all(SELECTOR_ROW);

        if (event.newVal) {
            rows.each(function(row) {
                if (instance._hasAnythingMovable(row)) {
                    instance._insertCutButtonOnCols(row);
                }
            });
        }
        else {
            this.get('removeColMoveButtons')();
        }
    },

    /**
     * Fired after `layout` attribute changes.
     *
     * @method _afterMoveLayoutChange
     * @protected
     */
    _afterMoveLayoutChange: function() {
        this._resetMoveUI();
    },

    /**
     * Fired after `rows` attribute changes.
     *
     * @method _afterMoveRowsChange
     * @protected
     */
    _afterMoveRowsChange: function() {
        this._resetMoveUI();
    },

    /**
     * Appends one move button before each row.
     *
     * @method _appendMoveButtonToRows
     * @protected
     */
    _appendMoveButtonToRows: function() {
        var instance = this,
            rows = this._layoutContainer.all(SELECTOR_ROW);

        rows.each(function(row) {
            if (instance._hasAnythingMovable(row)) {
                instance._insertCutButtonOnRow(row);
            }
        });
    },

    /**
     * Appends a move button for each col.
     *
     * @method _appendMoveButtonToCols
     * @protected
     */
    _appendMoveButtonToCols: function() {
        var instance = this,
            rows = this._layoutContainer.all(SELECTOR_ROW);

        rows.each(function(row) {
            if (instance._hasAnythingMovable(row)) {
                instance._insertCutButtonOnCols(row);
            }
        });
    },

    /**
     * Binds the necessary events for the functionality of moving cols on layout.
     *
     * @method _bindMoveColsEvents
     * @protected
     */
    _bindMoveColEvents: function() {
        var colCutButtonSelector = '.' + CSS_MOVE_CUT_BUTTON + '.' + CSS_MOVE_CUT_COL_BUTTON,
            colTargetSelector = '.' + CSS_MOVE_TARGET + '.' + CSS_MOVE_COL_TARGET;

        this._moveColEventHandles = [
            this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveCutButton, this), colCutButtonSelector),
            this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveCutButton, this), 'press:13', colCutButtonSelector),
            this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveTarget, this), colTargetSelector),
            this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveTarget, this), 'press:13', colTargetSelector)
        ];
    },

    /**
     * Binds the necessary events for the functionality of moving rows on layout.
     *
     * @method _bindMoveRowEvents
     * @protected
     */
    _bindMoveRowEvents: function() {
        var rowCutButtonSelector = '.' + CSS_MOVE_CUT_BUTTON + '.' + CSS_MOVE_CUT_ROW_BUTTON,
            rowTargetSelector = '.' + CSS_MOVE_TARGET + '.' + CSS_MOVE_ROW_TARGET;

        this._moveRowEventHandles = [
            this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveCutButton, this), rowCutButtonSelector),
            this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveCutButton, this), 'press:13', rowCutButtonSelector),
            this._layoutContainer.delegate('click', A.bind(this._onMouseClickOnMoveTarget, this), rowTargetSelector),
            this._layoutContainer.delegate('key', A.bind(this._onKeyPressOnMoveTarget, this), 'press:13', rowTargetSelector)
        ];
    },

    /**
     * Default value for `chooseColMoveTarget` attribute.
     *
     * @method _chooseColMoveTarget
     * @param {Node} cutButton
     * @param {A.LayoutCol} col
     * @protected
     */
    _chooseColMoveTarget: function(cutButton, col) {
        var instance = this,
            rows = this.get('layout').get('rows');

        this._colToBeMoved = col;

        A.Array.forEach(rows, function(row) {
            A.Array.forEach(row.get('cols'), function(col, index) {
                if (col !== instance._colToBeMoved) {
                    instance.get('addColMoveTarget')(col, index);
                }
            });
        });
    },

    /**
     * Default value for `clickColMoveTarget` attribute.
     *
     * @method _clickColMoveTarget
     * @param {Node} target
     * @protected
     */
    _clickColMoveTarget: function(target) {
        var row = target.ancestor(SELECTOR_ROW).getData('layout-row');

        this._resetMoveUI();
        row.moveColContent(target.getData('col-index'), this._colToBeMoved);
    },

    /**
     * Starts or cancels movement of rows and cols.
     *
     * @method _clickOnCutButton
     * @param {Node} cutButton
     * @protected
     */
    _clickOnCutButton: function(cutButton) {
        var itemToBeMoved = this._rowToBeMoved = cutButton.getData('layout-row');

        this._removeAllCutButton(cutButton);

        if (cutButton.hasClass(CSS_MOVE_CANCEL)) {
            this.cancelMove();
            return;
        }

        cutButton.toggleClass(CSS_MOVE_CANCEL);

        if (cutButton.hasClass(CSS_MOVE_CUT_ROW_BUTTON)) {
            this.get('chooseRowMoveTarget')();
        }
        else {
            itemToBeMoved = cutButton.getData('node-col').getData('layout-col');

            this.get('chooseColMoveTarget')(cutButton, cutButton.getData('node-col').getData('layout-col'));
        }

        this.fire('moveStart', {moveElement: itemToBeMoved});
    },

    /**
     * Default value for `chooseRowMoveTarget` attribute.
     *
     * @method _createRowTargetArea
     * @protected
     */
    _createRowTargetArea: function() {
        this._createRowTargetAreaInOneDirection('before');
        this._createRowTargetAreaInOneDirection('after');
    },

    /**
     * Create target area to move the row to one direction.
     *
     * @method _createRowTargetAreaInOneDirection
     * @param {String} direction Should be either 'before' or 'after'.
     * @protected
     */
    _createRowTargetAreaInOneDirection: function(direction) {
        var containerRow,
            currentIndex = A.Array.indexOf(this.get('layout').get('rows'), this._rowToBeMoved),
            currentRow,
            method = direction === 'before' ? 'previous' : 'next',
            target;

        containerRow = this._rowToBeMoved.get('node')[method]('.' + CSS_ROW_CONTAINER_ROW);
        while (containerRow) {
            currentRow = containerRow.one(SELECTOR_ROW);

            if (direction === 'before') {
                currentIndex -= 1;
            }
            else {
                currentIndex += 1;
            }

            if (currentRow.getData('layout-row').get('movable')) {
                target = A.Node.create(A.Lang.sub(TPL_MOVE_TARGET, {
                    pasteHere: this.get('strings').pasteHere
                }));
                target.addClass(CSS_MOVE_ROW_TARGET);
                target.setData('row-index', currentIndex);
                containerRow.insert(target, direction);
                containerRow = containerRow[method]('.' + CSS_ROW_CONTAINER_ROW);
            }
            else {
                break;
            }
        }
    },

    /**
     * Checks if the given row either can be moved or has at least one column
     * that can.
     *
     * @method _hasAnythingMovable
     * @param {A.LayoutRow} row
     * @return {Boolean}
     * @protected
     */
    _hasAnythingMovable: function(row) {
        var index,
            layoutCols,
            layoutRow = row.getData('layout-row');

        if (layoutRow.get('movable')) {
            return true;
        }

        layoutCols = layoutRow.get('cols');
        for (index = 0; index < layoutCols.length; index++) {
            if (layoutCols[index].get('movableContent')) {
                return true;
            }
        }

        return false;
    },

    /**
     * Inserts cut buttons on cols.
     *
     * @method _insertCutButtonOnCols
     * @param {Node} row
     * @protected
     */
    _insertCutButtonOnCols: function(row) {
        var instance = this,
            cols,
            layoutCol,
            rows = this._layoutContainer.all(SELECTOR_ROW);

        cols = row.all(SELECTOR_COL);

        if (!this.get('layout').get('isColumnMode')) {
            return;
        }

        if (cols.size() === 1 && rows.size() === 1) {
            return;
        }

        cols.each(function(col) {
            layoutCol = col.getData('layout-col');

            if (layoutCol.get('movableContent')) {
                instance.get('addColMoveButton')(col, row);
            }
        });
    },

    /**
     * Inserts cut button on a row.
     *
     * @method _insertCutButtonOnRow
     * @param {Node} row
     * @protected
     */
    _insertCutButtonOnRow: function(row) {
        var cutButton = A.Node.create(TPL_MOVE_CUT),
            layoutRow = row.getData('layout-row'),
            rows = this._layoutContainer.all(SELECTOR_ROW);

        if (rows.size() === 1) {
            return;
        }

        if (!layoutRow.get('movable')) {
            return;
        }

        cutButton.addClass(CSS_MOVE_CUT_ROW_BUTTON);
        cutButton.setData('node-row', row);
        cutButton.setData('layout-row', layoutRow);

        this._layoutContainer.insertBefore(cutButton, row);
    },

    /**
     * Move col or row to another place.
     *
     * @method _moveToTarget
     * @param {EventFacade} event
     * @protected
     */
    _moveToTarget: function(event) {
        var layout = this.get('layout'),
            target = event.currentTarget;

        if (target.hasClass(CSS_MOVE_COL_TARGET)) {
            this.get('clickColMoveTarget')(target);
        }
        else {
            layout.moveRow(target.getData('row-index'), this._rowToBeMoved);
        }

        this.fire('moveEnd', event);
    },

    /**
     * Fires when the esc key is pressed.
     *
     * @method _onEscKey
     * @protected
     */
    _onEscKey: function () {
        this.cancelMove();
    },

    /**
     * Fires when key press on cut button.
     *
     * @method _onKeyPressOnMoveCutButton
     * @param {EventFacade} event
     * @protected
     */
    _onKeyPressOnMoveCutButton: function(event) {
        event.preventDefault();
        this._clickOnCutButton(event.currentTarget);
    },

    /**
     * Fires when click on cut button.
     *
     * @method _onMouseClickOnMoveCutButton
     * @param {EventFacade} event
     * @protected
     */
    _onMouseClickOnMoveCutButton: function(event) {
        event.stopPropagation();
        this._clickOnCutButton(event.currentTarget);
    },

    /**
     * Fires when key press on target area.
     *
     * @method _onKeyPressOnMoveTarget
     * @param {EventFacade} event
     * @protected
     */
    _onKeyPressOnMoveTarget: function(event) {
        this._moveToTarget(event);
    },

    /**
     * Fires when click on target area.
     *
     * @method _onMouseClickOnMoveTarget
     * @param {EventFacade} event
     * @protected
     */
    _onMouseClickOnMoveTarget: function(event) {
        this._moveToTarget(event);
    },

    /**
     * Removes all cut buttons.
     *
     * @method _removeAllCutButton
     * @param {Node} cutButton
     * @protected
     */
    _removeAllCutButton: function(cutButton) {
        var cutRowButtons = this._layoutContainer.all('.' + CSS_MOVE_CUT_ROW_BUTTON);

        this.get('removeColMoveButtons')(cutButton);

        cutRowButtons.each(function(cutRowButton) {
            if (cutRowButton !== cutButton) {
                cutRowButton.remove();
            }
        });
    },

    /**
     * Default value for `removeColMoveButtons` attribute.
     *
     * @method _removeColMoveButtons
     * @param {Node} clickedButton
     * @protected
     */
    _removeColMoveButtons: function(clickedButton) {
        var cutButtons = this._layoutContainer.all('.' + CSS_MOVE_CUT_COL_BUTTON);

        cutButtons.each(function(cutButton) {
            if (cutButton !== clickedButton) {
                cutButton.remove();
            }
        });
    },

    /**
     * Default value for `removeColMoveTargets` attribute.
     *
     * @method _removeColMoveTargets
     * @protected
     */
    _removeColMoveTargets: function() {
        this._layoutContainer.all('.' + CSS_MOVE_COL_TARGET).remove();
    },

    /**
    * Removes cut button from row.
    *
    * @method _removeCutButtonFromRow
    * @param {Node} row
    * @protected
    */
    _removeCutButtonFromRow: function(row) {
        row.one('.' + CSS_MOVE_CUT_ROW_BUTTON).remove();
    },

    /**
    * Removes the CSS class indicating that some element has moving.
    *
    * @method _removeMovingClass
    * @protected
    */
    _afterMoveEnd: function() {
        this._removeMovingClass();
        this._resetMoveUI();
    },

    /**
    * Removes the CSS class indicating that some element has moving.
    *
    * @method _removeMovingClass
    * @protected
    */
    _removeMovingClass: function() {
        this._layoutContainer.removeClass(CSS_MOVING);
    },

    /**
     * Removes all target area.
     *
     * @method _removeTargetArea
     * @protected
     */
    _removeTargetArea: function() {
        this._layoutContainer.all('.' + CSS_MOVE_ROW_TARGET).remove();
        this.get('removeColMoveTargets')();
    },

    /**
     * Reset the UI to a initial state.
     *
     * @method _resetMoveUI
     * @protected
     */
    _resetMoveUI: function() {
        this._removeAllCutButton();
        this._removeTargetArea();
        this._unbindMoveColEvents();
        this._unbindMoveRowEvents();

        this._uiSetEnableMoveCols(this.get('enableMoveCols'));
        this._uiSetEnableMoveRows(this.get('enableMoveRows'));
    },

    /**
     * Updates the UI according to the value of the `enableMoveCols` attribute.
     *
     * @method _uiSetEnableMoveCols
     * @param {Boolean} enableMoveCols
     * @protected
     */
    _uiSetEnableMoveCols: function(enableMoveCols) {
        if (enableMoveCols) {
            this._appendMoveButtonToCols();
            this._bindMoveColEvents();
        }
        else {
            this._unbindMoveColEvents();
        }
    },

    /**
     * Updates the UI according to the value of the `enableMoveRows` attribute.
     *
     * @method _uiSetEnableMoveRows
     * @param {Boolean} enableMoveRows
     * @protected
     */
    _uiSetEnableMoveRows: function(enableMoveRows) {
        if (enableMoveRows) {
            this._appendMoveButtonToRows();
            this._bindMoveRowEvents();
        }
        else {
            this._unbindMoveRowEvents();
        }
    },

    /**
     * Unbinds the events related to the functionality of moving cols from layout.
     *
     * @method _unbindMoveColEvents
     * @protected
     */
    _unbindMoveColEvents: function() {
        if (this._moveColEventHandles) {
            (new A.EventHandle(this._moveColEventHandles)).detach();
        }
    },

    /**
     * Unbinds the events related to the functionality of moving rows from layout.
     *
     * @method _unbindMoveRowEvents
     * @protected
     */
    _unbindMoveRowEvents: function() {
        if (this._moveRowEventHandles) {
            (new A.EventHandle(this._moveRowEventHandles)).detach();
        }
    }
};

/**
 * Static property used to define the default attribute configuration for the
 * `A.LayoutBuilderMove`.
 *
 * @property ATTRS
 * @type {Object}
 * @static
 */
LayoutBuilderMove.ATTRS = {
    /**
     * Default function to add move button to cols.
     *
     * @attribute addColMoveButton
     * @type {Function}
     */
    addColMoveButton: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._addColMoveButton, this);
        }
    },

    /**
     * Default function to add target to cols.
     *
     * @attribute addColMoveTarget
     * @type {Function}
     */
    addColMoveTarget: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._addColMoveTarget, this);
        }
    },

    /**
     * Default function to choose target on cols.
     *
     * @attribute chooseColMoveTarget
     * @type {Function}
     */
    chooseColMoveTarget: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._chooseColMoveTarget, this);
        }
    },

    /**
     * Default function to choose target on rows.
     *
     * @attribute chooseRowMoveTarget
     * @type {Function}
     */
    chooseRowMoveTarget: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._createRowTargetArea, this);
        }
    },

    /**
     * Default function to click on targets in cols.
     *
     * @attribute clickColMoveTarget
     * @type {Function}
     */
    clickColMoveTarget: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._clickColMoveTarget, this);
        }
    },

    /**
     * Flag indicating if the feature of moving cols from layout is
     * enabled or not.
     *
     * @attribute enableMoveCols
     * @default true
     * @type {Boolean}
     */
    enableMoveCols: {
        validator: A.Lang.isBoolean,
        value: true
    },

    /**
     * Flag indicating if the feature of moving rows from layout is
     * enabled or not.
     *
     * @attribute enableMoveRows
     * @default true
     * @type {Boolean}
     */
    enableMoveRows: {
        validator: A.Lang.isBoolean,
        value: true
    },

    /**
     * Default function to remove move button from cols.
     *
     * @attribute removeColMoveButtons
     * @type {Function}
     */
    removeColMoveButtons: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._removeColMoveButtons, this);
        }
    },

    /**
     * Default function to remove targets from cols.
     *
     * @attribute removeColMoveTargets
     * @type {Function}
     */
    removeColMoveTargets: {
        validator: A.Lang.isFunction,
        valueFn: function() {
            return A.bind(this._removeColMoveTargets, this);
        }
    }
};

A.LayoutBuilderMove = LayoutBuilderMove;


}, '3.1.0-deprecated.95', {"requires": ["aui-node-base", "base-build"], "skinnable": true});
