/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

const DEFAULT_OFFSET_ACCURACY = 10
const DEFAULT_LOADMORE_OFFSET = 0

function getThrottledScroll (context) {
  const scale = weex.config.env.scale
  if (!context._throttleScroll) {
    const wrapper = context.$refs.wrapper
    const inner = context.$refs.inner
    let preOffset = (context.scrollDirection === 'horizontal'
      ? wrapper.scrollLeft
      : wrapper.scrollTop)
      || 0
    context._throttleScroll = weex.utils.throttle(function (evt) {
        let offsetMap = {x:0,y:0};
      let offset = context.scrollDirection === 'horizontal'
        ? offsetMap.x = wrapper.scrollLeft
        : offsetMap.y = wrapper.scrollTop
        if(process.env.RUNTIME==="web"  && !context.verticalNeedScroll && !context.horizontalNeedScroll){
            offset = context.scrollDirection === 'horizontal'
                ? offsetMap.x = (document.documentElement.scrollLeft||document.body.scrollLeft)
                : offsetMap.y = (document.documentElement.scrollTop||document.body.scrollTop);
        }
      const indent = parseInt(context.offsetAccuracy) * scale
      function triggerScroll () {
        const rect = inner.getBoundingClientRect()
        const evtObj = {
          contentSize: { width: rect.width, height: rect.height },
          contentOffset: {
            x: offsetMap.x,
            /**
             * positive direciton for y-axis is down.
             * so should use negative operation on scrollTop.
             *
             *  (0,0)---------------> x
             *       |
             *       |
             *       |
             *       |
             *       v y
             *
             */
            y: -offsetMap.y
          }
        }
        if (context.$el) {
          weex.utils.dispatchNativeEvent(context.$el, 'weex$scroll', evtObj)
        }
      }
      if (Math.abs(offset - preOffset) >= indent) {
        triggerScroll()
        preOffset = offset
      }
    }, 16, true)
  }
  return context._throttleScroll
}

