require 'minitest/autorun'
require 'geometry/polygon'

describe Geometry::Polygon do
    Polygon = Geometry::Polygon

    let(:cw_unit_square) { Polygon.new [0,0], [0,1], [1,1], [1,0] }
    let(:unit_square) { Polygon.new [0,0], [1,0], [1,1], [0,1] }
    let(:simple_concave) { Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2] }
    subject { unit_square }

    it "must create a Polygon object with no arguments" do
	polygon = Geometry::Polygon.new
	assert_kind_of(Geometry::Polygon, polygon)
	assert_equal(0, polygon.edges.size)
	assert_equal(0, polygon.vertices.size)
    end

    it "must create a Polygon object from array arguments" do
	polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
	assert_kind_of(Geometry::Polygon, polygon)
	assert_equal(4, polygon.edges.size)
	assert_equal(4, polygon.vertices.size)
    end

    describe "when creating a Polygon from an array of Points" do
	it "must ignore repeated Points" do
	    polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [1,1], [0,1])
	    polygon.must_be_kind_of Geometry::Polygon
	    polygon.edges.size.must_equal 4
	    polygon.vertices.size.must_equal 4
	    polygon.must_equal Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
	end

	it "must collapse collinear Edges" do
	    polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0.5,1], [0,1])
	    polygon.must_equal Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1])
	end

	it "must collapse backtracking Edges" do
	    polygon = Geometry::Polygon.new [0,0], [2,0], [2,2], [1,2], [1,1], [1,2], [0,2]
	    polygon.must_equal Geometry::Polygon.new([0,0], [2,0], [2,2], [0,2])
	end
    end

    it "must compare identical polygons as equal" do
	(unit_square.eql? unit_square).must_equal true
    end

    it "must create closed polygons" do
	subject.closed?.must_equal true
    end

    it "must handle already closed polygons" do
	polygon = Geometry::Polygon.new([0,0], [1,0], [1,1], [0,1], [0,0])
	assert_kind_of(Geometry::Polygon, polygon)
	assert_equal(4, polygon.edges.size)
	assert_equal(4, polygon.vertices.size)
	assert_equal(polygon.edges.first.first, polygon.edges.last.last)
    end

    it "must return itself on close" do
	closed = subject.close
	closed.closed?.must_equal true
	closed.must_equal subject
	closed.must_be_same_as subject
    end

    describe "orientation" do
	it "must return true for clockwise" do
	    Polygon.new([0,0], [0,1], [1,1], [1,0]).clockwise?.must_equal true
	    Polygon.new([1,1], [1,3], [3,3], [3,1]).clockwise?.must_equal true
	end

	it "must return false for counterclockwise" do
	    Polygon.new([0,0], [1,0], [1,1], [0,1]).clockwise?.must_equal false
	    Polygon.new([1,1], [3,1], [3,3], [1,3]).clockwise?.must_equal false
	end
    end

    it "must gift wrap a square polygon" do
	polygon = Polygon.new [0,0], [1,0], [1,1], [0,1]
	convex_hull = polygon.wrap
	convex_hull.must_be_kind_of Geometry::Polygon
	convex_hull.edges.size.must_equal 4
	convex_hull.vertices.must_equal [[0,0], [0,1], [1,1], [1,0]].map {|a| Point[*a]}
    end

    it "must gift wrap another square polygon" do
	polygon = Polygon.new [0,1], [0,0], [1,0], [1,1]
	convex_hull = polygon.wrap
	convex_hull.must_be_kind_of Geometry::Polygon
	convex_hull.edges.size.must_equal 4
	convex_hull.vertices.must_equal [[0,0], [0,1], [1,1], [1,0]].map {|a| Point[*a]}
    end

    it "must gift wrap a concave polygon" do
	polygon = Polygon.new [0,0], [1,-1], [2,0], [1,1], [2,2], [0,1]
	convex_hull = polygon.wrap
	convex_hull.must_be_kind_of Geometry::Polygon
	convex_hull.edges.size.must_equal 5
	convex_hull.vertices.must_equal [Point[0, 0], Point[0, 1], Point[2, 2], Point[2, 0], Point[1, -1]]
    end

    it "must gift wrap a polygon" do
	polygon = Polygon.new [0,0], [1,-1], [2,0], [2,1], [0,1]
	convex_hull = polygon.wrap
	convex_hull.must_be_kind_of Geometry::Polygon
	convex_hull.edges.size.must_equal 5
	convex_hull.vertices.must_equal [[0,0], [0,1], [2,1], [2,0], [1,-1]].map {|a| Point[*a]}
    end

    it "must generate spokes" do
	unit_square.spokes.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1]]
	cw_unit_square.spokes.must_equal [Vector[-1,-1], Vector[-1,1], Vector[1,1], Vector[1,-1]]
	simple_concave.spokes.must_equal [Vector[-1,-1], Vector[1,-1], Vector[1,1], Vector[-1,1], Vector[-1,1], Vector[1,1], Vector[1,1], Vector[-1,1]]
    end

    describe "spaceship" do
	it "with a Point" do
	    (unit_square <=> Point[2,0]).must_equal(-1)
	    (unit_square <=> Point[1,0]).must_equal 0
	    (unit_square <=> Point[0.5,0.5]).must_equal 1
	end

	it "with a Point that lies on a horizontal edge" do
	    (unit_square <=> Point[0.5,0]).must_equal 0
	end
    end

    describe "when outsetting" do
	it "must outset a unit square" do
	    outset_polygon = unit_square.outset(1)
	    expected_polygon = Polygon.new [-1.0,-1.0], [2.0,-1.0], [2.0,2.0], [-1.0,2.0]
	    outset_polygon.must_equal expected_polygon
	end

	it "must outset a simple concave polygon" do
	    concave_polygon = Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
	    outset_polygon = concave_polygon.outset(1)
	    outset_polygon.must_equal Polygon.new [-1,-1], [5,-1], [5,3], [-1,3]
	end

	it "must outset a concave polygon" do
	    concave_polygon = Polygon.new [0,0], [4,0], [4,2], [3,2], [3,1], [1,1], [1,2], [0,2]
	    outset_polygon = concave_polygon.outset(2)
	    outset_polygon.must_equal Polygon.new [-2,-2], [6,-2], [6,4], [-2,4]
	end

	it "must outset an asymetric concave polygon" do
	    concave_polygon = Polygon.new [0,0], [4,0], [4,3], [3,3], [3,1], [1,1], [1,2], [0,2]
	    outset_polygon = concave_polygon.outset(2)
	    outset_polygon.must_equal Polygon.new [-2,-2], [6,-2], [6,5], [1,5], [1,4], [-2,4]
	end

	it "must outset a concave polygon with multiply-intersecting edges" do
	    concave_polygon = Polygon.new [0,0], [5,0], [5,2], [4,2], [4,1], [3,1], [3,2], [2,2], [2,1], [1,1], [1,2], [0,2]
	    outset_polygon = concave_polygon.outset(1)
	    outset_polygon.must_equal Polygon.new [-1,-1], [6,-1], [6,3], [-1,3]
	end

	it "must outset a concave polygon where the first outset edge intersects with the last outset edge" do
	    polygon = Polygon.new [0,0], [0,1], [2,1], [2,2], [-1,2], [-1,-1], [2,-1], [2,0]
	    polygon.edges.count.must_equal 8
	    polygon.outset(1).must_equal Polygon.new [3, 0], [3, 3], [-2, 3], [-2, -2], [3, -2]
	end

	# Naturally, this test is very sensitive to the input coordinate values. This is a painfully contrived example that
	#  checks for sensitivity to edges that are very close to horizontal, but not quite.
	# When the test fails, the first point of the offset polygon is at [0,-1]
	it "must not be sensitive to floating point rounding errors" do
	    polygon = Polygon.new [0, 0], [0, -2], [10, -2], [10, 10], [-100, 10], [-100, -22], [-69, -22], [-69, 3.552713678800501e-15], [0,0]
	    outset = polygon.outset(1)
	    outset.edges.first.first.must_equal Geometry::Point[-1,-1]
	end
    end

    describe "set operations" do
	describe "union" do
	    it "must union two adjacent squares" do
		polygonA = Polygon.new [0,0], [1,0], [1,1], [0,1]
		polygonB = Polygon.new [1,0], [2,0], [2,1], [1,1]
		(polygonA.union polygonB).must_equal Polygon.new [0,0], [2,0], [2,1], [0,1]
		(polygonA + polygonB).must_equal Polygon.new [0,0], [2,0], [2,1], [0,1]
	    end

	    it "must union two overlapping squares" do
		polygonA = Polygon.new [0,0], [2,0], [2,2], [0,2]
		polygonB = Polygon.new [1,1], [3,1], [3,3], [1,3]
		expected_polygon = Polygon.new [0,0], [2,0], [2,1], [3,1], [3,3], [1,3], [1,2], [0,2]
		union = polygonA.union polygonB
		union.must_be_kind_of Polygon
		union.must_equal expected_polygon
	    end

	    it "must union two overlapping clockwise squares" do
		polygonA = Polygon.new [0,0], [0,2], [2,2], [2,0]
		polygonB = Polygon.new [1,1], [1,3], [3,3], [3,1]
		expected_polygon = Polygon.new [0, 0], [0, 2], [1, 2], [1, 3], [3, 3], [3, 1], [2, 1], [2, 0]
		union = polygonA.union polygonB
		union.must_be_kind_of Polygon
		union.must_equal expected_polygon
	    end

	    it "must union two overlapping squares with collinear edges" do
		polygonA = Polygon.new [0,0], [2,0], [2,2], [0,2]
		polygonB = Polygon.new [1,0], [3,0], [3,2], [1,2]
		union = polygonA + polygonB
		union.must_be_kind_of Polygon
		union.must_equal Polygon.new [0,0], [3,0], [3,2], [0,2]
	    end
	end
    end
end