var ua = navigator.userAgent.toLowerCase()
//http://stackoverflow.com/questions/9038625/detect-if-device-is-ios
function iOSversion() {
    //https://developer.apple.com/library/prerelease/mac/releasenotes/General/WhatsNewInSafari/Articles/Safari_9.html
    //http://mp.weixin.qq.com/s?__biz=MzA3MDQ4MzQzMg==&mid=256900619&idx=1&sn=b29f84cff0b8d7b9742e5d8b3cd8f218&scene=1&srcid=1009F9l4gh9nZ7rcQJEhmf7Q#rd
    if (/iPad|iPhone|iPod/i.test(ua) && !window.MSStream) {
        if ("backdropFilter" in document.documentElement.style) {
            return 9
        }
        if (!!window.indexedDB) {
            return 8
        }
        if (!!window.SpeechSynthesisUtterance) {
            return 7
        }
        if (!!window.webkitAudioContext) {
            return 6
        }
        if (!!window.matchMedia) {
            return 5
        }
        if (!!window.history && 'pushState' in window.history) {
            return 4
        }
        return 3
    }
    return NaN
}

var deviceIsAndroid = ua.indexOf('android') > 0
var deviceIsIOS = iOSversion()

var Recognizer = yua.gestureHooks = {
    pointers: {},
    //以AOP切入touchstart, touchmove, touchend, touchcancel回调
    start: function (event, callback) {
      
        //touches是当前屏幕上所有触摸点的列表;
        //targetTouches是当前对象上所有触摸点的列表;
        //changedTouches是涉及当前事件的触摸点的列表。
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i],
                id = touch.identifier,
                pointer = {
                    startTouch: mixLocations({}, touch),
                    startTime: Date.now(),
                    status: 'tapping',
                    element: event.target,
                    pressingHandler: Recognizer.pointers[id] && Recognizer.pointers[id].pressingHandler
                }
            Recognizer.pointers[id] = pointer;
            callback(pointer, touch)

        }
    },
    move: function (event, callback) {
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i]
            var pointer = Recognizer.pointers[touch.identifier]
            if (!pointer) {
                return
            }

            if (!("lastTouch" in pointer)) {
                pointer.lastTouch = pointer.startTouch
                pointer.lastTime = pointer.startTime
                pointer.deltaX = pointer.deltaY = pointer.duration =  pointer.distance = 0
            }
           
            var time = Date.now() - pointer.lastTime

            if (time > 0) {

                var RECORD_DURATION = 70
                if (time > RECORD_DURATION) {
                    time = RECORD_DURATION
                }
                if (pointer.duration + time > RECORD_DURATION) {
                    pointer.duration = RECORD_DURATION - time
                }

                pointer.duration += time;
                pointer.lastTouch = mixLocations({}, touch)

                pointer.lastTime = Date.now()

                pointer.deltaX = touch.clientX - pointer.startTouch.clientX
                pointer.deltaY = touch.clientY - pointer.startTouch.clientY
                var x = pointer.deltaX * pointer.deltaX
                var y = pointer.deltaY * pointer.deltaY
                pointer.distance = Math.sqrt(x + y)
                pointer.isVertical = x < y

                callback(pointer, touch)
            }
        }
    },
    end: function (event, callback) {
        for (var i = 0; i < event.changedTouches.length; i++) {
            var touch = event.changedTouches[i],
                    id = touch.identifier,
                    pointer = Recognizer.pointers[id]

            if (!pointer)
                continue

            callback(pointer, touch)

            delete Recognizer.pointers[id]
        }
    },
    //人工触发合成事件
    fire: function (elem, type, props) {
        if (elem) {
            var event = document.createEvent('Events')
            event.initEvent(type, true, true)
            yua.mix(event, props)
            elem.dispatchEvent(event)
        }
    },
    //添加各种识别器
    add: function (name, recognizer) {
        function move(event) {
            recognizer.touchmove(event)
        }

        function end(event) {
            recognizer.touchend(event)

            document.removeEventListener('touchmove', move)

            document.removeEventListener('touchend', end)

            document.removeEventListener('touchcancel', cancel)

        }

        function cancel(event) {
            recognizer.touchcancel(event)

            document.removeEventListener('touchmove', move)

            document.removeEventListener('touchend', end)

            document.removeEventListener('touchcancel', cancel)

        }

        recognizer.events.forEach(function (eventName) {
            yua.eventHooks[eventName] = {
                fix: function (el, fn) {
                    if (!el['touch-' + name]) {
                        el['touch-' + name] = '1'
                        el.addEventListener('touchstart', function (event) {
                            recognizer.touchstart(event)

                            document.addEventListener('touchmove', move)

                            document.addEventListener('touchend', end)

                            document.addEventListener('touchcancel', cancel)

                        })
                    }
                    return fn
                }
            }
        })
    }
}

