<template>
    <div :class="classes">
        <InputNumber
            v-if="!range && showInput"
            :min="min"
            :max="max"
            :step="step"
            :value="exportValue[0]"
            :disabled="disabled"
            @on-change="handleInputChange" />
        <div 
            ref="slider"
            :class="[prefixCls + '-wrap']" 
            @click.self="handleSetPosition($event)">
            <template v-if="showStops">
                <div 
                    v-for="(item, index) in stops" 
                    :class="[prefixCls + '-stop']" 
                    :key="index"
                    :style="{ 'left': item + '%' }" 
                    @click.self="handleSetPosition($event)">
                </div>
            </template>
            <div 
                :class="[prefixCls + '-bar']" 
                :style="barStyle" 
                @click.self="handleSetPosition($event)">
            </div>
            <div 
                :class="[prefixCls + '-button-wrap']"
                :style="{left: minPosition + '%'}"
                @touchstart="handlePointerDown($event, 'min')" 
                @mousedown="handlePointerDown($event, 'min')">
                <Tip 
                    ref="minTip" 
                    placement="top" 
                    trigger="hover">
                    <div slot="content">{{tipFormat(exportValue[0])}}</div>
                    <div :class="minButtonClasses"></div>
                </Tip>
            </div>
            <div 
                v-if="range" 
                :class="[prefixCls + '-button-wrap']" 
                :style="{left: maxPosition + '%'}"
                @touchstart="handlePointerDown($event, 'max')" 
                @mousedown="handlePointerDown($event, 'max')">
                <Tip 
                    ref="maxTip" 
                    placement="top" 
                    trigger="hover">
                    <div slot="content">{{tipFormat(exportValue[1])}}</div>
                    <div :class="maxButtonClasses"></div>
                </Tip>
            </div>
        </div>
    </div>
