<template>
    <div class='ui-range'>
        <template v-if="isMoblie">
            <div ref="wrap" :class="['vue-slider-wrap', flowDirection, className, disabledClass]" v-show="show" :style="[( styles || {} ), wrapStyles]" @click="wrapClick">
                <div ref="elem" class="vue-slider" :style="elemStyles">
                    <template v-if="isRange">
                        <div ref="dot0" :data-rangeValue="mValue[0]" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot']" :style="dotStyles" @touchstart="moveStart(0)"></div>
                        <div ref="dot1" :data-rangeValue="mValue[1]" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot']" :style="dotStyles" @touchstart="moveStart(1)"></div>
                    </template>
                    <template v-else>
                        <div ref="dot" :data-rangeValue="mValue" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot']" :style="dotStyles" @touchstart="moveStart"></div>
                    </template>
                    <template v-if="piecewise">
                        <ul v-if="direction === 'vertical'" class="vue-slider-piecewise">
                            <li v-for="i in (total - 1)" :style="[ piecewiseStyle, { bottom: `${ gap * i - width / 2 }px`, left: '0px' }]"></li>
                        </ul>
                        <ul v-else class="vue-slider-piecewise">
                            <li v-for="i in (total - 1)" :style="[ piecewiseStyle, { left: `${ gap * i - height / 2 }px`, top: '0px' }]"></li>
                        </ul>
                    </template>
                    <span ref="process" class="vue-slider-process"></span>
                </div>
            </div>
        </template>
        <template v-else>
            <div ref="wrap" :class="['vue-slider-wrap', flowDirection, className, disabledClass]" v-show="show" :style="[( styles || {} ), wrapStyles]" @click="wrapClick">
                <div ref="elem" class="vue-slider" :style="elemStyles">
                    <template v-if="isRange">
                        <div ref="dot0" :data-rangeValue="mValue[0]" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot', 'vue-slider-tooltip-from']" :style="dotStyles" @mousedown="moveStart(0)"></div>
                        <div ref="dot1" :data-rangeValue="mValue[1]" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot', 'vue-slider-tooltip-to']" :style="dotStyles" @mousedown="moveStart(1)"></div>
                    </template>
                    <template v-else>
                        <div ref="dot" :data-rangeValue="mValue" :class="[ tooltipStatus, `vue-slider-tooltip-${tooltipDirection}`, 'vue-slider-dot']" :style="dotStyles" @mousedown="moveStart"></div>
                    </template>
                    <template v-if="piecewise">
                        <ul v-if="direction === 'vertical'" class="vue-slider-piecewise">
                            <li v-for="i in (total - 1)" :style="[ piecewiseStyle, { bottom: `${ gap * i - width / 2 }px`, left: '0px' }]"></li>
                        </ul>
                        <ul v-else class="vue-slider-piecewise">
                            <li v-for="i in (total - 1)" :style="[ piecewiseStyle, { left: `${ gap * i - height / 2 }px`, top: '0px' }]"></li>
                        </ul>
                    </template>
                    <span ref="process" class="vue-slider-process"></span>
                </div>
            </div>
        </template>
    </div>
</template>

