local glm = honey.glm

local module = {}
setmetatable(module, {__index=_G})
setfenv(1, module)


Vec3 = {}
Mat4 = {}
Quaternion = {}


--===== Vec3 =====--


function Vec3.new(_, values)
	local self = {}
	self.data = glm.vec3_create()
	setmetatable(self, Vec3)
	if values then
		self[1] = values[1]
		self[2] = values[2]
		self[3] = values[3]
	end
	return self
end
setmetatable(Vec3, {__call=Vec3.new})


function Vec3.__index(self, key)
	if type(key) == 'number' then
		return glm.vec3_get(self.data, key-1)
	else
		return Vec3[key]
	end
end


function Vec3.__newindex(self, key, value)
	glm.vec3_set(self.data, key-1, value)
end


function Vec3.__tostring(self)
	return string.format("Vec3[%.4f, %.4f, %.4f]", self[1], self[2], self[3])
end


--===== arithmetic =====--

local function swapIfNumber(self, other)
	if type(self) == "number" and type(other) == "table" then
		return other, self
	else
		return self, other
	end
end


function Vec3.__add(self, other)
	local self, other = swapIfNumber(self, other)

	local dest = Vec3()
	if type(other) == "number" then
		glm.vec3_adds(self.data, other, dest.data)
	elseif type(other) == "table" then
		glm.vec3_add(self.data, other.data, dest.data)
	else
		error(string.format("cannot add %s to Vec3", type(other)))
	end
	return dest
end


function Vec3.__sub(self, other)
	local dest = Vec3()
	if type(other) == "number" then
		glm.vec3_subs(self.data, other, dest.data)
	elseif type(other) == "table" then
		glm.vec3_sub(self.data, other.data, dest.data)
	else
		error(string.format("cannot subtract %s from Vec3", type(other)))
	end
	return dest
end


function Vec3.__mul(self, other)
	local self, other = swapIfNumber(self, other)
	local dest = Vec3()
	if type(other) == "number" then
		glm.vec3_scale(self.data, other, dest.data)
	elseif type(other) == "table" then
		glm.vec3_mul(self.data, other.data, dest.data)
	else
		error(string.format("cannot multiply %s and Vec3", type(other)))
	end
	return dest
end


function Vec3.__div(self, other)
	local dest = Vec3()
	if type(other) == "number" then
		glm.vec3_divs(self.data, other, dest.data)
	elseif type(other) == "table" then
		glm.vec3_div(self.data, other.data, dest.data)
	else
		error(string.format("cannot divide Vec3 by %s", type(other)))
	end
	return dest
end




function Vec3.copyTo(self, dest)
	glm.vec3_copy(self.data, dest.data)
end


function Vec3.zero(self)
	glm.vec3_zero(self.data)
end


function Vec3.zero(self)
	glm.vec3_zero(self.data)
end
function Vec3.one(self)
	glm.vec3_one(self.data)
end


function Vec3.dot(self, other)
	return glm.vec3_dot(self.data, other.data)
end


function Vec3.crossTo(self, other, dest)
	glm.vec3_cross(self.data, other.data, dest.data)
end
function Vec3.cross(self, other)
	local dest = Vec3()
	self:crossTo(other, dest)
	return dest
end


function Vec3.crossnTo(self, other, dest)
	glm.vec3_crossn(self.data, other.data, dest.data)
end
function Vec3.crossn(self, other)
	local dest = Vec3()
	self:crossTo(other, dest)
	return dest
end


function Vec3.norm2(self)
	return glm.vec3_norm2(self.data)
end
function Vec3.norm(self)
	return glm.vec3_norm(self.data)
end


function Vec3.normalize(self)
	glm.vec3_normalize(self.data)
end
function Vec3.normalizeTo(self, dest)
	glm.vec3_normalize_to(self.data, dest.data)
end


----------------------------------------

local RowLookup = {}
function RowLookup.new(_, row, data)
	local self = {
		row=row,
		data=data,
	}
	setmetatable(self, RowLookup)
	return self
