<template>
  <div>
    <div class="row items-center">
      <div class="col-auto" style="align-self: stretch;">
        <q-btn flat class="full-height q-py-lg" size="sm" icon="fas fa-chevron-left" @click="move(-col)" aria-label="previous slide"/>
      </div>
      <div class="col">

        <div :class="{'slider-loading': loading, slider: !loading}" ref="slideWrapper" :style="currentSlideHeightStyle">
          <div
            :class="{
              one:col===1,
              two:col===2,
              three:col===3,
              four:col===4,
              five:col===5,
              six:col===6,
              seven:col===7,
              eight:col===8,
              nine:col===9,
              ten:col===10,
              eleven:col===11,
              twelve:col===12
            }"
            class="flex items-center"
            style="cursor: grab;"
            ref="slides"
            :style="`transform:translateX(${xPosition}px);`"
            v-for="(slide,index) in slides"
            :key="index"
            v-touch-pan.horizontal.prevent.mouse="interaction">
            <component :is="slide.name" :options="slide.options" class="slide-inner full-width full-height"/>
          </div>
        </div>
      </div>
      <div class="col-auto" style="align-self: stretch;">
        <q-btn flat class="full-height q-py-lg" size="sm" icon="fas fa-chevron-right" @click="move(col)" aria-label="next slide"/>
      </div>
    </div>
    <div>
    </div>
  </div>
</template>

<script>
import { dom } from 'quasar'
const { width, height } = dom
const { exp, round, random } = Math
import take from 'lodash/take'
import takeRight from 'lodash/takeRight'
import max from 'lodash/max'
import isUndefined from 'lodash/isUndefined'
import throttle from 'lodash/throttle'
import { tween } from 'shifty'
const defined = (...args) => !isUndefined(...args)

