import $ from '../core/renderer';
import fx from '../animation/fx';
import translator from '../animation/translator';
import mathUtils from '../core/utils/math';
import { extend } from '../core/utils/extend';
import { noop, deferRender } from '../core/utils/common';
import { triggerResizeEvent, getPublicElement } from '../core/utils/dom';
import { isDefined } from '../core/utils/type';
import devices from '../core/devices';
import registerComponent from '../core/component_registrator';
import CollectionWidget from './collection/ui.collection_widget.live_update';
import Swipeable from '../events/gesture/swipeable';
import { Deferred } from '../core/utils/deferred';

const MULTIVIEW_CLASS = 'dx-multiview';
const MULTIVIEW_WRAPPER_CLASS = 'dx-multiview-wrapper';
const MULTIVIEW_ITEM_CONTAINER_CLASS = 'dx-multiview-item-container';
const MULTIVIEW_ITEM_CLASS = 'dx-multiview-item';
const MULTIVIEW_ITEM_HIDDEN_CLASS = 'dx-multiview-item-hidden';
const MULTIVIEW_ITEM_DATA_KEY = 'dxMultiViewItemData';

const MULTIVIEW_ANIMATION_DURATION = 200;

const toNumber = value => +(value);

const position = $element => translator.locate($element).left;

const _translator = {
    move($element, position) {
        translator.move($element, { left: position });
    }
};

const animation = {
    moveTo($element, position, duration, completeAction) {
        fx.animate($element, {
            type: 'slide',
            to: { left: position },
            duration: duration,
            complete: completeAction
        });
    },

    complete($element) {
        fx.stop($element, true);
    }
};

