--- Extends Lua 5.2 math.
-- @module Utils.math
-- @see math
-- @usage local math = require('__stdlib__/stdlib/utils/math')
local Math = {}

Math.frexp = math.frexp
Math.sqrt = math.sqrt
Math.asin = math.asin
Math.random = math.random
Math.huge = math.huge
Math.abs = math.abs
Math.ldexp = math.ldexp
Math.exp = math.exp
Math.pow = math.pow
Math.pi = math.pi
Math.tan = math.tan
Math.acos = math.acos
Math.ceil = math.ceil
Math.atan2 = math.atan2
Math.tanh = math.tanh
Math.sin = math.sin
Math.min = math.min
Math.deg = math.deg
Math.sinh = math.sinh
Math.rad = math.rad
Math.randomseed = math.randomseed
Math.cosh = math.cosh
Math.modf = math.modf
Math.cos = math.cos
Math.atan = math.atan
Math.max = math.max
Math.log = math.log
Math.fmod = math.fmod
Math.floor = math.floor

for k, v in pairs(math) do if not Math[k] then Math[k] = v end end

local math_abs = math.abs
local math_floor = math.floor
local math_ceil = math.ceil
local math_min = math.min
local math_max = math.max
local math_huge = math.huge
local math_pi = math.pi
local math_log = math.log
local unpack = table.unpack

-- (( Math Constants
Math.DEG2RAD = math_pi / 180
Math.RAD2DEG = 180 / math_pi
Math.EPSILON = 1.401298e-45

Math.MAXINT8 = 128
Math.MININT8 = -128
Math.MAXUINT8 = 255

Math.MAX_INT8 = Math.MAXINT8
Math.MIN_INT8 = Math.MININT8
Math.MAX_UINT8 = Math.MAXUINT8

Math.MAXINT16 = 32768
Math.MININT16 = -32768
Math.MAXUINT16 = 65535
Math.MAX_INT16 = Math.MAXINT16
Math.MIN_INT16 = Math.MININT16
Math.MAX_UINT16 = Math.MAXUINT16

Math.MAXINT = 2147483648
Math.MAX_INT = Math.MAXINT
Math.MAXINT32 = Math.MAXINT
Math.MAX_INT32 = Math.MAXINT

Math.MAXUINT = 4294967296
Math.MAX_UINT = Math.MAXUINT
Math.MAXUINT32 = Math.MAXUINT
Math.MAX_UINT32 = Math.MAXUINT

Math.MININT = -2147483648
Math.MIN_INT = Math.MININT
Math.MININT32 = Math.MININT
Math.MIN_INT32 = Math.MININT

Math.MAXINT64 = 9223372036854775808
Math.MININT64 = -9223372036854775808
Math.MAXUINT64 = 18446744073709551615
Math.MAX_INT64 = Math.MAXINT64
Math.MIN_INT64 = Math.MININT64
Math.MAX_UINT64 = Math.MAXUINT64
-- ))

local function tuple(...)
    return type(...) == 'table' and ... or { ... }
end

function Math.log10(x)
    return math_log(x, 10)
end

--- Round a number.
-- @tparam number x
-- @treturn number the rounded number
function Math.round(x)
    return x >= 0 and math_floor(x + 0.5) or math_ceil(x - 0.5)
end

-- Returns the number x rounded to p decimal places.
-- @tparam number x
-- @tparam[opt=0] int p the number of decimal places to round to
-- @treturn number rounded to p decimal spaces.
function Math.round_to(x, p)
    local e = 10 ^ (p or 0)
    return math_floor(x * e + 0.5) / e
end

-- Returns the number floored to p decimal spaces.
-- @tparam number x
-- @tparam[opt=0] int p the number of decimal places to floor to
-- @treturn number floored to p decimal spaces.
function Math.floor_to(x, p)
    if (p or 0) == 0 then return math_floor(x) end
    local e = 10 ^ p
    return math_floor(x * e) / e
end

-- Returns the number ceiled to p decimal spaces.
-- @tparam number x
-- @tparam[opt=0] int p the number of decimal places to ceil to
-- @treturn number ceiled to p decimal spaces.
function Math.ceil_to(x, p)
    local e = 10 ^ (p or 0)
    return math_ceil(x * e + 0.5) / e
end

-- Various average (means) algorithms implementation
-- See: http://en.wikipedia.org/wiki/Average

