text: import clipBuffer from "./buffer";
  import clipPolygon from "./polygon";
  import {epsilon, halfPi} from "../math";
  import polygonContains from "../polygonContains";
  import {merge} from "d3-array";
  
  export default function(pointVisible, clipLine, interpolate, start) {
    return function(rotate, sink) {
      var line = clipLine(sink),
          rotatedStart = rotate.invert(start[0], start[1]),
          ringBuffer = clipBuffer(),
          ringSink = clipLine(ringBuffer),
          polygonStarted = false,
          polygon,
          segments,
          ring;
  
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          clip.point = pointRing;
          clip.lineStart = ringStart;
          clip.lineEnd = ringEnd;
          segments = [];
          polygon = [];
        },
        polygonEnd: function() {
          clip.point = point;
          clip.lineStart = lineStart;
          clip.lineEnd = lineEnd;
          segments = merge(segments);
          var startInside = polygonContains(polygon, rotatedStart);
          if (segments.length) {
            if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
            clipPolygon(segments, compareIntersection, startInside, interpolate, sink);
          } else if (startInside) {
            if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
            sink.lineStart();
            interpolate(null, null, 1, sink);
            sink.lineEnd();
          }
          if (polygonStarted) sink.polygonEnd(), polygonStarted = false;
          segments = polygon = null;
        },
        sphere: function() {
          sink.polygonStart();
          sink.lineStart();
          interpolate(null, null, 1, sink);
          sink.lineEnd();
          sink.polygonEnd();
        }
      };
  
      function point(lambda, phi) {
        var point = rotate(lambda, phi);
        if (pointVisible(lambda = point[0], phi = point[1])) sink.point(lambda, phi);
      }
  
      function pointLine(lambda, phi) {
        var point = rotate(lambda, phi);
        line.point(point[0], point[1]);
      }
  
      function lineStart() {
        clip.point = pointLine;
        line.lineStart();
      }
  
      function lineEnd() {
        clip.point = point;
        line.lineEnd();
      }
  
      function pointRing(lambda, phi) {
        ring.push([lambda, phi]);
        var point = rotate(lambda, phi);
        ringSink.point(point[0], point[1]);
      }
  
      function ringStart() {
        ringSink.lineStart();
        ring = [];
      }
  
      function ringEnd() {
        pointRing(ring[0][0], ring[0][1]);
        ringSink.lineEnd();
  
        var clean = ringSink.clean(),
            ringSegments = ringBuffer.result(),
            i, n = ringSegments.length, m,
            segment,
            point;
  
        ring.pop();
        polygon.push(ring);
        ring = null;
  
        if (!n) return;
  
        // No intersections.
        if (clean & 1) {
          segment = ringSegments[0];
          if ((m = segment.length - 1) > 0) {
            if (!polygonStarted) sink.polygonStart(), polygonStarted = true;
            sink.lineStart();
            for (i = 0; i < m; ++i) sink.point((point = segment[i])[0], point[1]);
            sink.lineEnd();
          }
          return;
        }
  
        // Rejoin connected segments.
        // TODO reuse ringBuffer.rejoin()?
        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
  
        segments.push(ringSegments.filter(validSegment));
      }
  
      return clip;
    };
  }
  
  function validSegment(segment) {
    return segment.length > 1;
  }
  
  // Intersections are sorted along the clip edge. For both antimeridian cutting
  // and circle clipping, the same comparison is used.
  function compareIntersection(a, b) {
    return ((a = a.x)[0] < 0 ? a[1] - halfPi - epsilon : halfPi - a[1])
         - ((b = b.x)[0] < 0 ? b[1] - halfPi - epsilon : halfPi - b[1]);
  }
  