var vows = require("vows"),
    _ = require("../../"),
    load = require("../load"),
    assert = require("../assert");

var suite = vows.describe("d3.geo.centroid");

suite.addBatch({
  "centroid": {
    topic: load("geo/centroid").expression("d3.geo.centroid"),

    "the centroid of a point is itself": function(centroid) {
      assert.inDelta(centroid({type: "Point", coordinates: [0, 0]}), [0, 0], 1e-6);
      assert.inDelta(centroid({type: "Point", coordinates: [1, 1]}), [1, 1], 1e-6);
      assert.inDelta(centroid({type: "Point", coordinates: [2, 3]}), [2, 3], 1e-6);
      assert.inDelta(centroid({type: "Point", coordinates: [-4, -5]}), [-4, -5], 1e-6);
    },

    "the centroid of a set of points is the (spherical) average of its constituent members": function(centroid) {
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Point", coordinates: [1, 2]}]}), [0.499847, 1.000038], 1e-6);
      assert.inDelta(centroid({type: "MultiPoint", coordinates: [[0, 0], [1, 2]]}), [0.499847, 1.000038], 1e-6);
      assert.inDelta(centroid({type: "MultiPoint", coordinates: [[179, 0], [-179, 0]]}), [180, 0], 1e-6);
    },

    "the centroid of a set of points and their antipodes is ambiguous": function(centroid) {
      assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [180, 0]]}).every(isNaN));
      assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [90, 0], [180, 0], [-90, 0]]}).every(isNaN));
      assert.ok(centroid({type: "MultiPoint", coordinates: [[0, 0], [0, 90], [180, 0], [0, -90]]}).every(isNaN));
    },

    "the centroid of the empty set of points is ambiguous": function(centroid) {
      assert.ok(centroid({type: "MultiPoint", coordinates: []}).every(isNaN));
    },

    "the centroid of a line string is the (spherical) average of its constituent great arc segments": function(centroid) {
      assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [1, 0]]}), [0.5, 0], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 90]]}), [0, 45], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[0, 0], [0, 45], [0, 90]]}), [0, 45], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[-1, -1], [1, 1]]}), [0, 0], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[-60, -1], [60, 1]]}), [0, 0], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[179, -1], [-179, 1]]}), [180, 0], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[-179, 0], [0, 0], [179, 0]]}), [0, 0], 1e-6);
      assert.inDelta(centroid({type: "LineString", coordinates: [[-180, -90], [0, 0], [0, 90]]}), [0, 0], 1e-6);
    },

    "the centroid of a great arc from a point to its antipode is ambiguous": function(centroid) {
      assert.ok(centroid({type: "LineString", coordinates: [[180, 0], [0, 0]]}).every(isNaN));
      assert.ok(centroid({type: "MultiLineString", coordinates: [[[0, -90], [0, 90]]]}).every(isNaN));
    },

    "the centroid of a set of line strings is the (spherical) average of its constituent great arc segments": function(centroid) {
      assert.inDelta(centroid({type: "MultiLineString", coordinates: [[[0, 0], [0, 2]]]}), [0, 1], 1e-6);
    },

    "a line of zero length is treated as points": function(centroid) {
      assert.inDelta(centroid({type: "LineString", coordinates: [[1, 1], [1, 1]]}), [1, 1], 1e-6);
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "LineString", coordinates: [[1, 2], [1, 2]]}]}), [0.666534, 1.333408], 1e-6);
    },

    "an empty polygon with non-zero extent is treated as a line": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[1, 1], [2, 1], [3, 1], [2, 1], [1, 1]]]}), [2, 1.000076], 1e-6);
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Polygon", coordinates: [[[1, 2], [1, 2], [1, 2], [1, 2]]]}]}), [0.799907, 1.600077], 1e-6);
    },

    "an empty polygon with zero extent is treated as a point": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[1, 1], [1, 1], [1, 1], [1, 1]]]}), [1, 1], 1e-6);
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [{type: "Point", coordinates: [0, 0]}, {type: "Polygon", coordinates: [[[1, 2], [1, 2], [1, 2], [1, 2]]]}]}), [0.799907, 1.600077], 1e-6);
    },

    "the centroid of the equator is ambiguous": function(centroid) {
      assert.ok(centroid({type: "LineString", coordinates: [[0, 0], [120, 0], [-120, 0], [0, 0]]}).every(isNaN));
    },

    "the centroid of a polygon is the (spherical) average of its surface": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}), [0.5, 0], 1e-6);
      assert.inDelta(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90, 1e-6);
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
    },

    "the centroid of a set of polygons is the (spherical) average of its surface": function(centroid) {
      var circle = _.geo.circle();
      assert.inDelta(centroid({
        type: "MultiPolygon",
        coordinates: [
          circle.angle(45).origin([0, 0])().coordinates,
          circle.angle(60).origin([180, 0])().coordinates
        ]
      }), [180, 0], 1e-6);
    },

    "the centroid of a lune is the (spherical) average of its surface": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}), [0.5, 0], 1e-6);
    },

    "the centroid of a small circle is its origin": {
      "5°": function(centroid) {
        assert.inDelta(centroid(_.geo.circle().angle(5).origin([30, 45])()), [30, 45], 1e-6);
      },
      "135°": function(centroid) {
        assert.inDelta(centroid(_.geo.circle().angle(135).origin([30, 45])()), [30, 45], 1e-6);
      },
      "South Pole": function(centroid) {
        assert.equal(centroid({type: "Polygon", coordinates: [_.range(-180, 180 + 1 / 2, 1).map(function(x) { return [x, -60]; })]})[1], -90);
      },
      "equator": function(centroid) {
        assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
      },
      "equator with coincident points": function(centroid) {
        assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
      },
      "other": function(centroid) {
        assert.inDelta(centroid({type: "Polygon", coordinates: [[[-180, 0], [-180, 10], [-179, 10], [-179, 0], [-180, 0]]]}), [-179.5, 4.987448], 1e-6);
      },
      "concentric rings": function(centroid) {
        var circle = _.geo.circle().origin([0, 45]),
            coordinates = circle.angle(60)().coordinates;
        coordinates.push(circle.angle(45)().coordinates[0].reverse());
        assert.inDelta(centroid({type: "Polygon", coordinates: coordinates}), [0, 45], 1e-6);
      }
    },

    "the centroid of a spherical square on the equator": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[0, -10], [0, 10], [10, 10], [10, -10], [0, -10]]]}), [5, 0], 1e-6);
    },

    "the centroid of a spherical square touching the antimeridian": function(centroid) {
      assert.inDelta(centroid({type: "Polygon", coordinates: [[[-180, 0], [-180, 10], [-179, 10], [-179, 0], [-180, 0]]]}), [-179.5, 4.987448], 1e-6);
    },

    "concentric rings": function(centroid) {
      var circle = _.geo.circle().origin([0, 45]),
          coordinates = circle.angle(60)().coordinates;
      coordinates.push(circle.angle(45)().coordinates[0].reverse());
      assert.inDelta(centroid({type: "Polygon", coordinates: coordinates}), [0, 45], 1e-6);
    },

    "the centroid of a sphere is ambiguous": function(centroid) {
      assert.ok(centroid({type: "Sphere"}).every(isNaN));
    },

    "the centroid of a feature is the centroid of its constituent geometry": function(centroid) {
      assert.inDelta(centroid({type: "Feature", geometry: {type: "LineString", coordinates: [[1, 1], [1, 1]]}}), [1, 1], 1e-6);
      assert.inDelta(centroid({type: "Feature", geometry: {type: "Point", coordinates: [1, 1]}}), [1, 1], 1e-6);
      assert.inDelta(centroid({type: "Feature", geometry: {type: "Polygon", coordinates: [[[0, -90], [0, 0], [0, 90], [1, 0], [0, -90]]]}}), [0.5, 0], 1e-6);
    },

    "the centroid of a feature collection is the centroid of its constituent geometry": function(centroid) {
      assert.inDelta(centroid({type: "FeatureCollection", features: [
        {type: "Feature", geometry: {type: "LineString", coordinates: [[179, 0], [180, 0]]}},
        {type: "Feature", geometry: {type: "Point", coordinates: [0, 0]}}
      ]}), [179.5, 0], 1e-6);
    },

    "the centroid of a non-empty line string and a point only considers the line string": function(centroid) {
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [
        {type: "LineString", coordinates: [[179, 0], [180, 0]]},
        {type: "Point", coordinates: [0, 0]}
      ]}), [179.5, 0], 1e-6);
    },

    "the centroid of a non-empty polygon, a non-empty line string and a point only considers the polygon": function(centroid) {
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [
        {type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]},
        {type: "LineString", coordinates: [[179, 0], [180, 0]]},
        {type: "Point", coordinates: [0, 0]}
      ]}), [-179.5, 0.500006], 1e-6);
      assert.inDelta(centroid({type: "GeometryCollection", geometries: [
        {type: "Point", coordinates: [0, 0]},
        {type: "LineString", coordinates: [[179, 0], [180, 0]]},
        {type: "Polygon", coordinates: [[[-180, 0], [-180, 1], [-179, 1], [-179, 0], [-180, 0]]]}
      ]}), [-179.5, 0.500006], 1e-6);
    },

    "the centroid of the sphere and a point is the point": function(centroid) {
      assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
        {type: "Sphere"},
        {type: "Point", coordinates: [0, 0]}
      ]}), [0, 0]);
      assert.deepEqual(centroid({type: "GeometryCollection", geometries: [
        {type: "Point", coordinates: [0, 0]},
        {type: "Sphere"}
      ]}), [0, 0]);
    }
  }
});

suite.export(module);
