local vec3        = require "modules.vec3"
local DBL_EPSILON = require("modules.constants").DBL_EPSILON
local abs, sqrt   = math.abs, math.sqrt

describe("vec3:", function()
	it("creates an empty vector", function()
		local a = vec3()
		assert.is.equal(0, a.x)
		assert.is.equal(0, a.y)
		assert.is.equal(0, a.z)
		assert.is_true(a:is_vec3())
		assert.is_true(a:is_zero())
	end)

	it("creates a vector from a number", function()
		local a = vec3(3)
		assert.is.equal(3, a.x)
		assert.is.equal(3, a.y)
		assert.is.equal(3, a.z)
	end)

	it("creates a vector from numbers", function()
		local a = vec3(3, 5, 7)
		assert.is.equal(3, a.x)
		assert.is.equal(5, a.y)
		assert.is.equal(7, a.z)
	end)

	it("creates a vector from a list", function()
		local a = vec3 { 3, 5, 7 }
		assert.is.equal(3, a.x)
		assert.is.equal(5, a.y)
		assert.is.equal(7, a.z)
	end)

	it("creates a vector from a record", function()
		local a = vec3 { x=3, y=5, z=7 }
		assert.is.equal(3, a.x)
		assert.is.equal(5, a.y)
		assert.is.equal(7, a.z)
	end)

	it("creates a vector from nan", function()
		local a = vec3(0/0)
		assert.is_true(a:has_nan())
	end)

	it("clones a vector", function()
		local a = vec3(3, 5, 7)
		local b = a:clone()
		assert.is.equal(a, b)
	end)

	it("clones a vector using the constructor", function()
		local a = vec3(3, 5, 7)
		local b = vec3(a)
		assert.is.equal(a, b)
	end)

	it("adds a vector to another", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:add(b)
		local d = a + b
		assert.is.equal(10, c.x)
		assert.is.equal(9,  c.y)
		assert.is.equal(8,  c.z)
		assert.is.equal(c,  d)
	end)

	it("subracts a vector from another", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:sub(b)
		local d = a - b
		assert.is.equal(-4, c.x)
		assert.is.equal( 1, c.y)
		assert.is.equal( 6, c.z)
		assert.is.equal( c, d)
	end)

	it("multiplies a vector by a scale factor", function()
		local a = vec3(3, 5, 7)
		local s = 2
		local c = a:scale(s)
		local d = a * s
		assert.is.equal(6,  c.x)
		assert.is.equal(10, c.y)
		assert.is.equal(14, c.z)
		assert.is.equal(c,  d)
	end)

	it("divides a vector by another vector", function()
		local a = vec3(3, 5, 7)
		local s = vec3(2, 2, 2)
		local c = a:div(s)
		local d = a / s
		assert.is.equal(1.5, c.x)
		assert.is.equal(2.5, c.y)
		assert.is.equal(3.5, c.z)
		assert.is.equal(c,   d)
	end)

	it("inverts a vector", function()
		local a = vec3(3, -5, 7)
		local b = -a
		assert.is.equal(-a.x, b.x)
		assert.is.equal(-a.y, b.y)
		assert.is.equal(-a.z, b.z)
	end)

	it("gets the length of a vector", function()
		local a = vec3(3, 5, 7)
		assert.is.equal(sqrt(83), a:len())
		end)

	it("gets the square length of a vector", function()
		local a = vec3(3, 5, 7)
		assert.is.equal(83, a:len2())
	end)

	it("normalizes a vector", function()
		local a = vec3(3, 5, 7)
		local b = a:normalize()
		assert.is_true(abs(b:len()-1) < DBL_EPSILON)
	end)

	it("normalizes a vector and gets the length", function()
		local a = vec3(3, 5, 7)
		local b, l = a:normalize_len()
		assert.is_true(abs(b:len()-1) < DBL_EPSILON)
		assert.is.equal(sqrt(83), l)
	end)

	it("trims the length of a vector", function()
		local a = vec3(3, 5, 7)
		local b = a:trim(0.5)
		assert.is_true(abs(b:len()-0.5) < DBL_EPSILON)
	end)

	it("gets the distance between two vectors", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:dist(b)
		assert.is.equal(sqrt(53), c)
	end)

	it("gets the square distance between two vectors", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:dist2(b)
		assert.is.equal(53, c)
	end)

	it("crosses two vectors", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:cross(b)
		assert.is.equal(-23, c.x)
		assert.is.equal( 46, c.y)
		assert.is.equal(-23, c.z)
	end)

	it("dots two vectors", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local c = a:dot(b)
		assert.is.equal(48, c)
	end)

	it("interpolates between two vectors", function()
		local a = vec3(3, 5, 7)
		local b = vec3(7, 4, 1)
		local s = 0.1
		local c = a:lerp(b, s)
		assert.is.equal(3.4, c.x)
		assert.is.equal(4.9, c.y)
		assert.is.equal(6.4, c.z)
	end)

	it("unpacks a vector", function()
		local a       = vec3(3, 5, 7)
		local x, y, z = a:unpack()
		assert.is.equal(3, x)
		assert.is.equal(5, y)
		assert.is.equal(7, z)
	end)

	it("rotates a vector", function()
		local a = vec3(3, 5, 7)
		local b = a:rotate( math.pi, vec3.unit_z)
		local c = b:rotate(-math.pi, vec3.unit_z)
		assert.is_not.equal(a, b)
		assert.is.equal(7, b.z)
		assert.is.equal(a, c)
	end)

	it("cannot rotate a vector without a valis axis", function()
		local a = vec3(3, 5, 7)
		local b = a:rotate(math.pi, 0)
		assert.is_equal(a, b)
	end)

	it("gets a perpendicular vector", function()
		local a = vec3(3, 5, 7)
		local b = a:perpendicular()
		assert.is.equal(-5, b.x)
		assert.is.equal( 3, b.y)
		assert.is.equal( 0, b.z)
	end)

	it("gets a string representation of a vector", function()
		local a = vec3()
		local b = a:to_string()
		assert.is.equal("(+0.000,+0.000,+0.000)", b)
	end)

	it("rounds a 3-vector", function()
		local a = vec3(1.1,1.9,3):round()
		assert.is.equal(a.x, 1)
		assert.is.equal(a.y, 2)
		assert.is.equal(a.z, 3)
	end)

	it("flips a 3-vector", function()
		local a = vec3(1,2,3)
		local temp = a:flip_x()
		assert.is.equal(temp, vec3(-1, 2, 3))
		temp = temp:flip_y()
		assert.is.equal(temp, vec3(-1, -2, 3))
		temp = temp:flip_z()
		assert.is.equal(temp, vec3(-1, -2, -3))
	end)

	it("get two 3-vectors angle", function()
		local angle_to = function(a, b)
			local deg = math.deg(a:angle_to(b))
			return string.format('%.2f', deg)
		end

		local a = vec3(1,2,3)
		assert.is.equal(angle_to(a, vec3(3, 2, 1)), '44.42')
		assert.is.equal(angle_to(a, vec3(0, 10, 0)), '57.69')
		assert.is.equal(angle_to(a, vec3(0, -12, -10)), '157.51')

		a = vec3.unit_z
		assert.is.equal(angle_to(a, vec3(0, 10, 0)), '90.00')
		assert.is.equal(angle_to(a, vec3(-123, 10, 0)), '90.00')
		assert.is.equal(angle_to(a, vec3(-10, 0, 10)), '45.00')
		assert.is.equal(angle_to(a, vec3(-10, 0, -10)), '135.00')
		assert.is.equal(angle_to(a, vec3(0, -10, -10)), '135.00')
		assert.is.equal(angle_to(a, vec3(0, 0, -10)), '180.00')
		assert.is.equal(angle_to(a, vec3(0, 0, 100)), '0.00')

		a = vec3(100, 100, 0)
		assert.is.equal(angle_to(a, vec3(0, 0, 100)), '90.00')
		assert.is.equal(angle_to(a, vec3(0, 0, -100)), '90.00')
		assert.is.equal(angle_to(a, vec3(-10, -10, 0)), '180.00')
		assert.is.equal(angle_to(a, vec3.unit_z), '90.00')
	end)
end)
