import “../arrays/merge”; import “../core/noop”; import “../math/trigonometry”; import “clip-polygon”;

function d3_geo_clip(pointVisible, clipLine, interpolate, polygonContains) {

return function(listener) {
  var line = clipLine(listener);

  var clip = {
    point: point,
    lineStart: lineStart,
    lineEnd: lineEnd,
    polygonStart: function() {
      clip.point = pointRing;
      clip.lineStart = ringStart;
      clip.lineEnd = ringEnd;
      segments = [];
      polygon = [];
      listener.polygonStart();
    },
    polygonEnd: function() {
      clip.point = point;
      clip.lineStart = lineStart;
      clip.lineEnd = lineEnd;

      segments = d3.merge(segments);
      if (segments.length) {
        d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
      } else if (polygonContains(polygon)) {
        listener.lineStart();
        interpolate(null, null, 1, listener);
        listener.lineEnd();
      }
      listener.polygonEnd();
      segments = polygon = null;
    },
    sphere: function() {
      listener.polygonStart();
      listener.lineStart();
      interpolate(null, null, 1, listener);
      listener.lineEnd();
      listener.polygonEnd();
    }
  };

  function point(λ, φ) { if (pointVisible(λ, φ)) listener.point(λ, φ); }
  function pointLine(λ, φ) { line.point(λ, φ); }
  function lineStart() { clip.point = pointLine; line.lineStart(); }
  function lineEnd() { clip.point = point; line.lineEnd(); }

  var segments;

  var buffer = d3_geo_clipBufferListener(),
      ringListener = clipLine(buffer),
      polygon,
      ring;

  function pointRing(λ, φ) {
    ringListener.point(λ, φ);
    ring.push([λ, φ]);
  }

  function ringStart() {
    ringListener.lineStart();
    ring = [];
  }

  function ringEnd() {
    pointRing(ring[0][0], ring[0][1]);
    ringListener.lineEnd();

    var clean = ringListener.clean(),
        ringSegments = buffer.buffer(),
        segment,
        n = ringSegments.length;

    ring.pop();
    polygon.push(ring);
    ring = null;

    if (!n) return;

    // No intersections.
    if (clean & 1) {
      segment = ringSegments[0];
      var n = segment.length - 1,
          i = -1,
          point;
      listener.lineStart();
      while (++i < n) listener.point((point = segment[i])[0], point[1]);
      listener.lineEnd();
      return;
    }

    // Rejoin connected segments.
    // TODO reuse bufferListener.rejoin()?
    if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));

    segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
  }

  return clip;
};

}

function d3_geo_clipSegmentLength1(segment) {

return segment.length > 1;

}

function d3_geo_clipBufferListener() {

var lines = [],
    line;
return {
  lineStart: function() { lines.push(line = []); },
  point: function(λ, φ) { line.push([λ, φ]); },
  lineEnd: d3_noop,
  buffer: function() {
    var buffer = lines;
    lines = [];
    line = null;
    return buffer;
  },
  rejoin: function() {
    if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
  }
};

}

// Intersection points are sorted along the clip edge. For both antimeridian // cutting and circle clipping, the same comparison is used. function d3_geo_clipSort(a, b) {

return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1])
     - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]);

}