/**
* @name dxMultiView
* @inherits CollectionWidget
* @module ui/multi_view
* @export default
*/
const MultiView = CollectionWidget.inherit({

    _activeStateUnit: '.' + MULTIVIEW_ITEM_CLASS,

    _supportedKeys: function() {
        return extend(this.callBase(), {
            pageUp: noop,
            pageDown: noop
        });
    },

    _getDefaultOptions: function() {
        return extend(this.callBase(), {
            /**
            * @name dxMultiViewOptions.selectedIndex
            * @type number
            * @default 0
            */
            selectedIndex: 0,

            /**
            * @name dxMultiViewOptions.swipeEnabled
            * @type boolean
            * @default true
            */
            swipeEnabled: true,

            /**
            * @name dxMultiViewOptions.animationEnabled
            * @type boolean
            * @default true
            */
            animationEnabled: true,

            /**
            * @name dxMultiViewOptions.loop
            * @type boolean
            * @default false
            */
            loop: false,

            /**
            * @name dxMultiViewOptions.deferRendering
            * @type boolean
            * @default true
            */
            deferRendering: true,

            /**
            * @name dxMultiViewOptions.selectedItems
            * @hidden
            */

            /**
            * @name dxMultiViewOptions.selectedItemKeys
            * @hidden
            */

            /**
            * @name dxMultiViewOptions.keyExpr
            * @hidden
            */

            /**
             * @name dxMultiViewOptions.items
             * @type Array<string, dxMultiViewItem, object>
             * @fires dxMultiViewOptions.onOptionChanged
             */

            _itemAttributes: { role: 'tabpanel' },
            loopItemFocus: false,
            selectOnFocus: true,
            selectionMode: 'single',
            selectionRequired: true,
            selectionByClick: false
        });
    },

    _defaultOptionsRules: function() {
        return this.callBase().concat([
            {
                device: function() {
                    return devices.real().deviceType === 'desktop' && !devices.isSimulator();
                },
                options: {
                    /**
                    * @name dxMultiViewOptions.focusStateEnabled
                    * @type boolean
                    * @default true @for desktop
                    */
                    focusStateEnabled: true
                }
            }
        ]);
    },

    _itemClass: function() {
        return MULTIVIEW_ITEM_CLASS;
    },

    _itemDataKey: function() {
        return MULTIVIEW_ITEM_DATA_KEY;
    },

    _itemContainer: function() {
        return this._$itemContainer;
    },

    _itemElements: function() {
        return this._itemContainer().children(this._itemSelector());
    },

    _itemWidth: function() {
        if(!this._itemWidthValue) {
            this._itemWidthValue = this._$wrapper.width();
        }

        return this._itemWidthValue;
    },

    _clearItemWidthCache: function() {
        delete this._itemWidthValue;
    },

    _itemsCount: function() {
        return this.option('items').length;
    },

    _normalizeIndex: function(index) {
        const count = this._itemsCount();

        if(index < 0) {
            index = index + count;
        }
        if(index >= count) {
            index = index - count;
        }

        return index;
    },

    _getRTLSignCorrection: function() {
        return this.option('rtlEnabled') ? -1 : 1;
    },

    _init: function() {
        this.callBase.apply(this, arguments);

        const $element = this.$element();

        $element.addClass(MULTIVIEW_CLASS);

        this._$wrapper = $('<div>').addClass(MULTIVIEW_WRAPPER_CLASS);
        this._$wrapper.appendTo($element);

        this._$itemContainer = $('<div>').addClass(MULTIVIEW_ITEM_CONTAINER_CLASS);
        this._$itemContainer.appendTo(this._$wrapper);

        this.option('loopItemFocus', this.option('loop'));

        this._initSwipeable();
    },

    _initMarkup: function() {
        this._deferredItems = [];

        this.callBase();

        const selectedItemIndices = this._getSelectedItemIndices();
        this._updateItemsVisibility(selectedItemIndices[0]);
    },

    _afterItemElementDeleted: function($item, deletedActionArgs) {
        this.callBase($item, deletedActionArgs);
        if(this._deferredItems) {
            this._deferredItems.splice(deletedActionArgs.itemIndex, 1);
        }
    },

    _beforeItemElementInserted: function(change) {
        this.callBase.apply(this, arguments);
        if(this._deferredItems) {
            this._deferredItems.splice(change.index, 0, null);
        }
    },

    _executeItemRenderAction: function(index, itemData, itemElement) {
        index = (this.option('items') || []).indexOf(itemData);
        this.callBase(index, itemData, itemElement);
    },

    _renderItemContent: function(args) {
        const renderContentDeferred = new Deferred();

        const that = this;
        const callBase = this.callBase;

        const deferred = new Deferred();
        deferred.done(function() {
            const $itemContent = callBase.call(that, args);
            renderContentDeferred.resolve($itemContent);
        });

        this._deferredItems[args.index] = deferred;
        this.option('deferRendering') || deferred.resolve();

        return renderContentDeferred.promise();
    },

    _render: function() {
        this.callBase();

        deferRender(() => {
            const selectedItemIndices = this._getSelectedItemIndices();
            this._updateItems(selectedItemIndices[0]);
        });
    },

    _updateItems: function(selectedIndex, newIndex) {
        this._updateItemsPosition(selectedIndex, newIndex);
        this._updateItemsVisibility(selectedIndex, newIndex);
    },

    _modifyByChanges: function() {
        this.callBase.apply(this, arguments);

        const selectedItemIndices = this._getSelectedItemIndices();
        this._updateItemsVisibility(selectedItemIndices[0]);
    },

    _updateItemsPosition: function(selectedIndex, newIndex) {
        const $itemElements = this._itemElements();
        const positionSign = isDefined(newIndex) ? -this._animationDirection(newIndex, selectedIndex) : undefined;
        const $selectedItem = $itemElements.eq(selectedIndex);

        _translator.move($selectedItem, 0);
        if(isDefined(newIndex)) {
            _translator.move($itemElements.eq(newIndex), positionSign * 100 + '%');
        }
    },

    _updateItemsVisibility: function(selectedIndex, newIndex) {
        const $itemElements = this._itemElements();

        $itemElements.each((function(itemIndex, item) {
            const $item = $(item);
            const isHidden = itemIndex !== selectedIndex && itemIndex !== newIndex;

            if(!isHidden) {
                this._renderSpecificItem(itemIndex);
            }
            $item.toggleClass(MULTIVIEW_ITEM_HIDDEN_CLASS, isHidden);

            this.setAria('hidden', isHidden || undefined, $item);
        }).bind(this));
    },

    _renderSpecificItem: function(index) {
        const $item = this._itemElements().eq(index);
        const hasItemContent = $item.find(this._itemContentClass()).length > 0;

        if(isDefined(index) && !hasItemContent) {
            this._deferredItems[index].resolve();
            triggerResizeEvent($item);
        }
    },

    _refreshItem: function($item, item) {
        this.callBase($item, item);

        this._updateItemsVisibility(this.option('selectedIndex'));
    },

    _setAriaSelected: noop,

    _updateSelection: function(addedSelection, removedSelection) {
        const newIndex = addedSelection[0];
        const prevIndex = removedSelection[0];

        animation.complete(this._$itemContainer);

        this._updateItems(prevIndex, newIndex);

        const animationDirection = this._animationDirection(newIndex, prevIndex);

        this._animateItemContainer(animationDirection * this._itemWidth(), (function() {
            _translator.move(this._$itemContainer, 0);
            this._updateItems(newIndex);

            // NOTE: force layout recalculation on iOS 6 & iOS 7.0 (B254713)
            this._$itemContainer.width();
        }).bind(this));
    },

    _animateItemContainer: function(position, completeCallback) {
        const duration = this.option('animationEnabled') ? MULTIVIEW_ANIMATION_DURATION : 0;

        animation.moveTo(this._$itemContainer, position, duration, completeCallback);
    },

    _animationDirection: function(newIndex, prevIndex) {
        const containerPosition = position(this._$itemContainer);
        const indexDifference = (prevIndex - newIndex) * this._getRTLSignCorrection() * this._getItemFocusLoopSignCorrection();
        const isSwipePresent = containerPosition !== 0;

        const directionSignVariable = isSwipePresent ? containerPosition : indexDifference;

        return mathUtils.sign(directionSignVariable);
    },

    _getSwipeDisabledState() {
        return !this.option('swipeEnabled') || this._itemsCount() <= 1;
    },

    _initSwipeable() {
        this._createComponent(this.$element(), Swipeable, {
            disabled: this._getSwipeDisabledState(),
            elastic: false,
            itemSizeFunc: this._itemWidth.bind(this),
            onStart: args => this._swipeStartHandler(args.event),
            onUpdated: args => this._swipeUpdateHandler(args.event),
            onEnd: args => this._swipeEndHandler(args.event)
        });
    },

    _swipeStartHandler: function(e) {
        animation.complete(this._$itemContainer);

        const selectedIndex = this.option('selectedIndex');
        const loop = this.option('loop');
        const lastIndex = this._itemsCount() - 1;
        const rtl = this.option('rtlEnabled');

        e.maxLeftOffset = toNumber(loop || (rtl ? selectedIndex > 0 : selectedIndex < lastIndex));
        e.maxRightOffset = toNumber(loop || (rtl ? selectedIndex < lastIndex : selectedIndex > 0));

        this._swipeDirection = null;
    },

    _swipeUpdateHandler: function(e) {
        const offset = e.offset;
        const swipeDirection = mathUtils.sign(offset) * this._getRTLSignCorrection();

        _translator.move(this._$itemContainer, offset * this._itemWidth());

        if(swipeDirection !== this._swipeDirection) {
            this._swipeDirection = swipeDirection;

            const selectedIndex = this.option('selectedIndex');
            const newIndex = this._normalizeIndex(selectedIndex - swipeDirection);

            this._updateItems(selectedIndex, newIndex);
        }
    },

    _swipeEndHandler: function(e) {
        const targetOffset = e.targetOffset * this._getRTLSignCorrection();
        if(targetOffset) {
            this.option('selectedIndex', this._normalizeIndex(this.option('selectedIndex') - targetOffset));
            // TODO: change focusedElement on focusedItem
            const $selectedElement = this.itemElements().filter('.dx-item-selected');
            this.option('focusStateEnabled') && this.option('focusedElement', getPublicElement($selectedElement));
        } else {
            this._animateItemContainer(0, noop);
        }
    },

    _getItemFocusLoopSignCorrection: function() {
        return this._itemFocusLooped ? -1 : 1;
    },

    _moveFocus: function() {
        this.callBase.apply(this, arguments);

        this._itemFocusLooped = false;
    },

    _prevItem: function($items) {
        const $result = this.callBase.apply(this, arguments);

        this._itemFocusLooped = $result.is($items.last());

        return $result;
    },

    _nextItem: function($items) {
        const $result = this.callBase.apply(this, arguments);

        this._itemFocusLooped = $result.is($items.first());

        return $result;
    },

    _dimensionChanged: function() {
        this._clearItemWidthCache();
    },

    _visibilityChanged: function(visible) {
        if(visible) {
            this._dimensionChanged();
        }
    },

    _updateSwipeDisabledState() {
        const disabled = this._getSwipeDisabledState();
        Swipeable.getInstance(this.$element()).option('disabled', disabled);
    },

    _optionChanged: function(args) {
        const value = args.value;

        switch(args.name) {
            case 'loop':
                this.option('loopItemFocus', value);
                break;
            case 'animationEnabled':
                break;
            case 'swipeEnabled':
                this._updateSwipeDisabledState();
                break;
            case 'deferRendering':
                this._invalidate();
                break;
            case 'items':
                this._updateSwipeDisabledState();
                this.callBase(args);
                break;
            default:
                this.callBase(args);
        }
    }

});
/**
* @name dxMultiViewItem
* @inherits CollectionWidgetItem
* @type object
*/
/**
* @name dxMultiViewItem.visible
* @hidden
*/

registerComponent('dxMultiView', MultiView);

module.exports = MultiView;
///#DEBUG
module.exports.animation = animation;
module.exports._translator = _translator;
///#ENDDEBUG
