<template>
  <div :class="createClass()">
    <div
      :style="trackStyle"
      :class="createClass('track')"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @touchcancel="onTouchEnd"
      @transitionend.stop="$emit('change', activeIndicator)"
    >
      <slot />
    </div>
    <slot name="indicator">
      <div
        v-if="showIndicators && count > 1"
        :class="createClass('indicators', { vertical })"
        @transitionend.stop
      >
        <i
          v-for="index in count"
          :key="index"
          :class="createClass('indicator', { active: index - 1 === activeIndicator })"
          :style="index - 1 === activeIndicator ? indicatorStyle : null"
        />
      </div>
    </slot>
  </div>
</template>

<script>
import create from '../utils/create'
import Touch from '../mixins/touch'
import { on, off } from '../utils/event'

export default create({
  name: 'swipe',

  mixins: [Touch],

  props: {
    width: {
      type: Number,
      default: 0
    },
    height: {
      type: Number,
      default: 0
    },
    autoplay: Number,
    vertical: Boolean,
    initialSwipe: {
      type: Number,
      default: 0
    },
    indicatorColor: String,
    loop: {
      type: Boolean,
      default: true
    },
    touchable: {
      type: Boolean,
      default: true
    },
    showIndicators: {
      type: Boolean,
      default: true
    },
    duration: {
      type: Number,
      default: 500
    }
  },

  data () {
    return {
      computedWidth: 0,
      computedHeight: 0,
      offset: 0,
      active: 0,
      deltaX: 0,
      deltaY: 0,
      swipes: [],
      swiping: false
    }
  },

  mounted () {
    this.initialize()

    if (!this.$isServer) {
      on(window, 'resize', this.onResize, true)
    }
  },

  activated () {
    if (this.rendered) {
      this.initialize()
    }

    this.rendered = true
  },

  destroyed () {
    this.clear()

    if (!this.$isServer) {
      off(window, 'resize', this.onResize, true)
    }
  },

  watch: {
    swipes () {
      this.initialize()
    },

    initialSwipe () {
      this.initialize()
    },

    autoplay (autoplay) {
      if (!autoplay) {
        this.clear()
      } else {
        this.autoPlay()
      }
    }
  },

  computed: {
    count () {
      return this.swipes.length
    },

    delta () {
      return this.vertical ? this.deltaY : this.deltaX
    },

    size () {
      return this[this.vertical ? 'computedHeight' : 'computedWidth']
    },

    trackSize () {
      return this.count * this.size
    },

    activeIndicator () {
      return (this.active + this.count) % this.count
    },

    isCorrectDirection () {
      const expect = this.vertical ? 'vertical' : 'horizontal'
      return this.direction === expect
    },

    trackStyle () {
      const mainAxis = this.vertical ? 'height' : 'width'
      const crossAxis = this.vertical ? 'width' : 'height'
      return {
        [mainAxis]: `${this.trackSize}px`,
        [crossAxis]: this[crossAxis] ? `${this[crossAxis]}px` : '',
        transitionDuration: `${this.swiping ? 0 : this.duration}ms`,
        transform: `translate${this.vertical ? 'Y' : 'X'}(${this.offset}px)`
      }
    },

    indicatorStyle () {
      return {
        backgroundColor: this.indicatorColor
      }
    }
  },

  methods: {
    // initialize swipe position
    initialize (active = this.initialSwipe) {
      clearTimeout(this.timer)
      if (this.$el) {
        const rect = this.$el.getBoundingClientRect()
        this.computedWidth = this.width || rect.width
        this.computedHeight = this.height || rect.height
      }
      this.swiping = true
      this.active = active
      this.offset = this.count > 1 ? -this.size * this.active : 0
      this.swipes.forEach(swipe => {
        swipe.offset = 0
      })
      this.autoPlay()
    },

    onResize () {
      this.initialize(this.activeIndicator)
    },

    onTouchStart (event) {
      if (!this.touchable) return

      this.clear()
      this.swiping = true
      this.touchStart(event)
      this.correctPosition()
    },

    onTouchMove (event) {
      if (!this.touchable || !this.swiping) return

      this.touchMove(event)

      if (this.isCorrectDirection) {
        event.preventDefault()
        event.stopPropagation()
        this.move(0, Math.min(Math.max(this.delta, -this.size), this.size))
      }
    },

    onTouchEnd () {
      if (!this.touchable || !this.swiping) return

      if (this.delta && this.isCorrectDirection) {
        const offset = this.vertical ? this.offsetY : this.offsetX
        this.move(offset > 0 ? (this.delta > 0 ? -1 : 1) : 0)
      }

      this.swiping = false
      this.autoPlay()
    },

    move (move = 0, offset = 0) {
      const { delta, active, count, swipes, trackSize } = this
      const atFirst = active === 0
      const atLast = active === count - 1
      const outOfBounds =
        !this.loop &&
        ((atFirst && (offset > 0 || move < 0)) ||
          (atLast && (offset < 0 || move > 0)))

      if (outOfBounds || count <= 1) {
        return
      }

      if (swipes[0]) {
        swipes[0].offset = atLast && (delta < 0 || move > 0) ? trackSize : 0
      }

      if (swipes[count - 1]) {
        swipes[count - 1].offset = atFirst && (delta > 0 || move < 0) ? -trackSize : 0
      }

      if (move && active + move >= -1 && active + move <= count) {
        this.active += move
      }

      this.offset = offset - this.active * this.size
    },

    swipeTo (index) {
      this.swiping = true
      this.resetTouchStatus()
      this.correctPosition()
      setTimeout(() => {
        this.swiping = false
        this.move((index % this.count) - this.active)
      }, 30)
    },

    correctPosition () {
      if (this.active <= -1) {
        this.move(this.count)
      }
      if (this.active >= this.count) {
        this.move(-this.count)
      }
    },

    clear () {
      clearTimeout(this.timer)
    },

    autoPlay () {
      const { autoplay } = this

      if (autoplay && this.count > 1) {
        this.clear()
        this.timer = setTimeout(() => {
          this.swiping = true
          this.resetTouchStatus()
          this.correctPosition()

          setTimeout(() => {
            this.swiping = false
            this.move(1)
            this.autoPlay()
          }, 30)
        }, autoplay)
      }
    }
  }
})
</script>