--- Calculates the sum of a sequence of values.
-- @tparam tuple ... a tuple of numbers
-- @treturn the sum
function Math.sum(...)
    local x = tuple(...)
    local s = 0
    for _, v in ipairs(x) do s = s + v end
    return s
end

--- Calculates the arithmetic mean of a set of values.
-- @tparam array x an array of numbers
-- @treturn number the arithmetic mean
function Math.arithmetic_mean(...)
    local x = tuple(...)
    return (Math.sum(x) / #x)
end

Math.avg = Math.arithmetic_mean
Math.average = Math.arithmetic_mean

--- Calculates the geometric mean of a set of values.
-- @tparam array x an array of numbers
-- @treturn number the geometric mean
function Math.geometric_mean(...)
    local x = tuple(...)
    local prod = 1
    for _, v in ipairs(x) do prod = prod * v end
    return (prod ^ (1 / #x))
end

--- Calculates the harmonic mean of a set of values.
-- @tparam tuple ... an array of numbers
-- @treturn number the harmonic mean
function Math.harmonic_mean(...)
    local x = tuple(...)
    local s = 0
    for _, v in ipairs(x) do s = s + (1 / v) end
    return (#x / s)
end

--- Calculates the quadratic mean of a set of values.
-- @tparam tuple ... an array of numbers
-- @treturn number the quadratic mean
function Math.quadratic_mean(...)
    local x = tuple(...)
    local squares = 0
    for _, v in ipairs(x) do squares = squares + (v * v) end
    return math.sqrt((1 / #x) * squares)
end

--- Calculates the generalized mean (to a specified power) of a set of values.
-- @tparam number p power
-- @tparam tuple ... an array of numbers
-- @treturn number the generalized mean
function Math.generalized_mean(p, ...)
    local x = tuple(...)
    local sump = 0
    for _, v in ipairs(x) do sump = sump + (v ^ p) end
    return ((1 / #x) * sump) ^ (1 / p)
end

--- Calculates the weighted mean of a set of values.
-- @tparam array x an array of numbers
-- @tparam array w an array of number weights for each value
-- @treturn number the weighted mean
function Math.weighted_mean(x, w)
    local sump = 0
    for i, v in ipairs(x) do sump = sump + (v * w[i]) end
    return sump / Math.sum(w)
end

--- Calculates the midrange mean of a set of values.
-- @tparam array x an array of numbers
-- @treturn number the midrange mean
function Math.midrange_mean(...)
    local x = tuple(...)
    return 0.5 * (math_min(unpack(x)) + math_max(unpack(x)))
end

--- Calculates the energetic mean of a set of values.
-- @tparam array x an array of numbers
-- @treturn number the energetic mean
function Math.energetic_mean(...)
    local x = tuple(...)
    local s = 0
    for _, v in ipairs(x) do s = s + (10 ^ (v / 10)) end
    return 10 * Math.log10((1 / #x) * s)
end

--- Returns the number x clamped between the numbers min and max.
-- @tparam number x
-- @tparam[opt=0] number min
-- @tparam[opt=1] number max
-- @treturn number clamped between min and max
function Math.clamp(x, min, max)
    min, max = min or 0, max or 1
    return x < min and min or (x > max and max or x)
end

--- Linear interpolation or 2 numbers.
-- @tparam number a
-- @tparam number b
-- @tparam float amount
-- @treturn number
function Math.lerp(a, b, amount)
    return a + (b - a) * Math.clamp(amount, 0, 1)
end

--- Smooth.
-- @tparam number a
-- @tparam number b
-- @tparam float amount
-- @treturn number
function Math.smooth(a, b, amount)
    local t = Math.clamp(amount, 0, 1)
    local m = t * t * (3 - 2 * t)
    return a + (b - a) * m
end

--- Approximately the same
-- @tparam number a
-- @tparam number b
-- @treturn boolean
function Math.approximately(a, b)
    return math_abs(b - a) < math_max(1e-6 * math_max(math_abs(a), math_abs(b)), 1.121039e-44)
end

--- Is x a number.
-- @tparam number x
-- @treturn boolean
function Math.is_number(x)
    return x == x and x ~= math_huge
end

--- Is x an integer.
-- @tparam number x
-- @treturn boolean
function Math.is_integer(x)
    return x == math_ceil(x)
end

--- Is x unsigned.
-- @tparam number x
-- @treturn boolean
function Math.is_unsigned(x)
    return x >= 0
end

return Math
