import { getRect, prepend, removeChild } from '../util/dom'
import { ease } from '../util/ease'
import { extend } from '../util/lang'
import { warn } from '../util/debug'

export function snapMixin(BScroll) {
  BScroll.prototype._initSnap = function () {
    this.currentPage = {}
    const snap = this.options.snap

    if (snap.loop) {
      let children = this.scroller.children
      if (children.length > 1) {
        prepend(children[children.length - 1].cloneNode(true), this.scroller)
        this.scroller.appendChild(children[1].cloneNode(true))
      } else {
        // Loop does not make any sense if there is only one child.
        snap.loop = false
      }
    }

    let el = snap.el
    if (typeof el === 'string') {
      el = this.scroller.querySelectorAll(el)
    }

    this.on('refresh', () => {
      this.pages = []

      if (!this.wrapperWidth || !this.wrapperHeight || !this.scrollerWidth || !this.scrollerHeight) {
        return
      }

      let stepX = snap.stepX || this.wrapperWidth
      let stepY = snap.stepY || this.wrapperHeight

      let x = 0
      let y
      let cx
      let cy
      let i = 0
      let l
      let m = 0
      let n
      let rect
      if (!el) {
        cx = Math.round(stepX / 2)
        cy = Math.round(stepY / 2)

        while (x > -this.scrollerWidth) {
          this.pages[i] = []
          l = 0
          y = 0

          while (y > -this.scrollerHeight) {
            this.pages[i][l] = {
              x: Math.max(x, this.maxScrollX),
              y: Math.max(y, this.maxScrollY),
              width: stepX,
              height: stepY,
              cx: x - cx,
              cy: y - cy
            }

            y -= stepY
            l++
          }

          x -= stepX
          i++
        }
      } else {
        l = el.length
        n = -1

        for (; i < l; i++) {
          rect = getRect(el[i])
          if (i === 0 || rect.left <= getRect(el[i - 1]).left) {
            m = 0
            n++
          }

          if (!this.pages[m]) {
            this.pages[m] = []
          }

          x = Math.max(-rect.left, this.maxScrollX)
          y = Math.max(-rect.top, this.maxScrollY)
          cx = x - Math.round(rect.width / 2)
          cy = y - Math.round(rect.height / 2)

          this.pages[m][n] = {
            x: x,
            y: y,
            width: rect.width,
            height: rect.height,
            cx: cx,
            cy: cy
          }

          if (x > this.maxScrollX) {
            m++
          }
        }
      }

      this._checkSnapLoop()

      let initPageX = snap._loopX ? 1 : 0
      let initPageY = snap._loopY ? 1 : 0
      this._goToPage(this.currentPage.pageX || initPageX, this.currentPage.pageY || initPageY, 0)

      // Update snap threshold if needed.
      const snapThreshold = snap.threshold
      if (snapThreshold % 1 === 0) {
        this.snapThresholdX = snapThreshold
        this.snapThresholdY = snapThreshold
      } else {
        this.snapThresholdX = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].width * snapThreshold)
        this.snapThresholdY = Math.round(this.pages[this.currentPage.pageX][this.currentPage.pageY].height * snapThreshold)
      }
    })

    this.on('scrollEnd', () => {
      if (snap.loop) {
        if (snap._loopX) {
          if (this.currentPage.pageX === 0) {
            this._goToPage(this.pages.length - 2, this.currentPage.pageY, 0)
          }
          if (this.currentPage.pageX === this.pages.length - 1) {
            this._goToPage(1, this.currentPage.pageY, 0)
          }
        } else {
          if (this.currentPage.pageY === 0) {
            this._goToPage(this.currentPage.pageX, this.pages[0].length - 2, 0)
          }
          if (this.currentPage.pageY === this.pages[0].length - 1) {
            this._goToPage(this.currentPage.pageX, 1, 0)
          }
        }
      }
    })

    if (snap.listenFlick !== false) {
      this.on('flick', () => {
        let time = snap.speed || Math.max(
            Math.max(
              Math.min(Math.abs(this.x - this.startX), 1000),
              Math.min(Math.abs(this.y - this.startY), 1000)
            ), 300)

        this._goToPage(
          this.currentPage.pageX + this.directionX,
          this.currentPage.pageY + this.directionY,
          time
        )
      })
    }

    this.on('destroy', () => {
      if (snap.loop) {
        let children = this.scroller.children
        if (children.length > 2) {
          removeChild(this.scroller, children[children.length - 1])
          removeChild(this.scroller, children[0])
        }
      }
    })
  }

  BScroll.prototype._checkSnapLoop = function () {
    const snap = this.options.snap

    if (!snap.loop || !this.pages || !this.pages.length) {
      return
    }

    if (this.pages.length > 1) {
      snap._loopX = true
    }
    if (this.pages[0] && this.pages[0].length > 1) {
      snap._loopY = true
    }
    if (snap._loopX && snap._loopY) {
      warn('Loop does not support two direction at the same time.')
    }
  }

  BScroll.prototype._nearestSnap = function (x, y) {
    if (!this.pages.length) {
      return {x: 0, y: 0, pageX: 0, pageY: 0}
    }

    let i = 0
    // Check if we exceeded the snap threshold
    if (Math.abs(x - this.absStartX) <= this.snapThresholdX &&
      Math.abs(y - this.absStartY) <= this.snapThresholdY) {
      return this.currentPage
    }

    if (x > this.minScrollX) {
      x = this.minScrollX
    } else if (x < this.maxScrollX) {
      x = this.maxScrollX
    }

    if (y > this.minScrollY) {
      y = this.minScrollY
    } else if (y < this.maxScrollY) {
      y = this.maxScrollY
    }

    let l = this.pages.length
    for (; i < l; i++) {
      if (x >= this.pages[i][0].cx) {
        x = this.pages[i][0].x
        break
      }
    }

    l = this.pages[i].length

    let m = 0
    for (; m < l; m++) {
      if (y >= this.pages[0][m].cy) {
        y = this.pages[0][m].y
        break
      }
    }

    if (i === this.currentPage.pageX) {
      i += this.directionX

      if (i < 0) {
        i = 0
      } else if (i >= this.pages.length) {
        i = this.pages.length - 1
      }

      x = this.pages[i][0].x
    }

    if (m === this.currentPage.pageY) {
      m += this.directionY

      if (m < 0) {
        m = 0
      } else if (m >= this.pages[0].length) {
        m = this.pages[0].length - 1
      }

      y = this.pages[0][m].y
    }

    return {
      x,
      y,
      pageX: i,
      pageY: m
    }
  }

  BScroll.prototype._goToPage = function (x, y = 0, time, easing) {
    const snap = this.options.snap

    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    easing = easing || snap.easing || ease.bounce

    if (x >= this.pages.length) {
      x = this.pages.length - 1
    } else if (x < 0) {
      x = 0
    }

    if (!this.pages[x]) {
      return
    }

    if (y >= this.pages[x].length) {
      y = this.pages[x].length - 1
    } else if (y < 0) {
      y = 0
    }

    let posX = this.pages[x][y].x
    let posY = this.pages[x][y].y

    time = time === undefined ? snap.speed || Math.max(
        Math.max(
          Math.min(Math.abs(posX - this.x), 1000),
          Math.min(Math.abs(posY - this.y), 1000)
        ), 300) : time

    this.currentPage = {
      x: posX,
      y: posY,
      pageX: x,
      pageY: y
    }
    this.scrollTo(posX, posY, time, easing)
  }

  BScroll.prototype.goToPage = function (x, y, time, easing) {
    const snap = this.options.snap
    if (!snap || !this.pages || !this.pages.length) {
      return
    }

    if (snap.loop) {
      let len
      if (snap._loopX) {
        len = this.pages.length - 2
        if (x >= len) {
          x = len - 1
        } else if (x < 0) {
          x = 0
        }
        x += 1
      } else {
        len = this.pages[0].length - 2
        if (y >= len) {
          y = len - 1
        } else if (y < 0) {
          y = 0
        }
        y += 1
      }
    }
    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.next = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x++
    if (x >= this.pages.length && this.hasVerticalScroll) {
      x = 0
      y++
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.prev = function (time, easing) {
    const snap = this.options.snap
    if (!snap) {
      return
    }

    let x = this.currentPage.pageX
    let y = this.currentPage.pageY

    x--
    if (x < 0 && this.hasVerticalScroll) {
      x = 0
      y--
    }

    this._goToPage(x, y, time, easing)
  }

  BScroll.prototype.getCurrentPage = function () {
    const snap = this.options.snap
    if (!snap) {
      return null
    }

    if (snap.loop) {
      let currentPage
      if (snap._loopX) {
        currentPage = extend({}, this.currentPage, {
          pageX: this.currentPage.pageX - 1
        })
      } else {
        currentPage = extend({}, this.currentPage, {
          pageY: this.currentPage.pageY - 1
        })
      }
      return currentPage
    }
    return this.currentPage
  }
}