export default {
  computed: {
    col () {
      if (process.env.CLIENT) {
        if (defined(this.options.xlCol) && this.windowWidth > 1919) return this.options.xl
        if (defined(this.options.lgCol) && this.windowWidth > 1439) return this.options.lgCol
        if (defined(this.options.mdCol) && this.windowWidth > 1023) return this.options.mdCol
        if (defined(this.options.smCol) && this.windowWidth > 599) return this.options.smCol
        if (defined(this.options.xsCol) && this.windowWidth < 600) return this.options.xsCol
      }
      return this.options.col || 3
    }
  },
  data () {
    return {
      // Settings
      scrollDecay: 100,
      snappiness: 2,
      throwWeight: 0.2, // lower is heavier

      // Data
      windowWidth: 1000,
      slides: this.options.slides,
      currentSlideHeightStyle: 'height: auto',
      currentSlideHeight: 0,
      slideIndex: 0,
      delta: 0,
      target: 0,
      amplitude: 0,
      now: 0,
      elapsed: 0,
      timestamp: 0,
      velocity: 0,
      slidesWidth: 0,
      xPosition: 0,
      loading: true
    }
  },
  destroyed () {
    window.removeEventListener('resize', this.calcColStuff)
    window.removeEventListener('resize', this.resizeSlider)
    window.removeEventListener('resize', this.setWindowWidth)
  },
  mounted () {
    this.setSlideHeight = throttle(this.setSlideHeight, 100)
    this.calcColStuff = throttle(this.calcColStuff, 100)
    this.resizeSlider = throttle(this.resizeSlider, 100)
    this.setWindowWidth = throttle(this.setWindowWidth, 100)

    window.addEventListener('resize', this.calcColStuff)
    window.addEventListener('resize', this.resizeSlider)
    window.addEventListener('resize', this.setWindowWidth)

    this.setSlideHeight(true)
    this.setWindowWidth()
    this.$nextTick(() => { this.calcColStuff() })

    this.loading = false
  },
  methods: {
    round,
    random,
    calcColStuff () {
      this.snap = width(this.$refs.slides[0])
      this.slidesWidth = this.$refs.slides.reduce((l, c) => l + width(c), 0)
      this.xPosition = this.snap * -this.col
      this.prepareSlides()
    },
    prepareSlides () {
      this.slides = [ ...takeRight(this.options.slides, this.col), ...this.options.slides, ...take(this.options.slides, this.col) ]
    },
    setWindowWidth () {
      this.windowWidth = width(window)
    },
    setSlideHeight (noTween) {
      let els = this.$refs.slides.slice(this.slideIndex + this.col, (this.col * 2) + this.slideIndex)
      const heights = els.map(el => {
        if (el && el.childNodes) {
          el = el.$el ? el.$el : el
          return Array.from(el.childNodes).reduce((l, e) => e.nodeType === 8 ? l : l + height(e), 0)
        }
        return 0
      })
      const newSlideHeight = max(heights)
      if (newSlideHeight === this.currentSlideHeight || this.currentSlideHeight === 0) return
      if (noTween) {
        this.currentSlideHeightStyle = `height: ${newSlideHeight}px`
      } else {
        tween({
          from: { y: this.currentSlideHeight },
          to: { y: newSlideHeight },
          duration: 300,
          easing: 'easeOutQuad',
          step: state => { this.currentSlideHeightStyle = `height: ${state.y}px` }
        })
      }
      this.currentSlideHeight = newSlideHeight
    },
    resizeSlider () {
      this.snap = width(this.$refs.slides[0])
      this.slidesWidth = this.$refs.slides.reduce((l, c) => l + width(c), 0) - this.snap * (this.col * 2)
      this.xPosition = this.snap * ((-this.col) - this.slideIndex)
    },
    move (slides) {
      this.amplitude = -(this.snap * slides)
      this.target = this.xPosition - (this.snap * slides)
      this.target = round(this.target / this.snap) * this.snap
      this.timestamp = Date.now()
      requestAnimationFrame(this.autoScroll)
    },
    interactStart () {
      this.velocity = 0
      this.amplitude = 0
      this.timestamp = Date.now()
    },
    interaction ({ isFirst, isFinal, delta }) {
      this.delta = delta
      if (isFirst) return this.interactStart()
      if (isFinal) return this.interactEnd()

      this.updatePos(this.xPosition + this.delta.x)

      const now = Date.now()
      this.elapsed = now - this.timestamp
      this.timestamp = now

      const v = 1000 * delta.x / (1 + this.elapsed)
      this.velocity = 0.8 * v + 0.2 * this.velocity
    },
    interactEnd () {
      // I don't know what this does
      // if (this.velocity > 0.1 || this.velocity < -0.1) {
      this.amplitude = this.throwWeight * this.velocity
      this.target = round(this.xPosition + this.amplitude)
      // }
      this.target = round(this.target / this.snap) * this.snap
      this.amplitude = this.target - this.xPosition
      this.timestamp = Date.now()
      requestAnimationFrame(this.autoScroll)
    },
    autoScroll () {
      if (this.amplitude) {
        this.elapsed = Date.now() - this.timestamp
        const deltaX = -this.amplitude * exp(-this.elapsed / this.scrollDecay)
        if (deltaX > this.snappiness || deltaX < -this.snappiness) {
          this.updatePos(+this.target + deltaX)
          requestAnimationFrame(this.autoScroll)
        } else {
          this.updatePos(+this.target)
        }
      }
    },
    updatePos (x) {
      this.xPosition = x
      this.slideIndex = round(this.xPosition / (-this.snap)) - this.col
      this.setSlideHeight()
      if (this.slidesWidth + x <= 0) {
        this.xPosition = 0
        this.amplitude = this.amplitude - this.slidesWidth
        this.target = this.target + this.slidesWidth
      }
      if (x >= 0) {
        this.xPosition = -this.slidesWidth
        this.amplitude = this.amplitude + this.slidesWidth
        this.target = this.target - this.slidesWidth
      }
    }
  },
  props: {
    options: Object
  },
  watch: {
    col () {
      this.$nextTick(() => { this.prepareSlides() })
    }
  }
}
</script>

<style lang="stylus" scoped>
.slider-loading
  display flex
  overflow auto
  overflow-scrolling touch
.slider
  display flex
  overflow hidden
  overflow-scrolling hidden
.slider-loading > div, .slider > div
  flex-shrink 0
  min-height 100px
  &.one
    width 100%
  &.two
    width 50%
  &.three
    width 33.33334%
  &.four
    width 25%
  &.five
    width 20%
  &.six
    width 16.66667%
  &.seven
    width 14.28571%
  &.eight
    width 12.5%
  &.nine
    width 11.11111%
  &.ten
    width 10%
  &.eleven
    width 9.09090%
  &.twelve
    width 8.33334%
.slider-loading .slide-inner, .slider .slide-inner
  background black
  color white
  margin-left 1px
</style>
