<template>
<div :class="mainClass">
    <label v-if="mega">{{title}}<span class="rfo" v-if="rfo">*</span><span class="rfq" v-if="rfq">*</span><span class="aster" v-if="aster">*</span></label>
    <div ref="wrap" :id="id" class="bt-slider-component bt-slider-horizontal" :style="{'padding-top': `${showTooltip?30:5}px`, 'min-height': range ? '100px' : '10px'}" @click="wrapClick" v-if="editable">
        <div ref="elem" class="bt-slider">
            <div ref="tooltip" class="bt-slider-tooltip-container" @mousedown="moveStart" @touchstart="moveStart">
                <span v-if="showTooltip" class="bt-slider-tooltip-top bt-slider-tooltip-wrap">
                    <slot name="tooltip">
                        <span :style="tooltipStyles" class="bt-slider-tooltip" v-html="customLabel"></span>
                    </slot>
                </span>
            </div>
            <div ref="processDot" class="bt-slider-dot" v-if="!showTooltip" @mousedown="moveStart" @touchstart="moveStart" :title="customLabel"></div>
            <div ref="process" :style="processStyle" class="bt-slider-process"/>
        </div>
        <div v-if="range" class="bt-slider-range">
            <div v-for="(r, index) in range" :key="index" class="bt-slider-separate" :style="dataLabelStyles">
                <span v-if="!r.hidden" class="bt-slider-separate-text">{{ r.label }}</span>
            </div>
        </div>
    </div>
    <span v-if="!editable" class="noneditable" v-html="customLabel"></span>
    <span v-show="editable && hasError" class="vldtn-error" v-html="message"></span>
</div>
</template>

