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

var suite = vows.describe("d3.geom.voronoi");

suite.addBatch({
  "voronoi": {
    topic: load("geom/voronoi").expression("d3.geom.voronoi"),

    "the default voronoi layout": {
      topic: function(voronoi) {
        return voronoi();
      },
      "has no defined clip extent": function(v) {
        assert.isNull(v.clipExtent());
      },
      "has no defined size": function(v) {
        assert.isNull(v.size());
      },
      "returns the configured clip extent": function(v) {
        try {
          assert.deepEqual(v.clipExtent([[1, 2], [3, 4]]).clipExtent(), [[1, 2], [3, 4]]);
        } finally {
          v.clipExtent(null);
        }
      },
      "returns the configured size": function(v) {
        try {
          assert.deepEqual(v.size([1, 2]).size(), [1, 2]);
        } finally {
          v.size(null);
        }
      },
      "size implies a clip extent from [0, 0]": function(v) {
        try {
          assert.deepEqual(v.size([1, 2]).clipExtent(), [[0, 0], [1, 2]]);
        } finally {
          v.size(null);
        }
      },
      "clip extent implies a size, assuming [0, 0]": function(v) {
        try {
          assert.deepEqual(v.clipExtent([[1, 2], [3, 4]]).size(), [3, 4]);
        } finally {
          v.clipExtent(null);
        }
      },
      "has the default x-accessor, d[0]": function(v) {
        assert.strictEqual(v.x()([42, 43]), 42);
      },
      "has the default y-accessor, d[1]": function(v) {
        assert.strictEqual(v.y()([42, 43]), 43);
      },
      "of two points": {
        topic: function(v) {
          return v([[200, 200], [760, 300]]);
        },
        "returns two cells with the expected geometry": function(cells) {
          assert.inDelta(cells, [
            [[-178046.7857142857, 1e6], [179096.07142857145, -1e6], [-1e6, -1e6], [-1e6, 1e6]],
            [[179096.07142857145, -1e6], [-178046.7857142857, 1e6], [1e6, 1e6], [1e6, -1e6]]
          ], 1e-6);
        },
        "the returned cells are open polygons": function(cells) {
          cells.forEach(function(cell) {
            assert.greater(cell.length, 2);
            cell.forEach(function(point) {
              assert.equal(point.length, 2);
              assert.isNumber(point[0]);
              assert.isNumber(point[1]);
            });
            assert.notDeepEqual(cell[0], cell[cell.length - 1]);
          });
        },
        "the returned cells are not clipped to the layout size": function(cells) {
          var x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
          cells.forEach(function(cell) {
            cell.forEach(function(point) {
              if (point[0] < x0) x0 = point[0];
              if (point[0] > x1) x1 = point[0];
              if (point[1] < y0) y0 = point[1];
              if (point[1] > y1) y1 = point[1];
            });
          });
          assert.strictEqual(x0, -1e6);
          assert.strictEqual(x1, 1e6);
          assert.strictEqual(y0, -1e6);
          assert.strictEqual(y1, 1e6);
        },
        "the returned cells' point property points back to the input point": function(cells) {
          assert.deepEqual(cells.map(function(cell) { return cell.point; }), [[200, 200], [760, 300]]);
        },
        "the returned cells’ have positive area": function(cells) {
          cells.forEach(function(cell) {
            assert.ok(_.geom.polygon(cell).area() > 0);
          });
        }
      },
      "links": {
        "for two points": function(v) {
          assert.deepEqual(v.links([[200, 200], [760, 300]]), [
            {source: [200, 200], target: [760, 300]}
          ]);
        },
        "for three points": function(v) {
          assert.deepEqual(v.links([[200, 200], [500, 250], [760, 300]]), [
            {source: [200, 200], target: [500, 250]},
            {source: [500, 250], target: [760, 300]},
            {source: [760, 300], target: [200, 200]}
          ]);
        }
      },
      "triangles": {
        "for three points": function(v) {
          assert.deepEqual(v.triangles([[200, 200], [500, 250], [760, 300]]), [
            [[200, 200], [760, 300], [500, 250]]
          ]);
        }
      }
    },

    "a voronoi layout with custom x- and y-accessors": {
      topic: function(voronoi) {
        return voronoi()
            .x(function(d) { return d.x; })
            .y(43);
      },
      "observes the specified x-accessor, a function": function(v) {
        assert.strictEqual(v.x()({x: 42, y: 43}), 42);
      },
      "observes the specified y-accessor, a constant": function(v) {
        assert.strictEqual(v.y(), 43);
      },
      "of two points": {
        topic: function(v) {
          return v([{x: 200}, {x: 760}]);
        },
        "returns two cells with the expected geometry": function(cells) {
          assert.inDelta(cells, [
            [[480, 1e6], [480, -1e6], [-1e6, -1e6], [-1e6, 1e6]],
            [[480, -1e6], [480, 1e6], [1e6, 1e6], [1e6, -1e6]]
          ], 1e-6);
        }
      },
      "links": {
        topic: function(v) {
          return v.y(function(d) { return d.y; });
        },
        "for two points": function(v) {
          assert.deepEqual(v.links([{x: 200, y: 200}, {x: 760, y: 300}]), [
            {source: {x: 200, y: 200}, target: {x: 760, y: 300}}
          ]);
        },
        "for three points": function(v) {
          assert.deepEqual(v.links([{x: 200, y: 200}, {x: 500, y: 250}, {x: 760, y: 300}]), [
            {source: {x: 200, y: 200}, target: {x: 500, y: 250}},
            {source: {x: 500, y: 250}, target: {x: 760, y: 300}},
            {source: {x: 760, y: 300}, target: {x: 200, y: 200}}
          ]);
        }
      }
    },

    "a voronoi layout with clip extent [[0, 0], [960, 500]]": {
      topic: function(voronoi) {
        return voronoi()
            .x(function(d) { return d.x; })
            .y(function(d) { return d.y; })
            .clipExtent([[0, 0], [960, 500]]);
      },
      "of two points": {
        topic: function(v) {
          return v([{x: 200, y: 200}, {x: 760, y: 300}]);
        },
        "returns two cells with the expected geometry": function(cells) {
          assert.inDelta(cells, [
            [[435.35714285715324, 500], [524.6428571428696, 0], [0, 0], [0, 500]],
            [[524.6428571428696, 0], [435.35714285715324, 500], [960, 500], [960, 0]]
          ], 1e-6);
        },
        "the returned cells are clipped to the layout size": function(cells) {
          assert.isTrue(cells.every(function(cell) {
            return cell.every(function(point) {
              return point[0] >= 0 && point[0] <= 960
                  && point[1] >= 0 && point[1] <= 500;
            });
          }));
        }
      }
    },

    "the default voronoi layout applied directly": {
      "with zero points": {
        "returns the empty array": function(voronoi) {
          assert.deepEqual(polygons(voronoi([])), []);
        }
      },
      "with one point": {
        topic: function(v) {
          return v([[50, 50]]);
        },
        "returns the semi-infinite bounding box": function(cells) {
          assert.deepEqual(polygons(cells), [
            [[-1e6, 1e6], [1e6, 1e6], [1e6, -1e6], [-1e6, -1e6]]
          ]);
        },
        "the returned cell has positive area": function(cells) {
          assert.ok(_.geom.polygon(cells[0]).area() > 0);
        }
      },
      "with two points": {
        "separated by a horizontal line": function(voronoi) {
          assert.deepEqual(polygons(voronoi([[0, -100], [0, 100]])), [
            [[-1e6, 0], [1e6, 0], [1e6, -1e6], [-1e6, -1e6]],
            [[1e6, 0], [-1e6, 0], [-1e6, 1e6], [1e6, 1e6]],
          ]);
          assert.deepEqual(polygons(voronoi([[0, 100], [0, -100]])), [
            [[1e6, 0], [-1e6, 0], [-1e6, 1e6], [1e6, 1e6]],
            [[-1e6, 0], [1e6, 0], [1e6, -1e6], [-1e6, -1e6]]
          ]);
        },
        "separated by a vertical line": function(voronoi) {
          assert.deepEqual(polygons(voronoi([[100, 0], [-100, 0]])), [
            [[0, -1e6], [0, 1e6], [1e6, 1e6], [1e6, -1e6]],
            [[0, 1e6], [0, -1e6], [-1e6, -1e6], [-1e6, 1e6]]
          ]);
          assert.deepEqual(polygons(voronoi([[-100, 0], [100, 0]])), [
            [[0, 1e6], [0, -1e6], [-1e6, -1e6], [-1e6, 1e6]],
            [[0, -1e6], [0, 1e6], [1e6, 1e6], [1e6, -1e6]]
          ]);
        },
        "separated by a diagonal line": function(voronoi) {
          assert.deepEqual(polygons(voronoi([[-100, -100], [100, 100]])), [
            [[-1e6, 1e6], [1e6, -1e6], [-1e6, -1e6]],
            [[1e6, -1e6], [-1e6, 1e6], [1e6, 1e6]]
          ]);
          assert.deepEqual(polygons(voronoi([[100, 100], [-100, -100]])), [
            [[1e6, -1e6], [-1e6, 1e6], [1e6, 1e6]],
            [[-1e6, 1e6], [1e6, -1e6], [-1e6, -1e6]]
          ]);
        },
        "separated by an arbitrary diagonal": function(voronoi) {
          assert.deepEqual(polygons(voronoi([[-100, -100], [100, 0]])), [
            [[-500025, 1e6], [499975, -1e6], [-1e6, -1e6], [-1e6, 1e6]],
            [ [499975, -1e6], [-500025, 1e6], [1e6, 1e6], [1e6, -1e6]]
          ]);
        }
      },
      "with three points": {
        "collinear": function(voronoi) {
          assert.deepEqual(polygons(voronoi([[-100, -100], [0, 0], [100, 100]])), [
            [[-1e6, 999900], [999900, -1e6], [-1e6, -1e6]],
            [[-999900, 1e6], [1e6, -999900], [1e6, -1e6], [999900, -1e6], [-1e6, 999900], [-1e6, 1e6]],
            [[1e6, -999900], [-999900, 1e6], [1e6, 1e6]]
          ]);
        }
      }
    }
  }
});

function polygons(cells) {
  cells.forEach(function(cell) { delete cell.point; });
  return cells;
}

suite.export(module);
