text: import {
      polygonHull as d3_polygonHull,
      polygonCentroid as d3_polygonCentroid
  } from 'd3-polygon';
  
  import {
      geoExtent,
      geoRotate,
      geoVecInterp,
      geoVecLength
  } from '../geo';
  
  import { utilGetAllNodes } from '../util';
  
  
  /* Reflect the given area around its axis of symmetry */
  export function actionReflect(reflectIds, projection) {
      var useLongAxis = true;
  
  
      // http://gis.stackexchange.com/questions/22895/finding-minimum-area-rectangle-for-given-points
      // http://gis.stackexchange.com/questions/3739/generalisation-strategies-for-building-outlines/3756#3756
      function getSmallestSurroundingRectangle(graph, nodes) {
          var points = nodes.map(function(n) { return projection(n.loc); }),
              hull = d3_polygonHull(points),
              centroid = d3_polygonCentroid(hull),
              minArea = Infinity,
              ssrExtent = [],
              ssrAngle = 0,
              c1 = hull[0];
  
          for (var i = 0; i <= hull.length - 1; i++) {
              var c2 = (i === hull.length - 1) ? hull[0] : hull[i + 1],
                  angle = Math.atan2(c2[1] - c1[1], c2[0] - c1[0]),
                  poly = geoRotate(hull, -angle, centroid),
                  extent = poly.reduce(function(extent, point) {
                          return extent.extend(geoExtent(point));
                      }, geoExtent()),
                  area = extent.area();
  
              if (area < minArea) {
                  minArea = area;
                  ssrExtent = extent;
                  ssrAngle = angle;
              }
              c1 = c2;
          }
  
          return {
              poly: geoRotate(ssrExtent.polygon(), ssrAngle, centroid),
              angle: ssrAngle
          };
      }
  
  
      var action = function(graph, t) {
          if (t === null || !isFinite(t)) t = 1;
          t = Math.min(Math.max(+t, 0), 1);
  
          var nodes = utilGetAllNodes(reflectIds, graph),
              ssr = getSmallestSurroundingRectangle(graph, nodes);
  
          // Choose line pq = axis of symmetry.
          // The shape's surrounding rectangle has 2 axes of symmetry.
          // Reflect across the longer axis by default.
          var p1 = [(ssr.poly[0][0] + ssr.poly[1][0]) / 2, (ssr.poly[0][1] + ssr.poly[1][1]) / 2 ],
              q1 = [(ssr.poly[2][0] + ssr.poly[3][0]) / 2, (ssr.poly[2][1] + ssr.poly[3][1]) / 2 ],
              p2 = [(ssr.poly[3][0] + ssr.poly[4][0]) / 2, (ssr.poly[3][1] + ssr.poly[4][1]) / 2 ],
              q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2 ],
              p, q;
  
          var isLong = (geoVecLength(p1, q1) > geoVecLength(p2, q2));
          if ((useLongAxis && isLong) || (!useLongAxis && !isLong)) {
              p = p1;
              q = q1;
          } else {
              p = p2;
              q = q2;
          }
  
          // reflect c across pq
          // http://math.stackexchange.com/questions/65503/point-reflection-over-a-line
          var dx = q[0] - p[0];
          var dy = q[1] - p[1];
          var a = (dx * dx - dy * dy) / (dx * dx + dy * dy);
          var b = 2 * dx * dy / (dx * dx + dy * dy);
          for (var i = 0; i < nodes.length; i++) {
              var node = nodes[i];
              var c = projection(node.loc);
              var c2 = [
                  a * (c[0] - p[0]) + b * (c[1] - p[1]) + p[0],
                  b * (c[0] - p[0]) - a * (c[1] - p[1]) + p[1]
              ];
              var loc2 = projection.invert(c2);
              node = node.move(geoVecInterp(node.loc, loc2, t));
              graph = graph.replace(node);
          }
  
          return graph;
      };
  
  
      action.useLongAxis = function(_) {
          if (!arguments.length) return useLongAxis;
          useLongAxis = _;
          return action;
      };
  
  
      action.transitionable = true;
  
  
      return action;
  }
  