end
setmetatable(RowLookup, {__call=RowLookup.new})
function RowLookup.__index(self, col)
	return glm.mat4_get(self.data, col-1, self.row-1)
end
function RowLookup.__newindex(self, col, value)
	return glm.mat4_set(self.data, col-1, self.row-1, value)
end


--===== Mat4 =====--


function Mat4.new(_, self, values)
	local self = {}
	self.type = "mat4"
	self.data = glm.mat4_create()
	setmetatable(self, Mat4)
	if values then
		self[1][1] = values[1]
		self[1][2] = values[2]
		self[1][3] = values[3]
		self[1][4] = values[4]

		self[2][1] = values[5]
		self[2][2] = values[6]
		self[2][3] = values[7]
		self[2][4] = values[8]

		self[3][1] = values[9]
		self[3][2] = values[10]
		self[3][3] = values[11]
		self[3][4] = values[12]

		self[4][1] = values[13]
		self[4][2] = values[14]
		self[4][3] = values[15]
		self[4][4] = values[16]
	end
	return self
end
setmetatable(Mat4, {__call=Mat4.new})


function Mat4.__index(self, key)
	if type(key) == "number" then
		return RowLookup(key, self.data)
	else
		return Mat4[key]
	end
end


function Mat4.__tostring(self)
	return string.format(
		"/ %0.4f, %0.4f, %0.4f, %0.4f \\\n" ..
		"| %0.4f, %0.4f, %0.4f, %0.4f |\n" ..
		"| %0.4f, %0.4f, %0.4f, %0.4f |\n" ..
		"\\ %0.4f, %0.4f, %0.4f, %0.4f /",
		self[1][1], self[1][2], self[1][3], self[1][4],
		self[2][1], self[2][2], self[2][3], self[2][4],
		self[3][1], self[3][2], self[3][3], self[3][4],
		self[4][1], self[4][2], self[4][3], self[4][4]
	)
end


function Mat4.__mul(self, other)
	if other.type == "mat4" then
		local dest = Mat4()
		glm.mat4_mul(self.data, other.data, dest.data)
		return dest
	elseif other.type == "vec4" then
		-- todo
	elseif other.type == "vec3" then
		local dest = Vec3()
		glm.mat4_mulv3(self.data, other.data, 1.0, dest.data)
		return dest
	else
		error(string.format("cannot multiply Mat4 by %s", type(other)))
	end
end


function Mat4.copyTo(self, dest)
	glm.mat4_copy(self.data, dest.data)
end


function Mat4.identity(self)
	glm.mat4_identity(self.data)
	return self
end


function Mat4.zero(self)
	glm.mat4_zero(self.data)
	return self
end


function Mat4.mul(self, other)
	glm.mat4_mul(self.data, other.data, self.data)
	return self
end


function Mat4.translate(self, vec)
	glm.translate(self.data, vec.data)
	return self
end


function Mat4.rotateX(self, angle)
	glm.rotate_x(self.data, angle, self.data)
	return self
end
function Mat4.rotateY(self, angle)
	glm.rotate_y(self.data, angle, self.data)
	return self
end
function Mat4.rotateZ(self, angle)
	glm.rotate_z(self.data, angle, self.data)
	return self
end


function Mat4.scale(self, vec)
	glm.scale(self.data, vec.data)
	return self
end


function Mat4.perspective(self, fovy, aspect, near, far)
	glm.perspective(fovy, aspect, near, far, self.data)
	return self
end
function Mat4.perspectiveResize(self, aspect)
	glm.perspective_resize(aspect, self.data)
	return self
end


--===== Quaternion =====--


Quaternion.__index = Quaternion


function Quaternion.new(_, tbl)
	local tbl = tbl or { 0, 0, 0, 0 }
	local self = {}
	self.data = glm.quat_create()
	glm.quat_init(self.data, unpack(tbl))
	setmetatable(self, Quaternion)
	return self
end
setmetatable(Quaternion, {__call=Quaternion.new})


function Quaternion.toMat4(self)
	local m = Mat4()
	glm.quat_mat4(self.data, m.data)
	return m
end


---------------------------


return module