var locations = ['screenX', 'screenY', 'clientX', 'clientY', 'pageX', 'pageY']

// 复制 touch 对象上的有用属性到固定对象上
function mixLocations(target, source) {
    if (source) {
        locations.forEach(function (key) {
            target[key] = source[key]
        })
    }
    return target
}

var supportPointer = !!navigator.pointerEnabled || !!navigator.msPointerEnabled

if (supportPointer) { // 支持pointer的设备可用样式来取消click事件的300毫秒延迟
  root.style.msTouchAction = root.style.touchAction = 'none'
}
var tapRecognizer = {
  events: ['tap'],
  touchBoundary: 10,
  tapDelay: 200,
  needClick: function(target) {
    //判定是否使用原生的点击事件, 否则使用sendClick方法手动触发一个人工的点击事件
    switch (target.nodeName.toLowerCase()) {
      case 'button':
      case 'select':
      case 'textarea':
        if (target.disabled) {
          return true
        }

        break;
      case 'input':
        // IOS6 pad 上选择文件，如果不是原生的click，弹出的选择界面尺寸错误
        if ((deviceIsIOS && target.type === 'file') || target.disabled) {
          return true
        }

        break;
      case 'label':
      case 'iframe':
      case 'video':
        return true
    }

    return false
  },
  needFocus: function(target) {
    switch (target.nodeName.toLowerCase()) {
      case 'textarea':
      case 'select': //实测android下select也需要
        return true;
      case 'input':
        switch (target.type) {
          case 'button':
          case 'checkbox':
          case 'file':
          case 'image':
          case 'radio':
          case 'submit':
            return false
        }
        //如果是只读或disabled状态,就无须获得焦点了
        return !target.disabled && !target.readOnly
      default:
        return false
    }
  },
  focus: function(targetElement) {
    var length;
    //在iOS7下, 对一些新表单元素(如date, datetime, time, month)调用focus方法会抛错,
    //幸好的是,我们可以改用setSelectionRange获取焦点, 将光标挪到文字的最后
    var type = targetElement.type
    if (deviceIsIOS && targetElement.setSelectionRange &&
      type.indexOf('date') !== 0 && type !== 'time' && type !== 'month') {
      length = targetElement.value.length
      targetElement.setSelectionRange(length, length)
    } else {
      targetElement.focus()
    }
  },
  findControl: function(labelElement) {
    // 获取label元素所对应的表单元素
    // 可以能过control属性, getElementById, 或用querySelector直接找其内部第一表单元素实现
    if (labelElement.control !== undefined) {
      return labelElement.control
    }

    if (labelElement.htmlFor) {
      return document.getElementById(labelElement.htmlFor)
    }

    return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea')
  },
  fixTarget: function(target) {
    if (target.nodeType === 3) {
      return target.parentNode
    }
    if (window.SVGElementInstance && (target instanceof SVGElementInstance)) {
      return target.correspondingUseElement;
    }

    return target
  },
  updateScrollParent: function(targetElement) {
    //如果事件源元素位于某一个有滚动条的祖父元素中,那么保持其scrollParent与scrollTop值
    var scrollParent = targetElement.tapScrollParent

    if (!scrollParent || !scrollParent.contains(targetElement)) {
      var parentElement = targetElement
      do {
        if (parentElement.scrollHeight > parentElement.offsetHeight) {
          scrollParent = parentElement
          targetElement.tapScrollParent = parentElement
          break
        }

        parentElement = parentElement.parentElement
      } while (parentElement)
    }

    if (scrollParent) {
      scrollParent.lastScrollTop = scrollParent.scrollTop
    }
  },
  touchHasMoved: function(event) {
    //判定是否发生移动,其阀值是10px
    var touch = event.changedTouches[0],
      boundary = tapRecognizer.touchBoundary
    return Math.abs(touch.pageX - tapRecognizer.pageX) > boundary ||
      Math.abs(touch.pageY - tapRecognizer.pageY) > boundary

  },

  findType: function(targetElement) {
    // 安卓chrome浏览器上，模拟的 click 事件不能让 select 打开，故使用 mousedown 事件
    return deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select' ?
      'mousedown' : 'click'
  },
  sendClick: function(targetElement, event) {
    // 在click之前触发tap事件
    Recognizer.fire(targetElement, 'tap', {
      touchEvent: event
    })
    var clickEvent, touch
      //某些安卓设备必须先移除焦点，之后模拟的click事件才能让新元素获取焦点
    if (document.activeElement && document.activeElement !== targetElement) {
      document.activeElement.blur()
    }

    touch = event.changedTouches[0]
      // 手动触发点击事件,此时必须使用document.createEvent('MouseEvents')来创建事件
      // 及使用initMouseEvent来初始化它
    clickEvent = document.createEvent('MouseEvents')
    clickEvent.initMouseEvent(tapRecognizer.findType(targetElement), true, true, window, 1, touch.screenX,
      touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null)
    clickEvent.touchEvent = event
    targetElement.dispatchEvent(clickEvent)
  },
  touchstart: function(event) {
    //忽略多点触摸
    if (event.targetTouches.length !== 1) {
      return true
    }
    //修正事件源对象
    var targetElement = tapRecognizer.fixTarget(event.target)
    var touch = event.targetTouches[0]
    if (deviceIsIOS) {
      // 判断是否是点击文字，进行选择等操作，如果是，不需要模拟click
      var selection = window.getSelection();
      if (selection.rangeCount && !selection.isCollapsed) {
        return true
      }
      var id = touch.identifier
        //当 alert 或 confirm 时，点击其他地方，会触发touch事件，identifier相同，此事件应该被忽略
      if (id && isFinite(tapRecognizer.lastTouchIdentifier) && tapRecognizer.lastTouchIdentifier === id) {
        event.preventDefault()
        return false
      }

      tapRecognizer.lastTouchIdentifier = id

      tapRecognizer.updateScrollParent(targetElement)
    }
    //收集触摸点的信息
    tapRecognizer.status = "tapping"
    tapRecognizer.startTime = Date.now()
    tapRecognizer.element = targetElement
    tapRecognizer.pageX = touch.pageX
    tapRecognizer.pageY = touch.pageY
      // 如果点击太快,阻止双击带来的放大收缩行为
    if ((tapRecognizer.startTime - tapRecognizer.lastTime) < tapRecognizer.tapDelay) {
      event.preventDefault()
    }
  },
  touchmove: function(event) {
    if (tapRecognizer.status !== "tapping") {
      return true
    }
    // 如果事件源元素发生改变,或者发生了移动,那么就取消触发点击事件
    if (tapRecognizer.element !== tapRecognizer.fixTarget(event.target) ||
      tapRecognizer.touchHasMoved(event)) {
      tapRecognizer.status = tapRecognizer.element = 0
    }

  },
  touchend: function(event) {
    var targetElement = tapRecognizer.element
    var now = Date.now()
      //如果是touchstart与touchend相隔太久,可以认为是长按,那么就直接返回
      //或者是在touchstart, touchmove阶段,判定其不该触发点击事件,也直接返回
    if (!targetElement || now - tapRecognizer.startTime > tapRecognizer.tapDelay) {
      return true
    }


    tapRecognizer.lastTime = now

    var startTime = tapRecognizer.startTime
    tapRecognizer.status = tapRecognizer.startTime = 0

    targetTagName = targetElement.tagName.toLowerCase()
    if (targetTagName === 'label') {
      //尝试触发label上可能绑定的tap事件
      Recognizer.fire(targetElement, 'tap', {
        touchEvent: event
      })
      var forElement = tapRecognizer.findControl(targetElement)
      if (forElement) {
        tapRecognizer.focus(targetElement)
        targetElement = forElement
      }
    } else if (tapRecognizer.needFocus(targetElement)) {
      //  如果元素从touchstart到touchend经历时间过长,那么不应该触发点击事
      //  或者此元素是iframe中的input元素,那么它也无法获点焦点
      if ((now - startTime) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
        tapRecognizer.element = 0
        return false
      }

      tapRecognizer.focus(targetElement)
      deviceIsAndroid && tapRecognizer.sendClick(targetElement, event)

      return false
    }

    if (deviceIsIOS) {
      //如果它的父容器的滚动条发生改变,那么应该识别为划动或拖动事件,不应该触发点击事件
      var scrollParent = targetElement.tapScrollParent;
      if (scrollParent && scrollParent.lastScrollTop !== scrollParent.scrollTop) {
        return true
      }
    }
    //如果这不是一个需要使用原生click的元素，则屏蔽原生事件，避免触发两次click
    if (!tapRecognizer.needClick(targetElement)) {
      event.preventDefault()
        // 触发一次模拟的click
      tapRecognizer.sendClick(targetElement, event)
    }
  },
  touchcancel: function() {
    tapRecognizer.startTime = tapRecognizer.element = 0
  }
}