</template>
<script>
    import InputNumber from '../input-number/'
    import Tip from '../tip/'
    import { getStyle, oneOf } from '../../../utils/assist'
    import { on, off } from '../../../utils/dom'
    import Emitter from '../../../mixins/emitter'
    import elementResizeDetectorMaker from 'element-resize-detector'

    const prefixCls = 'yy-slider'
    export default {
        mixins: [ Emitter ],
        components: {
            InputNumber,
            Tip
        },
        props: {
            value: {
                type: [Number, Array],
                default: 0
            },
            disabled: {
                type: Boolean,
                default: false
            },
            min: {
                type: Number,
                default: 0
            },
            max: {
                type: Number,
                default: 100
            },
            step: {
                type: Number,
                default: 1
            },
            range: {
                type: Boolean,
                default: false
            },
            showStops: {
                type: Boolean,
                default: false
            },
            showInput: {
                type: Boolean,
                default: false
            },
            tipFormat: {
                type: Function,
                default (value) {
                    return value
                }
            }
        },
        data() {
            const currentValue = this.checkLimits(Array.isArray(this.value) ? this.value : [this.value, this.value])
            return {
                prefixCls: prefixCls,
                currentValue: currentValue,
                pointerDown: '',
                dragging: false,
                startX: 0,
                currentX: 0,
                startPos: 0,
                maker: null,
                sliderWidth: 0,
                oldValue: [...currentValue],
                valueIndex: {
                    min: 0,
                    max: 1
                }
            }
        },
        computed: {
            classes () {
                return [
                    `${prefixCls}`,
                    {
                        [`${prefixCls}-input`]: this.showInput && !this.range,
                        [`${prefixCls}-range`]: this.range,
                        [`${prefixCls}-disabled`]: this.disabled
                    }
                ]
            },
            minButtonClasses () {
                return [
                    `${prefixCls}-button`,
                    {
                        [`${prefixCls}-button-dragging`]: this.pointerDown === 'min',
                    }
                ]
            },
            maxButtonClasses () {
                return [
                    `${prefixCls}-button`,
                    {
                        [`${prefixCls}-button-dragging`]: this.pointerDown === 'max'
                    }
                ]
            },
            barStyle () {
                const style = {
                    width: (this.currentValue[0] - this.min) / this.valueRange * 100 + '%'
                }

                if (this.range) {
                    style.left = (this.currentValue[0] - this.min) / this.valueRange * 100 + '%'
                    style.width = (this.currentValue[1] - this.currentValue[0]) / this.valueRange * 100 + '%'
                }

                return style
            },
            stops () {
                let stopCount = this.valueRange / this.step
                let result = []
                let stepWidth = 100 * this.step / this.valueRange
                for (let i = 1; i < stopCount; i++) {
                    result.push(i * stepWidth)
                }
                return result
            },
            exportValue(){
                const decimalCases = (String(this.step).split('.')[1] || '').length
                return this.currentValue.map(nr => Number(nr.toFixed(decimalCases)))
            },
            valueRange () {
                return this.max - this.min
            },
            firstPosition () {
                return this.currentValue[0]
            },
            secondPosition () {
                return this.currentValue[1]
            },
            minPosition () {
                const vallue = this.currentValue
                return (vallue[0] - this.min) / this.valueRange * 100
            },
            maxPosition: function () {
                const vallue = this.currentValue

                return (vallue[1] - this.min) / this.valueRange * 100
            }
        },
        methods: {
            getPointerX (e) {
                return e.type.indexOf('touch') !== -1 ? e.touches[0].clientX : e.clientX
            },
            getCurrentValue (event, type) {
                if (this.disabled) return

                const index = this.valueIndex[type]
                if (typeof index === 'undefined') return

                return this.currentValue[index]
            },
            checkLimits ([min, max]) {
                min = Math.max(this.min, min)
                min = Math.min(this.max, min)

                max = Math.max(this.min, min, max)
                max = Math.min(this.max, max)
                return [min, max]
            },
            changeButtonPosition (newPos, forceType) {
                const type = forceType || this.pointerDown
                const index = type === 'min' ? 0 : 1
                if (type === 'min') newPos = this.checkLimits([newPos, this.max])[0]
                else newPos = this.checkLimits([this.min, newPos])[1]

                const modulus = this.handleDecimal(newPos, this.step)
                const value = this.currentValue
                value[index] = newPos - modulus

                if (this.range) {
                    if (type === 'min' && value[0] > value[1]) value[1] = value[0]
                    if (type === 'max' && value[0] > value[1]) value[0] = value[1]
                }

                this.currentValue = [...value]

                if (!this.dragging) {
                    if (this.currentValue[index] !== this.oldValue[index]) {
                        this.handleChange()
                        this.oldValue[index] = this.currentValue[index]
                    }
                }
            },
            handleDecimal(pos, step) {
                if (step < 1) {
                    let sl = step.toString(),
                        multiple = 1,
                        m
                    try {
                        m = sl.split('.')[1].length
                    } catch (e) {
                        m = 0
                    }
                    multiple = Math.pow(10, m)
                    return (pos * multiple) % (step * multiple) / multiple
                } else {
                    return pos % step
                }
            },
            handlePointerDown (event, type) {
                if (this.disabled) return
                event.preventDefault()
                this.pointerDown = type

                this.handlePointerDragStart(event)
                on(window, 'mousemove', this.handlePointerDrag)
                on(window, 'touchmove', this.handlePointerDrag)
                on(window, 'mouseup', this.handlePointerDragEnd)
                on(window, 'touchend', this.handlePointerDragEnd)
            },
            handlePointerDragStart (event) {
                this.dragging = false
                this.startX = this.getPointerX(event)
                this.$refs[`${this.pointerDown}Tip`].visible = true
                this.startPos = (this[`${this.pointerDown}Position`] * this.valueRange / 100) + this.min
            },
            handlePointerDrag (event) {
                this.dragging = true
                this.currentX = this.getPointerX(event)
                const diff = (this.currentX - this.startX) / this.sliderWidth * this.valueRange

                this.changeButtonPosition(this.startPos + diff)
            },
            handlePointerDragEnd () {
                if (this.dragging) {
                    this.dragging = false
                    this.$refs[`${this.pointerDown}Tip`].visible = false
                    this.handleChange()
                }

                this.pointerDown = ''
                off(window, 'mousemove', this.handlePointerDrag)
                off(window, 'touchmove', this.handlePointerDrag)
                off(window, 'mouseup', this.handlePointerDragEnd)
                off(window, 'touchend', this.handlePointerDragEnd)
            },
            handleInputChange (value) {
                this.currentValue = [value === 0 ? 0 : value || this.min, this.currentValue[1]]
                this.handleChange()
            },
            handleChange(){
                const value = this.range ? this.exportValue : this.exportValue[0]
                this.$emit('on-change', value)
                this.dispatch('YYFormItem', 'on-form-change', value)
            },
            handleSetPosition (event) {
                if (this.disabled) return
                const currentX = this.getPointerX(event)
                const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left
                let newPostion = ((currentX - sliderOffsetLeft) / this.sliderWidth * this.valueRange) + this.min
                let regularPosition = newPostion / this.valueRange * 100

                if (!this.range || regularPosition <= this.minPosition) {
                    this.changeButtonPosition(newPostion, 'min')
                } else if (regularPosition >= this.maxPosition) {
                    this.changeButtonPosition(newPostion, 'max')
                } else {
                    let forceType = ((newPostion - this.firstPosition) <= (this.secondPosition - newPostion)) ? 'min' : 'max'
                    this.changeButtonPosition(newPostion, forceType)
                }
            },
            handleSetSliderWidth() {
                this.sliderWidth = parseInt(getStyle(this.$refs.slider, 'width'), 10)
            }
        },
        mounted() {
            this.marker = elementResizeDetectorMaker()
            this.marker.listenTo(this.$refs.slider, this.handleSetSliderWidth)
        },
        beforeDestroy() {
            this.marker.removeListener(this.$refs.slider, this.handleSetSliderWidth)
        }
    }
</script>