export default {
  data () {
    return {
      // 组件本身纵向是否会滚动
      verticalNeedScroll: false,
      // 组件本身横向是否会滚动
      horizontalNeedScroll: false
    }
  },
  props: {
    loadmoreoffset: {
      type: [String, Number],
      default: DEFAULT_LOADMORE_OFFSET,
      validator (value) {
        const val = parseInt(value)
        return !isNaN(val) && val >= DEFAULT_LOADMORE_OFFSET
      }
    },

    offsetAccuracy: {
      type: [Number, String],
      default: DEFAULT_OFFSET_ACCURACY,
      validator (value) {
        const val = parseInt(value)
        return !isNaN(val) && val >= DEFAULT_OFFSET_ACCURACY
      }
    }
  },

  created () {
    // should call resetLoadmore() to enable loadmore event.
    this._loadmoreReset = true
  },

  mounted () {
    this.reloadStickyChildren();
    // // 92869 scroller组件不设高度，无法发出scroll事件
    // this.$el.style.height = this.$el.scrollHeight - 1 + 'px';
    // 只有scroller 有横向滚动
    this.verticalNeedScroll = this.$el.scrollHeight > this.$el.clientHeight && (this.scrollDirection !== 'horizontal' || this.weexType !== 'scroller')
    this.horizontalNeedScroll = this.$el.scrollWidth > this.$el.clientWidth && this.scrollDirection === 'horizontal'
      if(process.env.RUNTIME==="web"  && !this.verticalNeedScroll && !this.horizontalNeedScroll){
        document.addEventListener("scroll",this.handleScroll,true)
      }
  },
    destroyed(){
        if(process.env.RUNTIME==="web"  && !this.verticalNeedScroll && !this.horizontalNeedScroll){
            document.removeEventListener("scroll",this.handleScroll,true)
        }
    },

  updated () {
    this.reloadStickyChildren()
  },

  methods: {
    updateLayout () {
      const wrapper = this.$refs.wrapper
      if (wrapper) {
        const rect = wrapper.getBoundingClientRect()
        this._wrapperWidth = rect.width
        this._wrapperHeight = rect.height
      }
      const inner = this.$refs.inner
      const children = inner && inner.children
      if (inner) {
        const rect = inner.getBoundingClientRect()
        this._innerWidth = rect.width
        this._innerHeight = rect.height
      }
      const loadingEl = this._loading && this._loading.$el
      const refreshEl = this._refresh && this._refresh.$el
      if (loadingEl) {
        this._innerHeight -= loadingEl.getBoundingClientRect().height
      }
      if (refreshEl) {
        this._innerHeight -= refreshEl.getBoundingClientRect().height
      }
      // inner width is always the viewport width somehow in horizontal
      // scoller, therefore the inner width should be reclaculated.
      if (this.scrollDirection === 'horizontal' && children) {
        this._innerWidth = weex.utils.getRangeWidth(inner)
      }
    },

    resetLoadmore () {
      this._loadmoreReset = true
    },

    /**
     * process sticky children in scrollable components.
     * current only support list and vertical scroller.
     */
    processSticky () {
      /**
       * current browser support 'sticky' or '-webkit-sticky', so there's no need
       * to do further more.
       */
      const stickyChildren = this._stickyChildren
      const len = stickyChildren && stickyChildren.length || 0
      if (len <= 0) { return }

      const origSticky = weex.utils.supportSticky()
      // current only support list and vertical scroller.
      if (this.scrollDirection === 'horizontal') {
        return
      }

      const container = this.$el
      if (!container) { return }
      const scrollTop = container.scrollTop||document.documentElement.scrollTop||document.body.scrollTop;

      let stickyChild
      for (let i = 0; i < len; i++) {
        stickyChild = stickyChildren[i]
        if (origSticky) {
          this.addSticky(stickyChild, origSticky)
        }
        else if (stickyChild._initOffsetTop < scrollTop) {
          this.addSticky(stickyChild)
        }
        else {
          this.removeSticky(stickyChild)
        }
      }
    },

    addSticky (el, supportSticky) {
      if (supportSticky) {
        el.classList.add('weex-ios-sticky')
      }
      else {
        if (el._sticky === true) return
        if (process.env.NODE_ENV !== 'production') {
          console.log(`[vue-render] header add sticky`, el)
        }
        el._sticky = true
        if (!el._placeholder) {
          const placeholder = el.cloneNode(true)
          placeholder._origNode = el
          placeholder.classList.add('weex-sticky-placeholder')
          el._placeholder = placeholder
        }
        el.parentNode.insertBefore(el._placeholder, el)
        el.style.width = window.getComputedStyle(el).width
        el.classList.add('weex-sticky')
      }
    },

    removeSticky (el) {
      if (
        typeof el._sticky === 'undefined'
        || el._sticky === false
      ) {
        return
      }
      if (process.env.NODE_ENV !== 'production') {
        console.log(`[vue-render] header remove sticky`, el)
      }
      el._sticky = false
      el.parentNode.removeChild(el._placeholder)
      el.classList.remove('weex-sticky')
    },

    reloadStickyChildren () {
      const container = this.$el
      if (!container) return
      const stickyChildren = []
      const children = container.querySelectorAll('[sticky]')
      for (let i = 0, l = children.length; i < l; i++) {
        const child = children[i]
        if (/weex-sticky-placeholder/.test(child.className)) {  // is a placeholder.
          const origNode = child._origNode
          if (
            !origNode
            || !origNode.parentNode
            || origNode.parentNode !== child.parentNode
          ) {
            child.parentNode.removeChild(child)
          }
        }
        else {  // is a sticky node.
          stickyChildren.push(child)
          if (!child._sticky) {
            child._initOffsetTop = child.offsetTop
          }
        }
      }
      this._stickyChildren = stickyChildren
    },

    handleScroll (event) {
      weex.utils.getThrottleLazyload(25, this.$el, 'scroll')()
      getThrottledScroll(this)(event)

      this.processSticky()

      // fire loadmore event.
      const inner = this.$refs.inner
      if (inner) {
        const innerLength = this.scrollDirection === 'horizontal'
          ? this._innerWidth
          : this._innerHeight
        if (!this._innerLength) {
          this._innerLength = innerLength
        }
        if (this._innerLength !== innerLength) {
          this._innerLength = innerLength
          this._loadmoreReset = true
        }
        if (this._loadmoreReset && this.reachBottom(this.loadmoreoffset)) {
          this._loadmoreReset = false
          const el = this.$el
          if (el) {
            weex.utils.dispatchNativeEvent(el, 'loadmore')
          }
        }
      }
    },

    reachTop () {
      const wrapper = this.$refs.wrapper
      return (!!wrapper) && (wrapper.scrollTop <= 0)
    },

    reachBottom (offset) {
      const wrapper = this.$refs.wrapper
      const inner = this.$refs.inner
      offset = parseInt(offset || 0) * weex.config.env.pxBase

      if (wrapper && inner) {
        const key = this.scrollDirection === 'horizontal'
          ? 'width'
          : 'height'
        const innerLength = this[`_inner${key[0].toUpperCase()}${key.substr(1)}`]
        let wrapperLength = this[`_wrapper${key[0].toUpperCase()}${key.substr(1)}`]
        let scrollOffset = this.scrollDirection === 'horizontal'
          ? wrapper.scrollLeft
          : wrapper.scrollTop;

        if(wrapperLength===innerLength){
          // 说明滚动区域为window
          wrapperLength = document.documentElement[`client${(key[0].toUpperCase()) + (key.substr(1))}`]
          scrollOffset = this.scrollDirection === 'horizontal'
              ? document.documentElement.scrollLeft || document.body.scrollLeft
              : document.documentElement.scrollTop || document.body.scrollTop;
        }
        //172747 loading组件 当容器与内部内容高度相等时，由于JS计算的误差导致scrollOffset-(innerLength-wrapperLength-offset) 不等于0
        return scrollOffset + 2 >= innerLength - wrapperLength - offset
      }
      return false
    },

    handleTouchStart (event) {
      if (this._loading || this._refresh) {
        const touch = event.changedTouches[0]
        this._touchParams = {
          reachTop: this.reachTop(),
          reachBottom: this.reachBottom(),
          startTouchEvent: touch,
          startX: touch.pageX,
          startY: touch.pageY,
          timeStamp: event.timeStamp
        }
      }
    },

    handleTouchMove (event) {
      if (!this._touchParams || !this._refresh && !this._loading) {
        return
      }
      const inner = this.$refs.inner
      const { startY, reachTop, reachBottom } = this._touchParams
      if (inner) {
        const touch = event.changedTouches[0]
        const offsetY = touch.pageY - startY
        const dir = offsetY > 0 ? 'down' : 'up'
        this._touchParams.offsetY = offsetY
        // 在event.cancelable为false时表明滑动事件完全是系统在处理，不做滑动处理
        // 为true时才接管滑动事件，处理refresh，屏蔽微信的下拉功能
        if (event.cancelable) {
          if (this._innerHeight > this._wrapperHeight && ((reachTop && offsetY > 0) || (reachBottom && offsetY < 0))) {
            event.preventDefault && event.preventDefault()
          }
          if (this._refresh && (dir === 'down') && reachTop) {
            this._refresh.pullingDown(offsetY)
          }
          else if (this._loading && (dir === 'up') && reachBottom) {
            this._loading.pullingUp(-offsetY)
            // 解决滚动到最底部时，外层wrapper不继续滚动问题
            this.$refs.wrapper.scrollTop = this.$refs.wrapper.scrollTop - offsetY
          }
        }
      }
    },

    handleTouchEnd (event) {
      if (!this._touchParams || !this._refresh && !this._loading) {
        return
      }
      const inner = this.$refs.inner
      const { startY, reachTop, reachBottom } = this._touchParams
      if (inner) {
        const touch = event.changedTouches[0]
        const offsetY = touch.pageY - startY
        const dir = offsetY > 0 ? 'down' : 'up'
        this._touchParams.offsetY = offsetY
        if (this._refresh && (dir === 'down') && reachTop) {
          this._refresh.pullingEnd()
        }
        else if (this._loading && (dir === 'up') && reachBottom) {
          this._loading.pullingEnd()
        }
      }
      delete this._touchParams
    }
  }
}