Recognizer.add("tap", tapRecognizer)

var pressRecognizer = {
    events: ['longtap', 'doubletap'],
    cancelPress: function (pointer) {
        clearTimeout(pointer.pressingHandler)
        pointer.pressingHandler = null
    },
    touchstart: function (event) {
        Recognizer.start(event, function (pointer, touch) {
            pointer.pressingHandler = setTimeout(function () {
                if (pointer.status === 'tapping') {
                    Recognizer.fire(event.target, 'longtap', {
                        touch: touch,
                        touchEvent: event
                    })
                }
                pressRecognizer.cancelPress(pointer)
            }, 800)
            if (event.changedTouches.length !== 1) {
                pointer.status = 0
            }
        })

    },
    touchmove: function (event) {
        Recognizer.move(event, function (pointer) {
            if (pointer.distance > 10 && pointer.pressingHandler) {
                pressRecognizer.cancelPress(pointer)
                if (pointer.status === 'tapping') {
                    pointer.status = 'panning'
                }
            }
        })
    },
    touchend: function (event) {
        Recognizer.end(event, function (pointer, touch) {
            pressRecognizer.cancelPress(pointer)
            if (pointer.status === 'tapping') {
                pointer.lastTime = Date.now()
                if (pressRecognizer.lastTap && pointer.lastTime - pressRecognizer.lastTap.lastTime < 300) {
                    Recognizer.fire(pointer.element, 'doubletap', {
                        touch: touch,
                        touchEvent: event
                    })
                }

                pressRecognizer.lastTap = pointer
            }
        })

    },
    touchcancel: function (event) {
        Recognizer.end(event, function (pointer) {
            pressRecognizer.cancelPress(pointer)
        })
    }
}
Recognizer.add('press', pressRecognizer)

