describe("Vector", function()
	local vector = require("math.vector")
	local function assert_almost_equal(expected_num, num)
		assert(expected_num - num < 1e-6)
	end
	local function random_int_vec()
		return vector.new({ math.random(-10, 10), math.random(-10, 10), math.random(-10, 10) })
	end
	it("constructors", function()
		assert.same({ 1, 2, 3 }, vector.new({ 1, 2, 3 }))
		assert.same({ 0, 0, 0 }, vector.make(0, 3))
	end)
	it("scalar multiplication", function()
		assert.same(vector.new({ 2, 4, 6 }), vector.new({ 1, 2, 3 }) * 2)
	end)
	it("dot product", function()
		assert.same(1 * 3 + 2 * 2 + 3 * 1, vector.new({ 1, 2, 3 }) * vector.new({ 3, 2, 1 }))
	end)
	it("cross product", function()
		assert.same(vector.new({ 0, -1, 0 }), vector.new({ 1, 0, 0 }):cross(vector.new({ 0, 0, 1 })))
		for _ = 1, 100 do
			local a, b = random_int_vec(), random_int_vec()
			local c = a:cross(b)
			assert(c:orthogonal(a) and c:orthogonal(b))
			assert_almost_equal(math.abs(math.sin(a:angle(b))) * a:length() * b:length(), c:length())
		end
	end)
	it("minus", function()
		assert.same(vector.new({ -1, -2, -3 }), -vector.new({ 1, 2, 3 }))
	end)
	it("addition", function()
		assert.same(vector.new({ 4, 4, 4 }), vector.new({ 1, 2, 3 }) + vector.new({ 3, 2, 1 }))
	end)
	it("subtraction", function()
		assert.same(vector.new({ 0, 0, 0 }), vector.new({ 1, 2, 3 }) - vector.new({ 1, 2, 3 }))
	end)
	it("division", function()
		assert.same(vector.new({ 1, 2, 3 }) / 2, 0.5 * vector.new({ 1, 2, 3 }))
		assert.same(2 / vector.new({ 1, 2, 3 }), vector.new({ 2 / 1, 2 / 2, 2 / 3 }))
	end)
	it("comparison", function()
		assert.equal(true, vector.new({ 1, 2, 3 }) == vector.new({ 1, 2, 3 }))
		assert.equal(false, vector.new({ 1, 2, 2 }) == vector.new({ 1, 2, 3 }))
	end)
	it("length", function()
		assert.equal(3, vector.new({ 1, 2, 2 }):length())
		assert.equal(21, vector.new({ 4, 13, 16 }):length())
	end)
	it("distance", function()
		assert.equal(3, vector.new({ 1 + 1, 2 + 2, 3 + 2 }):distance(vector.new({ 1, 2, 3 })))
	end)
	it("normalization", function()
		assert.same(vector.new({ 1, 2, 2 }) / 3, vector.new({ 1, 2, 2 }):normalize())
		assert.same(vector.new({ 4, 13, 16 }) / 21, vector.new({ 4, 13, 16 }):normalize())
		assert.same(vector.new({ 0, 0 }), vector.new({ 0, 0 }):normalize())
	end)
	it("angle between vectors", function()
		assert_almost_equal(math.pi, vector.new({ 1, 1, 1 }):angle(vector.new({ -1, -1, -1 })))
		assert_almost_equal(math.pi / 2, vector.new({ 1, 0, 0 }):angle(vector.new({ 0, 0, 1 })))
		assert_almost_equal(math.pi / 4, vector.new({ 1, 0, 0 }):angle(vector.new({ 1, 1, 0 })))
	end)
	it("orthogonality check", function()
		assert.equal(true, vector.new({ 1, 0, 0 }):orthogonal(vector.new({ 0, 1, 0 })))
		assert.equal(true, vector.new({ 1, 0, 3 }):orthogonal(vector.new({ 0, 2, 0 })))
		assert.equal(true, vector.new({ 1, 2, 4 }):orthogonal(vector.new({ 2, 1, -1 })))
		assert.equal(false, vector.new({ 1, 0, 0 }):orthogonal(vector.new({ 0.1, 1, 0 })))
	end)
	it("parallelity check", function()
		assert.equal(true, vector.new({ 1, 2, 3 }):parallel(2 * vector.new({ 1, 2, 3 })))
		assert.equal(true, vector.new({ 1, 2, 3 }):parallel(-vector.new({ 1, 2, 3 })))
		assert.equal(false, vector.new({ 1, 0, 0 }):parallel(vector.new({ 0, 1, 0 })))
	end)
	it("reflection", function()
		assert.equal(vector.new({ 1, -2, 3 }), vector.new({ 1, 2, 3 }):reflect(vector.new({ 0, 1, 0 })))
		local v = vector.new({ 1, 0, 0 }):reflect(vector.new({ 1, 1, 0 }):normalize())
		assert_almost_equal(0, v[1])
		assert_almost_equal(-1, v[2])
		assert_almost_equal(0, v[3])
	end)
	it("tostring", function()
		assert.equal("(1 2 3)", tostring(vector.new({ 1, 2, 3 })))
	end)
end)
