import “../core/document”; import “../core/rebind”; import “../event/drag”; import “../event/event”; import “../event/mouse”; import “../event/touches”; import “../selection/selection”; import “behavior”;

d3.behavior.zoom = function() {

var translate = [0, 0],
    translate0, // translate when we started zooming (to avoid drift)
    scale = 1,
    distance0, // distance² between initial touches
    scale0, // scale when we started touching
    scaleExtent = d3_behavior_zoomInfinity,
    mousedown = "mousedown.zoom",
    mousemove = "mousemove.zoom",
    mouseup = "mouseup.zoom",
    event = d3_eventDispatch(zoom, "zoom"),
    x0,
    x1,
    y0,
    y1,
    touchtime; // time of last touchstart (to detect double-tap)

function zoom() {
  this.on(mousedown, mousedowned)
      .on(d3_behavior_zoomWheel + ".zoom", mousewheeled)
      .on(mousemove, mousewheelreset)
      .on("dblclick.zoom", dblclicked)
      .on("touchstart.zoom", touchstarted);
}

zoom.translate = function(x) {
  if (!arguments.length) return translate;
  translate = x.map(Number);
  rescale();
  return zoom;
};

zoom.scale = function(x) {
  if (!arguments.length) return scale;
  scale = +x;
  rescale();
  return zoom;
};

zoom.scaleExtent = function(x) {
  if (!arguments.length) return scaleExtent;
  scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
  return zoom;
};

zoom.x = function(z) {
  if (!arguments.length) return x1;
  x1 = z;
  x0 = z.copy();
  translate = [0, 0];
  scale = 1;
  return zoom;
};

zoom.y = function(z) {
  if (!arguments.length) return y1;
  y1 = z;
  y0 = z.copy();
  translate = [0, 0];
  scale = 1;
  return zoom;
};

function location(p) {
  return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
}

function point(l) {
  return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
}

function scaleTo(s) {
  scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
}

function translateTo(p, l) {
  l = point(l);
  translate[0] += p[0] - l[0];
  translate[1] += p[1] - l[1];
}

function rescale() {
  if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert));
  if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert));
}

function dispatch(event) {
  rescale();
  event({type: "zoom", scale: scale, translate: translate});
}

function mousedowned() {
  var target = this,
      event_ = event.of(target, arguments),
      eventTarget = d3.event.target,
      dragged = 0,
      w = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended),
      l = location(d3.mouse(target)),
      dragRestore = d3_event_dragSuppress();

  function moved() {
    dragged = 1;
    translateTo(d3.mouse(target), l);
    dispatch(event_);
  }

  function ended() {
    w.on(mousemove, d3_window === target ? mousewheelreset : null).on(mouseup, null);
    dragRestore(dragged && d3.event.target === eventTarget);
  }
}

function touchstarted() {
  var target = this,
      event_ = event.of(target, arguments),
      touches = d3.touches(target),
      now = Date.now(),
      name = "zoom-" + d3.event.changedTouches[0].identifier,
      touchmove = "touchmove." + name,
      touchend = "touchend." + name,
      w = d3.select(d3_window).on(touchmove, moved).on(touchend, ended).on(mousedown, null).on(mousemove, null), // prevent duplicate events
      dragRestore = d3_event_dragSuppress();

  scale0 = scale;
  translate0 = {};
  distance0 = 0;
  touches.forEach(function(t) { translate0[t.identifier] = location(t); });

  if (touches.length === 1) {
    if (now - touchtime < 500) { // dbltap
      var p = touches[0], l = location(touches[0]);
      scaleTo(scale * 2);
      translateTo(p, l);
      d3_eventPreventDefault();
      dispatch(event_);
    }
    touchtime = now;
  } else if (touches.length > 1) {
    var p = touches[0], q = touches[1],
        dx = p[0] - q[0], dy = p[1] - q[1];
    distance0 = dx * dx + dy * dy;
  }

  function moved() {
    var touches = d3.touches(target),
        p0 = touches[0],
        l0 = translate0[p0.identifier];

    if (p1 = touches[1]) {
      var p1, l1 = translate0[p1.identifier],
          scale1 = d3.event.scale;
      if (scale1 == null) {
        var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1;
        scale1 = distance0 && Math.sqrt(distance1 / distance0);
      }
      p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
      l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
      scaleTo(scale1 * scale0);
    }

    touchtime = null;
    translateTo(p0, l0);
    dispatch(event_);
  }

  function ended() {
    w.on(touchmove, null).on(touchend, null);
    dragRestore();
  }
}

function mousewheeled() {
  d3_eventPreventDefault();
  if (!translate0) translate0 = location(d3.mouse(this));
  scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
  translateTo(d3.mouse(this), translate0);
  dispatch(event.of(this, arguments));
}

function mousewheelreset() {
  translate0 = null;
}

function dblclicked() {
  var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2;
  scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
  translateTo(p, l);
  dispatch(event.of(this, arguments));
}

return d3.rebind(zoom, event, "on");

};

var d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent

// developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel var d3_behavior_zoomDelta, d3_behavior_zoomWheel

= "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); }, "wheel")
: "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { return d3.event.wheelDelta; }, "mousewheel")
: (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll");