local lib = ...
local fn = {}

fn.vsep = function(vec) -- separate a vector into a direction + magnitude
	return vec:normalize(), vec:length()
end

-- minetest now only provides the version of this function that sqrts the result
-- which is pointlessly wasteful much of the time
fn.vdsq = function(a,b)
	local d = vector.subtract(v1,v2)
	return (d.x ^ 2) + (d.y ^ 2) + (d.z ^ 2)
end

fn.vdcomp = function(dist,v1,v2) -- compare the distance between two points
	-- (cheaper than calculating distance outright)
	local d if v2
		then d = vector.subtract(v1,v2)
		else d = v1
	end
	local dsq = (d.x ^ 2) + (d.y ^ 2) + (d.z ^ 2)
	return dsq / (dist^2)
	-- [0,1) == less then
	-- 1 == equal
	-- >1 == greater than
end

-- produce an SI expression for a quantity
fn.si = function(unit, val, full, uncommonScales, prec)
	if val == 0 then return '0 ' .. unit end
	local scales = {
		{30, 'Q', 'quetta',true,  'q', 'quecto',true};
		{27, 'R', 'ronna', true,  'r', 'ronto', true};
		{24, 'Y', 'yotta', true,  'y', 'yocto', true};
		{21, 'Z', 'zetta', true,  'z', 'zepto', true};
		{18, 'E', 'exa',   true,  'a', 'atto',  true};
		{15, 'P', 'peta',  true,  'f', 'femto', true};
		{12, 'T', 'tera',  true,  'p', 'pico',  true};
		{9, 'G', 'giga',   true,  'n', 'nano',  true};
		{6, 'M', 'mega',   true,  'μ', 'micro', true};
		{3, 'k', 'kilo',   true,  'm', 'milli', true};
		{2, 'h', 'hecto',  false, 'c', 'centi', true};
		{1, 'da','deca',   false, 'd', 'deci',  false};
	}

	local function unitForAmt(n)
		if type(unit)=='table' then
			if n == 1
				then return unit[1]
				else return unit[2]
			end
		end
		return unit
	end

	for i, s in ipairs(scales) do
		local amt, smaj, pmaj, cmaj,
		           smin, pmin, cmin = lib.tbl.unpack(s)


		if math.abs(val) > 1 then
			if uncommonScales or cmaj then
				local denom = 10^amt
				local vd = val/denom
				if prec then vd = lib.math.trim(vd, prec) end
				if math.abs(val) >= (10^(amt)) then
					return string.format("%s%s%s",
						vd, (full and (' ' .. pmaj) or smaj), unitForAmt(vd))
				end
			end
		elseif math.abs(val) < 1 then
			if uncommonScales or cmin then
				local denom = 10^-amt
				local vd = val/denom
				if prec then vd = lib.math.trim(vd, prec) end
				if math.abs(val) <= (10^-(amt-1)) then
					return string.format("%s%s%s",
						vd, (full and (' ' .. pmin) or smin), unitForAmt(vd))
				end
			end
		end
	end

	return string.format("%s%s", val, unitForAmt(val))
end
function fn.siUI(u,v,f,us,...) return fn.si(u,v,f,us,2,...) end

function fn.lerp(t, a, b) return (1-t)*a + t*b end
function fn.gradient(grad, pos)
	local n = #grad
	if n == 1 then return grad[1] end
	local op = pos*(n-1)
	local idx = math.floor(op)
	local t = op-idx
	return fn.lerp(t, grad[1 + idx], grad[2 + idx])
end

function fn.trim(fl, prec)
	local fac = 10^prec
	return math.floor(fl * fac) / fac
end

function fn.sign(v)
	if v > 0 then return 1
	elseif v < 0 then return -1
	else return 0 end
end

function fn.toward(from, to, mag)
	local dir = fn.sign(to - from)
	local step = mag * dir
	if (dir ==  1 and from+step < to)
	or (dir == -1 and from+step > to)
		then return from+step
		else return to
	end
end

fn.rng = lib.class {
	__name = 'rng';
	construct = function(seed)
		return {seed = seed, rng = PcgRandom(seed)}
	end;
	__index = {
		int = function(self,m,x)
			return self.rng:next(m,x)
		end;
		real = function(self,m,x)
			local i = self:int()
			local f = (i+bit.lshift(1,31)) / bit.lshift(1, 32)
			if m==nil then return f end
			if x==nil then x=m m=0  end
			return m + ((x-m) * f)
		end;
		fork = function(self)
			return fn.rng(self:int())
		end;
	};
	__add = function(self,n)
		return fn.rng(self.seed + n)
	end;
}

fn.seedbank = lib.class {
	__name = 'seedbank';
	construct = function(seed)
		return {seed = seed}
	end;
	__index = function(self, n)
		return fn.rng(PcgRandom(self.seed+n):next())
	end;
	__add = function(self, n)
		return fn.seedbank(self.seed + n)
	end;
}
-- function fn.vlerp

function fn.timespec(n)
	if n == 0 then return '0s' end
	if n < 0 then return '-' .. fn.timespec(n*-1) end

	local sec = math.floor(n % 60)
	local hr = math.floor(n / 60)
	local spec = {}

	if hr  ~= 0 then table.insert(spec, string.format("%shr", hr))  end
	if sec ~= 0 then table.insert(spec, string.format("%ss",  sec)) end
	return table.concat(spec, ' ')
end
return fn