var swipeRecognizer = {
    events: ['swipe', 'swipeleft', 'swiperight', 'swipeup', 'swipedown'],
    getAngle: function (x, y ) {
       return Math.atan2(y, x) * 180 / Math.PI
    },
    getDirection: function (x, y) {
        var angle = swipeRecognizer.getAngle(x, y)
        if ((angle < -45) && (angle > -135)) {
            return "up"
        } else if ((angle >= 45) && (angle < 315)) {
            return "down"
        } else if ((angle > -45) && (angle <= 45)) {
            return "right"
        } else{
            return "left"
        }
    },
    touchstart: function (event) {
        Recognizer.start(event, noop)
    },
    touchmove: function (event) {
        Recognizer.move(event, noop)
    },
    touchend: function (event) {
        if(event.changedTouches.length !== 1){
            return
        }
        Recognizer.end(event, function (pointer, touch) {
            var isflick = (pointer.distance > 30 && pointer.distance / pointer.duration > 0.65)
            if (isflick) {
                var extra = {
                    deltaX : pointer.deltaX,
                    deltaY: pointer.deltaY,
                    touch: touch,
                    touchEvent: event,
                    direction:  swipeRecognizer.getDirection(pointer.deltaX, pointer.deltaY),
                    isVertical: pointer.isVertical
                }
                var target = pointer.element
                Recognizer.fire(target, 'swipe', extra)
                Recognizer.fire(target, 'swipe' + extra.direction, extra)
            }
        })
    }
}

swipeRecognizer.touchcancel = swipeRecognizer.touchend
Recognizer.add('swipe', swipeRecognizer)