var SUPPORT_TOUCH = "ontouchstart" in window;
var touchEvents = [
  ["touchstart", "start"],
  ["touchmove", "move"],
  ["touchend", "end"],
  ["touchcancel", "cancel"]
];
var mouseEvents = [
  ["mousedown", "start"],
  ["mouseup", "end"],
  ["mousemove", "move"]
];

var events = SUPPORT_TOUCH ? touchEvents : mouseEvents;

function forEach(obj, callback) {
  for (key in obj) {
    if (obj.hasOwnProperty(key)) callback(obj[key], key, obj);
  }
}

/* 
  options{
    leading：false 表示禁用第一次执行
    trailing: false 表示禁用停止触发的回调
  }
*/

function throttle(func, wait, options) {
  var timeout, context, args, result;
  var previous = 0;
  if (!options) options = {};

  var later = function() {
      previous = options.leading === false ? 0 : new Date().getTime();
      timeout = null;
      func.apply(context, args);    
      if (!timeout) context = args = null;
  };

  var throttled = function() {
      var now = new Date().getTime();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      // 如果没有剩余的时间了或者你改了系统时间     
      if (remaining <= 0 || remaining > wait) {
          if (timeout) {
              clearTimeout(timeout);
              timeout = null;
          }
          previous = now;
          func.apply(context, args);      
          if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
          timeout = setTimeout(later, remaining);
      }
  };
  return throttled;
}

/* 
  @params options : { 
    string   name
    function start ,
    function move, 
    function end, 
    function cancel
  }
*/
function Gesture(options) {
  var _this = this;
  forEach(options, function(v, k) {
    _this[k] = v;
  });
  if (this.init) this.init();
  events.forEach(function(ev) {
    document.addEventListener(
      [ev[0]],
      function(e) {
        if (_this[ev[1]]) _this[ev[1]](e);
      },
      false
    );
  });
}

Gesture.prototype = {
  pos: function(e, identifier) {
    var target = this.target(e, identifier);
    if (target) {
      return {
        x: target.clientX,
        y: target.clientY,
        identifier: identifier
      };
    } else {
      return false;
    }
  },
  target: function(e, identifier) {
    var target;
    if (SUPPORT_TOUCH) {
      if (typeof identifier === "number") {
        e.changedTouches.forEach(function(touch) {
          if (touch.identifier === identifier) target = touch;
        });
      } else {
        target = e.changedTouches[0];
      }
    } else {
      target = e;
    }
    return target;
  },
  dispatch: function(e, name, detail) {
    e.target.dispatchEvent(
      new CustomEvent(name, {
        detail: detail,
        bubbles: true,
        cancelable: true
      })
    );
  }
};

var tap = new Gesture({
  name: "tap",
  init: function() {
    this.moved = false;
    this.startX = 0;
    this.startY = 0;
    this.number = 0;
    this.identifier = undefined;
  },
  start: function(e) {
    if (SUPPORT_TOUCH && e.touches.length > 1) return;
    this.init();
    var pos = this.pos(e);
    this.startX = pos.x;
    this.startY = pos.y;
    this.identifier = pos.identifier;
  },
  move: function(e) {
    var pos = this.pos(e, this.identifier);
    if (
      pos &&
      (Math.abs(pos.x - this.startX) > 10 || Math.abs(pos.y - this.startY) > 10)
    ) {
      this.moved = true;
    }
  },
  end: function(e) {
    if (this.target(e, this.identifier) && !this.moved) {
      this.dispatch(e, this.name, this.pos(e));
    }
  },
  cancel: function(e) {
    this.end(e)
  }
});

var pan = new Gesture({
  name: "pan",
  init: function() {
    this.isStart = false;
    this.startX = 0;
    this.startY = 0;
    this.prevX = 0;
    this.prevY = 0;
    this.prevT = 0;
    this.identifier = undefined;
    this.history = [];
    this.directionX = "none";
    this.directionY = "none";
  },
  start: function(e) {
    if (SUPPORT_TOUCH && e.touches.length > 1) return;
    this.init();
    var pos = this.pos(e);
    this.startX = this.prevX = pos.x;
    this.startY = this.prevY = pos.y;
    this.prevY = e.timeStamp;
    this.identifier = pos.identifier;
  },
  move: function(e) {
    var pos = this.pos(e, this.identifier);
    if (pos) {
      var detail = this.detail(pos);

      // 方向相同且时间短记录，不然清空
      if (
        detail.directionX === this.directionX &&
        e.timeStamp - this.prevT < 50
      ) {
        if (this.history.length >= 10) this.history.shift();
      } else {     
        this.history = [];
      }
      this.history.push({
        x: detail.x,
        y: detail.y,
        t: e.timeStamp,
        deltaX: detail.deltaX,
        deltaY: detail.deltaY
      });

      if (!this.isStart) {
        this.isStart = true;
        this.dispatch(e, this.name + "start", detail);
      }

      this.throttleDispatch(e, this.name, detail)

      this.prevX = pos.x;
      this.prevY = pos.y;
      this.prevT = e.timeStamp;
      this.directionX = detail.directionX;
      this.directionY = detail.directionY;
    }
  },
  end: function(e) {
    var pos = this.pos(e, this.identifier);
    var detail = this.detail(pos);
    detail.fast =
      this.history.length > 3 &&
      e.timeStamp - this.prevT < 50 && 
      Math.abs(
        (this.history[this.history.length - 1].x - this.history[0].x) /
          (this.history[this.history.length - 1].t - this.history[0].t)
      ) > 1;

    if (pos && this.isStart) this.dispatch(e, this.name + "end", detail);
  },
  cancel: function(e) {
    this.end(e);
  },
  detail: function(pos) {
    var deltaX = pos.x - this.startX,
      deltaY = pos.y - this.startY;
    if (this.prevX < pos.x) {
      var directionX = "right";
    } else if (this.prevX > pos.x) {
      var directionX = "left";
    } else {
      directionX = this.directionX;
    }
    if (this.prevY < pos.y) {
      var directionY = "bottom";
    } else if (this.prevY > pos.y) {
      var directionY = "top";
    } else {
      directionY = this.directionY;
    }
    return {
      x: pos.x,
      y: pos.y,
      deltaX: deltaX,
      deltaY: deltaY,
      directionY: directionY,
      directionX: directionX
    };
  },
  throttleDispatch: throttle(Gesture.prototype.dispatch,20,{ trailing: false })
});