<script>
import { isEmpty, isUndefined, map } from 'lodash'
export default {
    name: 'SlideBar',
    inject: ['$validator'],
    props: {
        id: {
            type: String,
            default: 'wrap',
            required: true
        },
        title: String,
        mega: {
            default: false,
            type: Boolean
        },
        rfq: {
            default: false,
            type: Boolean
        },
        rfo: {
            default: false,
            type: Boolean
        },
        aster: {
            default: false,
            type: Boolean
        },
        range: {
            type: Array,
            default: null
        },
        speed: {
            type: Number,
            default: 0.5
        },
        value: {
            type: [String, Number],
            default: 0
        },
        min: {
            type: Number,
            default: 0
        },
        max: {
            type: Number,
            default: 100
        },
        showTooltip: {
            type: Boolean,
            default: true
        },
        disabled: {
            type: Boolean,
            default: false
        },
        draggable: {
            type: Boolean,
            default: true
        },
        formatter: {
            type: Function,
            default: null
        },
        tooltipStyles: Object,
        labelStyles: Object,
        processStyle: Object,
        message: String,
        editable: {
            default: true,
            type: Boolean
        }
    },
    $_veeValidate: {
        name () {
            return this.id
        },
        value () {
            return this.val
        }
    },
    data () {
        return {
            flag: false,
            size: 0,
            currentValue: 0,
            currentSlider: 0,
            isComponentExists: true,
            interval: 1,
            lazy: false,
            realTime: false,
            dataLabelStyles: {
                color: '#4a4a4a',
                'font-family': 'BTRegular, sans-serif',
                'font-size': '12px',
                ...this.$props.labelStyles
            }
        }
    },
    computed: {
        mainClass () {
            let cls = ['slider-bar']
            if (this.mega) {
                cls.push('megaform')
            }
            if (this.hasError) {
                cls.push('error')
            }
            return cls.join(' ')
        },
        data () {
            if (!isEmpty(this.range)) {
                return map(this.range, 'value')
            } else {
                return null
            }
        },
        slider () {
            return this.$refs.tooltip
        },
        val: {
            get () {
                return this.data ? this.data[this.currentValue] : this.currentValue
            },
            set (val) {
                if (this.data) {
                    let index = this.data.indexOf(val)
                    if (index > -1) {
                        this.currentValue = index
                    }
                } else {
                    this.currentValue = val
                }
            }
        },
        currentIndex () {
            return (this.currentValue - this.minimum) / this.spacing
        },
        indexRange () {
            return [0, this.currentIndex]
        },
        minimum () {
            return this.data ? 0 : this.min
        },
        maximum () {
            return this.data ? (this.data.length - 1) : this.max
        },
        multiple () {
            let decimals = `${this.interval}`.split('.')[1]
            return decimals ? Math.pow(10, decimals.length) : 1
        },
        spacing () {
            return this.data ? 1 : this.interval
        },
        total () {
            if (this.data) {
                return this.data.length - 1
            } else if (Math.floor((this.maximum - this.minimum) * this.multiple) % (this.interval * this.multiple) !== 0) {
                this.printError('[SlideBar error]: Prop[interval] is illegal, Please make sure that the interval can be divisible')
            }
            return (this.maximum - this.minimum) / this.interval
        },
        gap () {
            return this.size / this.total
        },
        position () {
            return ((this.currentValue - this.minimum) / this.spacing * this.gap)
        },
        limit () {
            return [0, this.size]
        },
        valueLimit () {
            return [this.minimum, this.maximum]
        },
        hasError () {
            return !isEmpty(this.message)
        },
        customLabel () {
            return this.formatter ? this.formatter(this.val) : this.val
        }
    },
    watch: {
        value (val) {
            if (this.flag) {
                this.setValue(val)
            } else {
                this.setValue(val, this.speed)
            }
        },
        max (val) {
            if (val < this.min) {
                return this.printError('[SlideBar error]: The maximum value can not be less than the minimum value.')
            }
            let resetVal = this.limitValue(this.val)
            this.setValue(resetVal)
            this.refresh()
        },
        min (val) {
            if (val > this.max) {
                return this.printError('[SlideBar error]: The minimum value can not be greater than the maximum value.')
            }
            let resetVal = this.limitValue(this.val)
            this.setValue(resetVal)
            this.refresh()
        }
    },
    methods: {
        bindEvents () {
            document.addEventListener('touchmove', this.moving, {passive: false})
            document.addEventListener('touchend', this.moveEnd, {passive: false})
            document.addEventListener('mousemove', this.moving)
            document.addEventListener('mouseup', this.moveEnd)
            document.addEventListener('mouseleave', this.moveEnd)
            window.addEventListener('resize', this.refresh)
        },
        unbindEvents () {
            window.removeEventListener('resize', this.refresh)
            document.removeEventListener('touchmove', this.moving)
            document.removeEventListener('touchend', this.moveEnd)
            document.removeEventListener('mousemove', this.moving)
            document.removeEventListener('mouseup', this.moveEnd)
            document.removeEventListener('mouseleave', this.moveEnd)
        },
        getPos (e) {
            this.realTime && this.getStaticData()
            return e.clientX - this.offset
        },
        wrapClick (e) {
            if (this.disabled || (!this.draggable && e.target.id === this.id)) {
                return false
            }
            let pos = this.getPos(e)
            this.setValueOnPos(pos)
        },
        moveStart (e, index) {
            if (!this.draggable) {
                return false
            }
            this.flag = true
            this.$emit('dragStart', this)
        },
        moving (e) {
            if (!this.flag || !this.draggable) {
                return false
            }
            e.preventDefault()
            if (e.targetTouches && e.targetTouches[0]) {
                e = e.targetTouches[0]
            }
            this.setValueOnPos(this.getPos(e), true)
        },
        moveEnd (e) {
            if (this.flag && this.draggable) {
                this.$emit('dragEnd', this)
                if (this.lazy && this.isDiff(this.val, this.value)) {
                    this.syncValue()
                }
            } else {
                return false
            }
            this.flag = false
            this.setPosition()
        },
        setValueOnPos (pos, isDrag) {
            let range = this.limit
            let valueRange = this.valueLimit
            if (pos >= range[0] && pos <= range[1]) {
                this.setTransform(pos)
                let v = (Math.round(pos / this.gap) * (this.spacing * this.multiple) + (this.minimum * this.multiple)) / this.multiple
                this.setCurrentValue(v, isDrag)
            } 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) => v !== b[i])
            }
            return a !== b
        },
        setCurrentValue (val, bool) {
            if (val < this.minimum || val > this.maximum) {
                return false
            }
            if (this.isDiff(this.currentValue, val)) {
                this.currentValue = val
                if (!this.lazy || !this.flag) {
                    this.syncValue()
                }
            }
            bool || this.setPosition()
        },
        setIndex (val) {
            val = this.spacing * val + this.minimum
            this.setCurrentValue(val)
        },
        setValue (val, speed) {
            if (this.isDiff(this.val, val)) {
                let resetVal = this.limitValue(val)
                this.val = resetVal
                this.syncValue()
            }
            this.$nextTick(() => this.setPosition(speed))
        },
        setPosition (speed) {
            if (!this.flag) {
                this.setTransitionTime(isUndefined(speed) ? this.speed : speed)
            } else {
                this.setTransitionTime(0)
            }
            this.setTransform(this.position)
        },
        setTransform (val) {
            let value = val - ((this.$refs.tooltip.scrollWidth - 2) / 2)
            let translateValue = `translateX(${value}px)`
            this.slider.style.transform = translateValue
            this.slider.style.WebkitTransform = translateValue
            this.slider.style.msTransform = translateValue
            this.$refs.process.style.width = `${val}px`
            this.$refs.process.style['left'] = 0
            if (this.$refs.processDot) {
                this.$refs.processDot.style['left'] = `${val}px`
            }
        },
        setTransitionTime (time) {
            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`
            if (this.$refs.processDot) {
                this.$refs.processDot.style.transitionDuration = `${time}s`
                this.$refs.processDot.style.WebkitTransitionDuration = `${time}s`
            }
        },
        limitValue (val) {
            if (this.data) {
                return val
            }
            const inRange = (v) => {
                if (v < this.min) {
                    this.printError(`[SlideBar warn]: The value of the slider is ${val}, the minimum value is ${this.min}, the value of this slider can not be less than the minimum value`)
                    return this.min
                } else if (v > this.max) {
                    this.printError(`[SlideBar warn]: The value of the slider is ${val}, the maximum value is ${this.max}, the value of this slider can not be greater than the maximum value`)
                    return this.max
                }
                return v
            }
            return inRange(val)
        },
        syncValue () {
            let val = this.val
            if (this.range) {
                this.$emit('callbackRange', this.range[this.currentIndex])
            }
            this.$emit('input', val)
        },
        getValue () {
            return this.val
        },
        getIndex () {
            return this.currentIndex
        },
        getStaticData () {
            if (this.$refs.elem) {
                this.size = this.$refs.elem.offsetWidth
                this.offset = this.$refs.elem.getBoundingClientRect().left
            }
        },
        refresh () {
            if (this.$refs.elem) {
                this.getStaticData()
                this.setPosition()
            }
        },
        printError (msg) {
            console.error(msg)
        }
    },
    mounted () {
        this.isComponentExists = true
        if (isUndefined(window) || isUndefined(document)) {
            return this.printError('[VueSlideBar error]: window or document is undefined, can not be initialized.')
        }
        this.$nextTick(() => {
            if (this.isComponentExists) {
                this.getStaticData()
                this.setValue(this.limitValue(this.value), 0)
                this.bindEvents()
            }
        })
    },
    beforeDestroy () {
        this.isComponentExists = false
        this.unbindEvents()
    }
}
</script>
