local export = {}

local format = string.format
local is_integer -- defined as export.is_integer below
local is_positive_integer -- defined as export.is_positive_integer below
local log = math.log
local log10 = math.log10
local select = select
local tonumber = tonumber
local tostring = tostring
local type = type

--[==[
Returns true if the given value is a finite real number, or false if not.]==]
function export.is_finite_real_number(n)
	return n and type(n) == "number" and n - n == 0 -- INF, -INF and NAN fail here.
end

--[==[
Returns true if the given value is an integer, or false if not.]==]
function export.is_integer(n)
	return n and type(n) == "number" and n % 1 == 0 -- INF, -INF and NAN also fail here.
end
is_integer = export.is_integer

--[==[
Returns true if the given value is a positive integer (or zero if the `include_0` flag is set), or false if not.]==]
function export.is_positive_integer(n, include_0)
	return is_integer(n) and (n > 0 or include_0 and n == 0) or false
end
is_positive_integer = export.is_positive_integer

--[==[
Converts a decimal number to hexadecimal.]==]
function export.to_hex(dec)
	dec = tonumber(dec)
	if not is_positive_integer(dec, true) then
		error("must be an integer greater than or equal to 0")
	elseif dec >= 18446744073709551616 then
		error("integer overflow") -- string.format returns "0" for numbers >= 2^64
	end
	return format("%X", dec)
end

--[==[
Returns the base-10 logarithm of `x`.

Should be used instead of `math.log10`, which is deprecated and may stop working
if Scribunto is updated to a more recent Lua version.]==]
function export.log10(x)
	if log10 == nil then
		if log(10, 10) == 1 then -- Lua 5.2
			function log10(x)
				return log(x, 10)
			end
		else
			function log10(x)
				return log(x) * 0.43429448190325182765112891891660508229439700580367 -- log10(e)
			end
		end
	end
	export.log10 = log10
end
export.log10() -- Sets the actual returned function. Structured like this so that module docuemntation works.

--[==[
Returns the greatest common divisor.]==]
function export.gcd(n, q, ...)
	if not is_integer(n) then
		local type_n = type(n)
		error(format(
			"bad argument #%d to 'gcd' (integer expected, got %s)",
			1, type_n == "number" and tostring(n) or type_n
		), 2)
	end
	local args_n, integers = select("#", q, ...)
	-- q is always counted in args_n, but if it's nil there's nothing left to do.
	if args_n == 1 and q == nil then
		args_n = 0
	end
	-- Compute p_1 = gcd(n_1, n_2), p_2 = gcd(p_1, n_3), ... i.e. compute GCD by Euclid's algorithm for the current result and the next number.
	for i = 1, args_n do
		if not is_integer(q) then
			local type_q = type(q)
			error(format(
				"bad argument #%d to 'gcd' (integer expected, got %s)",
				i + 1, type_q == "number" and tostring(q) or type_q
			), 2)
		elseif n ~= 1 then -- If n is 1, validate remaining inputs.
			-- GCD of two integers n, q with Euclid's algorithm.
			while q ~= 0 do
				n, q = q, n % q
			end
		end
		if i == 1 then
			q = ...
		elseif i < args_n then
			-- Only create a table if absolutely necessary, as it's inefficient.
			if i == 2 then
				integers = {...}
			end
			q = integers[i]
		end
	end
	return n < 0 and -n or n
end

--[==[
Returns the least common multiple.]==]
function export.lcm(n, q, ...)
	if not is_integer(n) then
		local type_n = type(n)
		error(format(
			"bad argument #%d to 'lcm' (integer expected, got %s)",
			1, type_n == "number" and tostring(n) or type_n
		), 2)
	end
	local args_n, integers = select("#", q, ...)
	-- q is always counted in args_n, but if it's nil there's nothing left to do.
	if args_n == 1 and q == nil then
		args_n = 0
	end
	-- Compute the product of all inputs as p and GCD as n.
	for i = 1, args_n do
		if not is_integer(q) then
			local type_q = type(q)
			error(format(
				"bad argument #%d to 'lcm' (integer expected, got %s)",
				i + 1, type_q == "number" and tostring(q) or type_q
			), 2)
		elseif n ~= 0 then  -- If n is 0, validate remaining inputs.
			-- Compute the product.
			local p = n * q
			-- GCD of two integers n, q with Euclid's algorithm.
			while q ~= 0 do
				n, q = q, n % q
			end
			-- Divide product by the GCD to get new LCM.
			n = p / n
		end
		if i == 1 then
			q = ...
		elseif i < args_n then
			-- Only create a table if absolutely necessary, as it's inefficient.
			if i == 2 then
				integers = {...}
			end
			q = integers[i]
		end
	end
	return n < 0 and -n or n
end

return export