<script>
export default {
    data() {
        return {
            flag: false,
            size: 0,
            currentValue: 0,
            currentSlider: 0
        }
    },
    props: {
        value: {
            type: [String, Number, Array],
            default: 0
        },
        className: String,
        styles: Object,
        width: {
            type: [Number, String],
            default: 'auto'
        },
        height: {
            type: [Number, String],
            default: 1
        },
        data: {
            type: Array,
            default: null
        },
        dotSize: {
            type: Number,
            default: 20
        },
        min: {
            type: Number,
            default: 0
        },
        max: {
            type: Number,
            default: 100
        },
        interval: {
            type: Number,
            default: 1
        },
        show: {
            type: Boolean,
            default: true
        },
        disabled: {
            type: Boolean,
            default: false
        },
        piecewise: {
            type: Boolean,
            default: false
        },
        tooltip: {
            type: [String, Boolean],
            default: 'always'
        },
        eventType: {
            type: String,
            default: 'auto'
        },
        direction: {
            type: String,
            default: 'horizontal'
        },
        tooltipDir: {
            type: String
        },
        reverse: {
            type: Boolean,
            default: false
        },
        speed: {
            type: Number,
            default: 0.5
        },
    },
    computed: {
        flowDirection: function() {
            return `vue-slider-${this.direction + (this.reverse ? '-reverse' : '')}`
        },
        tooltipDirection: function() {
            return this.tooltipDir || (this.direction === 'vertical' ? 'left' : 'top')
        },
        tooltipStatus: function() {
            if (this.tooltip === 'hover' && this.flag) return 'vue-slider-always'
            return this.tooltip ? `vue-slider-${this.tooltip}` : ''
        },
        isMoblie: function() {
            if (this.eventType === 'touch') {
                return true
            }
            else if (this.eventType === 'mouse') {
                return false
            }
            else {
                return /(iPhone|iPad|iPod|iOS|Android|SymbianOS|Windows Phone|Mobile)/i.test(navigator.userAgent)
            }
        },
        isDisabled: function() {
            return this.eventType === 'none' ? true : this.disabled
        },
        disabledClass: function() {
            return this.disabled ? 'vue-slider-disabled' : ''
        },
        isRange: function() {
            return Array.isArray(this.value)
        },
        slider: function() {
            if (this.isRange) {
                return [this.$refs.dot0, this.$refs.dot1]
            }
            else {
                return this.$refs.dot
            }
        },
        minimum: function() {
            if (this.data) {
                return 0
            }
            return this.min
        },
        mValue: {
            get: function() {
                if (this.data) {
                    if (this.isRange) {
                        return [this.data[this.currentValue[0]], this.data[this.currentValue[1]]]
                    }
                    return this.data[this.currentValue]
                }
                return this.currentValue
            },
            set: function(val) {
                if (this.data) {
                    if (this.isRange) {
                        let index0 = this.data.indexOf(val[0])
                        let index1 = this.data.indexOf(val[1])
                        if (index0 > -1 && index1 > -1) {
                            this.currentValue = [index0, index1]
                        }
                    }
                    else {
                        let index = this.data.indexOf(val)
                        if (index > -1) {
                            this.currentValue = index
                        }
                    }
                }
                else {
                    // swap extremums
                    if (this.isRange && val[0] > val[1]) {
                        val = [val[1], val[0]];
                    }
                    this.currentValue = val
                }
            }
        },
        maximum: function() {
            if (this.data) {
                return this.data.length - 1
            }
            return this.max
        },
        spacing: function() {
            if (this.data) {
                return 1
            }
            return this.interval
        },
        total: function() {
            if (this.data) {
                return this.data.length - 1
            }
            if ((this.maximum - this.minimum) % this.interval !== 0) {
                console.error('[Vue warn]: Prop[interval] is illegal, Please make sure that the interval can be divisible')
            }
            return (this.maximum - this.minimum) / this.interval
        },
        gap: function() {
            return this.size / this.total
        },
        offset: function() {
            return this.direction === 'vertical' ? (this.$refs.elem.getBoundingClientRect().top + window.pageYOffset || document.documentElement.scrollTop) : this.$refs.elem.getBoundingClientRect().left
        },
        position: function() {
            if (this.isRange) {
                return [(this.currentValue[0] - this.minimum) / this.spacing * this.gap, (this.currentValue[1] - this.minimum) / this.spacing * this.gap]
            }
            return (this.currentValue - this.minimum) / this.spacing * this.gap
        },
        limit: function() {
            if (this.isRange) {
                return [[0, this.position[1]], [this.position[0], this.size]]
            }
            return [0, this.size]
        },
        valueLimit: function() {
            if (this.isRange) {
                return [[this.minimum, this.currentValue[1]], [this.currentValue[0], this.maximum]]
            }
            return [this.minimum, this.maximum]
        },
        wrapStyles: function() {
            if (this.direction === 'vertical') {
                return {
                    height: typeof this.height === 'number' ? `${this.height}px` : this.height
                }
            }
            return {
                width: typeof this.width === 'number' ? `${this.width}px` : this.width
            }
        },
        elemStyles: function() {
            if (this.direction === 'vertical') {
                return {
                    width: `${this.width}px`,
                    height: '100%',
                    margin: `${this.dotSize / 2}px`
                }
            }
            return {
                height: `${this.height}px`,
                margin: `${this.dotSize / 2}px`
            }
        },
        dotStyles: function() {
            if (this.direction === 'vertical') {
                return {
                    width: `${this.dotSize}px`,
                    height: `${this.dotSize}px`,
                    left: `${(-(this.dotSize - this.width) / 2)}px`
                }
            }
            return {
                width: `${this.dotSize}px`,
                height: `${this.dotSize}px`,
                top: `${(-(this.dotSize - this.height) / 2)}px`
            }
        },
        piecewiseStyle: function() {
            if (this.direction === 'vertical') {
                return {
                    width: `${this.width}px`,
                    height: `${this.width}px`
                }
            }
            return {
                width: `${this.height}px`,
                height: `${this.height}px`
            }
        }
    },
    watch: {
        value: function(value) {
            this.flag || this.setValue(value)
        },
        size(size) {
            this.setPosition();
        }
    },
    methods: {
        bindEvents() {
            if (this.isMoblie) {
                document.addEventListener('touchmove', this.moving)
                document.addEventListener('touchend', this.moveEnd)
            }
            else {
                document.addEventListener('mousemove', this.moving)
                document.addEventListener('mouseup', this.moveEnd)
                document.addEventListener('mouseleave', this.moveEnd)
            }
        },
        unbindEvents() {
            if (this.isMoblie) {
                document.removeEventListener('touchmove', this.moving)
                document.removeEventListener('touchend', this.moveEnd)
            }
            else {
                document.removeEventListener('mousemove', this.moving)
                document.removeEventListener('mouseup', this.moveEnd)
                document.removeEventListener('mouseleave', this.moveEnd)
            }
        },
        getPos(e) {
            let pos
            if (this.direction === 'vertical') {
                pos = this.reverse ? (e.pageY - this.offset) : (this.size - (e.pageY - this.offset))
            }
            else {
                pos = this.reverse ? (this.size - (e.clientX - this.offset)) : (e.clientX - this.offset)
            }
            return pos
        },
        wrapClick(e) {
            if (this.isDisabled || e.target.classList.contains('vue-slider-dot')) return false
            let pos = this.getPos(e)
            if (this.isRange) {
                this.currentSlider = pos > ((this.position[1] - this.position[0]) / 2 + this.position[0]) ? 1 : 0
            }
            this.setValueOnPos(pos)
        },
        moveStart(index) {
            if (this.isDisabled) return false
            else if (this.isRange) {
                this.currentSlider = index
            }
            this.flag = true
            this.$emit('drag-start', this)
        },
        moving(e) {
            if (!this.flag) return false
            e.preventDefault()

            if (this.isMoblie) e = e.targetTouches[0]
            this.setValueOnPos(this.getPos(e), true)
        },
        moveEnd(e) {
            if (this.flag) this.$emit('drag-end', this)
            this.flag = false
            this.setPosition(0.2)
        },
        setValueOnPos(pos, bool) {
            let range = this.isRange ? this.limit[this.currentSlider] : this.limit
            let valueRange = this.isRange ? this.valueLimit[this.currentSlider] : this.valueLimit
            if (pos >= range[0] && pos <= range[1]) {
                this.setTransform(pos)
                let v = Math.round(pos / this.gap) * this.spacing + this.minimum
                this.setCurrentValue(v, bool)
            }
            else if (pos < range[0]) {
                this.setTransform(range[0])
                this.setCurrentValue(valueRange[0])
                if (this.currentSlider === 1) this.currentSlider = 0
            }
            else {
                this.setTransform(range[1])
                this.setCurrentValue(valueRange[1]);
                if (this.currentSlider === 0) this.currentSlider = 1
            }
        },
        isDiff(a, b) {
            if (Object.prototype.toString.call(a) !== Object.prototype.toString.call(b)) {
                return true
            }
            else if (Array.isArray(a) && a.length === b.length) {
                return a.some((v, i) => {
                    return v !== b[i]
                })
            }
            return a !== b
        },
        setCurrentValue(val, bool) {
            if (val < this.minimum || val > this.maximum) return false
            if (this.isRange) {
                if (this.isDiff(this.currentValue[this.currentSlider], val)) {
                    this.currentValue.splice(this.currentSlider, 1, val)
                    this.$emit('callback', this.mValue)
                    this.$emit('input', this.mValue)
                }
            }
            else if (this.isDiff(this.currentValue, val)) {
                this.currentValue = val
                this.$emit('callback', this.mValue)
                this.$emit('input', this.mValue)
            }
            bool || this.setPosition()
        },
        setIndex(val) {
            val = this.spacing * val + this.minimum
            if (this.isRange) {
                this.currentSlider = val > ((this.currentValue[1] - this.currentValue[0]) / 2 + this.currentValue[0]) ? 1 : 0
            }
            this.setCurrentValue(val)
        },
        setValue(val) {
            if (this.isDiff(this.mValue, val)) {
                this.mValue = val
                this.$emit('callback', this.mValue)
                this.$emit('input', this.mValue)
            }
            this.setPosition()
        },
        setPosition(time = this.speed) {
            this.flag || this.setTransitionTime(time)
            if (this.isRange) {
                this.currentSlider = 0
                this.setTransform(this.position[this.currentSlider])
                this.currentSlider = 1
                this.setTransform(this.position[this.currentSlider])
            }
            else {
                this.setTransform(this.position)
            }
            this.flag || this.setTransitionTime(0)
        },
        setTransform(val) {
            let value = (this.direction === 'vertical' ? ((this.dotSize / 2) - val) : (val - (this.dotSize / 2))) * (this.reverse ? -1 : 1)
            let translateValue = this.direction === 'vertical' ? `translateY( ${value}px )` : `translateX( ${value}px )`
            let processSize = `${this.currentSlider === 0 ? this.position[1] - val : val - this.position[0]}px`
            let processPos = `${this.currentSlider === 0 ? val : this.position[0]}px`
            if (this.isRange) {
                this.slider[this.currentSlider].style.transform = translateValue
                this.slider[this.currentSlider].style.WebkitTransform = translateValue
                this.slider[this.currentSlider].style.msTransform = translateValue
                if (this.direction === 'vertical') {
                    this.$refs.process.style.height = processSize
                    this.$refs.process.style[this.reverse ? 'top' : 'bottom'] = processPos
                }
                else {
                    this.$refs.process.style.width = processSize
                    this.$refs.process.style[this.reverse ? 'right' : 'left'] = processPos
                }
            }
            else {
                this.slider.style.transform = translateValue
                this.slider.style.WebkitTransform = translateValue
                this.slider.style.msTransform = translateValue
                if (this.direction === 'vertical') {
                    this.$refs.process.style.height = `${val}px`
                }
                else {
                    this.$refs.process.style.width = `${val}px`
                }
            }
        },
        setTransitionTime(time) {
            time || this.$refs.process.offsetWidth
            if (this.isRange) {
                for (let i = 0; i < this.slider.length; i++) {
                    this.slider[i].style.transitionDuration = `${time}s`
                    this.slider[i].style.WebkitTransitionDuration = `${time}s`
                }
                this.$refs.process.style.transitionDuration = `${time}s`
                this.$refs.process.style.WebkitTransitionDuration = `${time}s`
            }
            else {
                this.slider.style.transitionDuration = `${time}s`
                this.slider.style.WebkitTransitionDuration = `${time}s`
                this.$refs.process.style.transitionDuration = `${time}s`
                this.$refs.process.style.WebkitTransitionDuration = `${time}s`
            }
        },
        getValue() {
            return this.mValue
        },
        getIndex() {
            if (Array.isArray(this.currentValue)) {
                if (this.data) {
                    return this.currentValue
                }
                else {
                    return [(this.currentValue[0] - this.minimum) / this.spacing, (this.currentValue[1] - this.minimum) / this.spacing]
                }
            }
            else {
                return (this.currentValue - this.minimum) / this.spacing
            }
        },
        refresh() {
            this.updatedSize();
            this.setPosition(0)
        },
        updatedSize() {
            this.size = this.direction === 'vertical' ? this.$refs.elem.offsetHeight : this.$refs.elem.offsetWidth;
        }
    },
    created() {
        window.addEventListener('resize', this.refresh)
    },
    updated() {
        this.updatedSize();
    },
    mounted() {
        // console.log( this.val , Array.isArray(this.val) );
        this.$nextTick(function () {
            // this.updatedSize();
            this.setValue(this.value)
            this.bindEvents()
        })
    },
    destroyed() {
        this.unbindEvents();
        window.removeEventListener('resize', this.refresh);
    }
}
</script>