<template>
  <div
    class="sv-carousel"
    :style="carouselStyle"
    @touchstart="handleTouchStart"
    @touchmove="handleTouchMove"
    @touchend="handleTouchEnd"
    v-event:tap="handleClick">
    <ul class="sv-carousel-list" ref="carouselListRef">
      <slot>
        <li class="sv-carousel-item" v-if="loop">
          <img :src="data[data.length - 1]" class="image">
        </li>
        <template v-for="(image, $index) in data">
          <li class="sv-carousel-item">
            <img :src="image" class="image">
          </li>
        </template>
        <li class="sv-carousel-item" v-if="loop">
          <img :src="data[0]" class="image">
        </li>
      </slot>
    </ul>
    <ul class="sv-carousel-indicator" v-if="showIndicator">
      <template v-for="number in carouselItemCount">
        <li class="indicator-item" :class="{'active': number - 1 === currentIndex}"></li>
      </template>
    </ul>
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    name: 'svCarousel',
    data () {
      return {
        carouselWidth: this.width,
        touchHandler: {
          startX: 0,
          currentX: 0,
          endX: 0,
          translateX: 0,
          threshold: 100, // 阈值
          startTime: 0
        },
        direction: 'left', // 轮播的方向 left/right
        currentIndex: 0,
        autoplayInterval: null,
        carouselItemCount: 0
      }
    },
    props: {
      data: {
        type: Array,
        default () {
          return []
        }
      },
      showIndicator: {
        type: Boolean,
        default: true
      },
      autoplay: {
        type: Boolean,
        default: false
      },
      duration: {
        type: Number,
        default: 3000
      },
      loop: {
        type: Boolean,
        default: true
      },
      active: {
        type: Number,
        default: 0,
        validator (val) {
          return Number.isInteger(val)
        }
      },
      width: {
        type: [Number, String],
        default: '100%'
      },
      height: {
        type: [Number, String],
        default: '200px'
      }
    },
    computed: {
      carouselStyle () {
        return {
          width: this.getCssProp(this.width),
          height: this.getCssProp(this.height)
        }
      },
      indicatorItemClass () {
        return ``
      }
    },
    watch: {
      currentIndex (val) {
        if (val >= 0 && val <= this.carouselItemCount - 1) {
          this.$emit('change', val)
        }
      }
    },
    methods: {
      getCssProp (value) {
        if (!value) return ''
        if (typeof value === 'number' || (typeof value === 'string' && window.parseInt(value) + '' === value)) {
          return `${value}px`
        }
        return value
      },
      handleTouchStart (event) {
        this.autoplay && this.clearAutoPlay()
        this.touchHandler.startX = event.touches[0].pageX
        this.touchHandler.startTime = Date.now()
      },
      handleTouchMove (event) {
        let currentX = event.touches[0].pageX
        let currentTime = new Date().getTime()
        let {startX} = this.touchHandler

        this.direction = currentX < this.touchHandler.startX ? 'left' : 'right'
        if (this.direction === 'left') { // 向左滑动
          if (this.loop) {
            if (this.currentIndex <= this.carouselItemCount) {
              // 计算当前激活的图片index
              this.currentIndex = Math.floor(Math.abs(this.touchHandler.translateX) / this.carouselWidth) - 1
              this.touchHandler.translateX += (currentX - startX) / 10
            } else {
              this.currentIndex = this.carouselItemCount - 1
              this.touchHandler.translateX += (currentX - startX) / ((currentTime - this.touchHandler.startTime) / 10)
            }
          } else {
            if (this.currentIndex <= this.carouselItemCount) {
              // 计算当前激活的图片index
              this.currentIndex = Math.floor(Math.abs(this.touchHandler.translateX) / this.carouselWidth)
              this.touchHandler.translateX += (currentX - startX) / 10
            } else {
              this.currentIndex = this.carouselItemCount - 1
              this.touchHandler.translateX += (currentX - startX) / ((currentTime - this.touchHandler.startTime) / 10)
            }
          }
        } else { // 向右滑动
          if (this.loop) {
            this.touchHandler.translateX += (currentX - startX) / 10
            if (this.touchHandler.translateX >= 0) {
              this.currentIndex = this.carouselItemCount - 1
              this.translateX = -this.currentIndex * this.carouselWidth
            }
          } else {
            if (this.touchHandler.translateX < 0) {
              // 计算当前激活的图片index
              this.currentIndex = Math.ceil(Math.abs(this.touchHandler.translateX) / this.carouselWidth)
              this.touchHandler.translateX += (currentX - startX) / 10
            } else {
              this.currentIndex = 0
              this.touchHandler.translateX += (currentX - startX) / ((currentTime - this.touchHandler.startTime) / 10)
            }
          }
        }

        this.slide(this.touchHandler.translateX)
      },
      handleTouchEnd (event) {
        let touch = event.changedTouches[0]
        let endX = touch.pageX
        let slideX = endX - this.touchHandler.startX
        let duration = 0.3

        this.direction = endX < this.touchHandler.startX ? 'left' : 'right'
        if (Math.abs(slideX) > this.touchHandler.threshold) {
          if (this.direction === 'left') { // 向左滑动
            this.currentIndex = ++this.currentIndex

            if (this.loop) {
              if (this.currentIndex === this.carouselItemCount) {
                let loopTimeout = window.setTimeout(() => {
                  this.currentIndex = 0
                  this.touchHandler.translateX = -this.carouselWidth
                  this.slide(this.touchHandler.translateX, 0)
                  window.clearTimeout(loopTimeout)
                }, duration * 1000)
              }
            } else {
              (this.currentIndex > this.carouselItemCount - 1) && (this.currentIndex = this.carouselItemCount - 1)
            }
          } else { // 向右滑动
            this.currentIndex = --this.currentIndex
            if (this.loop) {
              if (this.currentIndex === -1) {
                let loopTimeout = window.setTimeout(() => {
                  this.currentIndex = this.carouselItemCount
                  this.touchHandler.translateX = -(this.currentIndex + 1) * this.carouselWidth
                  this.slide(this.touchHandler.translateX, 0)
                  window.clearTimeout(loopTimeout)
                }, duration * 1000)
              }
            } else {
              (this.currentIndex < 0) && (this.currentIndex = 0)
            }
          }
        }

        if (this.loop) {
          this.touchHandler.translateX = -(this.currentIndex + 1) * this.carouselWidth
        } else {
          this.touchHandler.translateX = -this.currentIndex * this.carouselWidth
        }

        this.slide(this.touchHandler.translateX, duration)
        this.autoplay && this.autoPlay()
      },
      slide (distance, duration = 0.2, timing = 'linear') {
        this.$refs.carouselListRef.style.transform = `translate3d(${distance}px, 0, 0)`
        this.$refs.carouselListRef.style.transition = duration > 0 ? `transform ${duration}s ${timing}` : ''
      },
      autoPlay () {
        this.autoplayInterval = window.setInterval(() => {
          this.currentIndex += 1

          if (this.currentIndex === this.carouselItemCount) {
            let resetTimeout = window.setTimeout(() => {
              this.currentIndex = 0
              this.touchHandler.translateX = -this.carouselWidth
              this.slide(this.touchHandler.translateX, 0)
              window.clearInterval(resetTimeout)
            }, 400)
          }
          this.touchHandler.translateX = -(this.currentIndex + 1) * this.carouselWidth
          this.slide(this.touchHandler.translateX, 0.4)
        }, this.duration)
      },
      clearAutoPlay () {
        if (!this.autoplayInterval) return

        window.clearInterval(this.autoplayInterval)
        this.autoplayInterval = null
      },
      handleClick () {
        this.$emit('click', this.currentIndex)
      }
    },
    created () {
      if (this.autoplay) {
        this.loop = true
        this.autoPlay()
      }

      this.$nextTick(() => {
        let carouselRect = this.$el.getBoundingClientRect()
        this.carouselWidth = carouselRect.width

        this.currentIndex = this.active
        this.touchHandler.translateX = this.loop ? -(this.currentIndex + 1) * this.carouselWidth : this.currentIndex * this.carouselWidth
        this.slide(this.touchHandler.translateX, 0)
      })
    },
    mounted () {
      this.$nextTick(() => {
        this.carouselItemCount = this.data.length || this.$children.length
        if (this.loop && this.$children.length > 0) {
          let itemsNode = this.$refs.carouselListRef.querySelectorAll('.sv-carousel-item')
          let lastItemNode = itemsNode[itemsNode.length - 1].cloneNode(true)
          let firstItemNode = itemsNode[0].cloneNode(true)
          this.$refs.carouselListRef.appendChild(firstItemNode) // clone第一个item向最后插入一份
          this.$refs.carouselListRef.insertBefore(lastItemNode, itemsNode[0])
        }
      })
    }
  }
</script>

<style lang="scss" rel="stylesheet/scss">
  @import "../../styles/base";

  .sv-carousel{
    position: relative;
    overflow: hidden;
    .sv-carousel-list{
      display: flex;
      flex-wrap: nowrap;
      height: 100%;
      .sv-carousel-item{
        width: 100%;
        height: 100%;
        flex-shrink: 0;
        .image{
          width: 100%;
          height: 100%;
        }
      }
    }
    .sv-carousel-indicator{
      position: absolute;
      bottom: pxToRem(20);
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      justify-content: center;
      align-items: center;
      .indicator-item{
        width: pxToRem(20);
        height: 0;
        border-top: pxToRem(4) solid $color-stable;
        &:not(:last-child){
          margin-right: pxToRem(10);
        }
        &.active{
          width: pxToRem(10);
          height: pxToRem(10);
          border-radius: 50%;
          background: transparent;
          border: pxToRem(2) solid $color-lively;
        }
      }
    }
  }
</style>
