
---@class TableNumber
ExtraClasses.TableNumber = {}
ExtraClasses.TableNumber.__index = ExtraClasses.TableNumber

-- return sum of all values
---@return number
function ExtraClasses.TableNumber:sum()
	local t = 0
	for k, v in pairs(self) do t = t + v end
	return t
end
-- return number of values
---@return number
function ExtraClasses.TableNumber:count()
	local c = 0
	for k, v in pairs(self) do c = c + 1 end
	return c
end
-- return most negative value
---@return number, any
function ExtraClasses.TableNumber:min()
	local t
	local n
	for k, v in pairs(self) do
		if v <= (t or v) then
			t = v
			n = k
		end
	end
	return t, n
end
-- return most positive value
---@return number, any
function ExtraClasses.TableNumber:max()
	local t
	local n
	for k, v in pairs(self) do
		if v >= (t or v) then
			t = v
			n = k
		end
	end
	return t, n
end
-- round down
---@return TableNumber
function ExtraClasses.TableNumber:floor()
	local ret = ExtraClasses.TableNumber.new(self)
	for k, v in pairs(ret) do
		ret[k] = math.floor(v)
	end
	return ret
end
-- round up
---@return TableNumber
function ExtraClasses.TableNumber:ceil()
	local ret = ExtraClasses.TableNumber.new(self)
	for k, v in pairs(ret) do
		ret[k] = math.ceil(v)
	end
	return ret
end
-- round nearest
---@return TableNumber
function ExtraClasses.TableNumber:round()
	local ret = ExtraClasses.TableNumber.new(self)
	for k, v in pairs(ret) do
		ret[k] = math.round(v)
	end
	return ret
end
-- return the squared 'distance' from this set to another set `tn`
---@param tn TableNumber|table
---@return number
function ExtraClasses.TableNumber:square_distance_to(tn)
	local s = 0
	-- get dist from this set's values
	for k, v in pairs(self) do
		s = s + ((tn[k] or 0) - v) ^ 2
	end
	-- get dist from any values this set is missing to the target
	for k, v in pairs(tn) do
		if not self[k] then
			s = s + (v - 0) ^ 2
		end
	end
	return s
end
-- return real distance (square rooted) from this set to set `tn`
---@param tn TableNumber|table
---@return number
function ExtraClasses.TableNumber:distance_to(tn)
	return math.sqrt(self:square_distance_to(tn))
end

function ExtraClasses.TableNumber.__sub(a, b)
	local ret = ExtraClasses.TableNumber.new(a)
	for k, v in pairs(a) do ret[k] = v - (b[k] or 0) end
	for k, v in pairs(b) do if not ret[k] then ret[k] = -v end end
	return ret
end
function ExtraClasses.TableNumber.__add(a, b)
	local ret = ExtraClasses.TableNumber.new(a)
	for k, v in pairs(a) do ret[k] = v + (b[k] or 0) end
	for k, v in pairs(b) do if not ret[k] then ret[k] = v end end
	return ret
end
function ExtraClasses.TableNumber.__mul(a, b)
	local ret
	if type(a) == "number" or type(b) == "number" then
		if type(a) == "number" then a,b = b,a end
		ret = ExtraClasses.TableNumber.new(a)
		for k, v in pairs(a) do ret[k] = v * (b) end
	else
		ret = ExtraClasses.TableNumber.new(a)
		for k, v in pairs(a) do ret[k] = v * (b[k] or 1) end
		for k, v in pairs(b) do if not ret[k] then ret[k] = v end end
	end
	return ret
end
function ExtraClasses.TableNumber.__div(a, b)
	local ret = ExtraClasses.TableNumber.new(a)
	if type(b) == "number" then
		for k, v in pairs(a) do ret[k] = v / (b or 1) end
	else
		for k, v in pairs(a) do ret[k] = v / (b[k] or 1) end
		for k, v in pairs(b) do if not ret[k] then ret[k] = 1/v end end
	end
	return ret
end
function ExtraClasses.TableNumber.__unm(a)
	local ret = ExtraClasses.TableNumber.new(a)
	for k, v in pairs(a) do ret[k] = -v end
	return ret
end
function ExtraClasses.TableNumber.__mod(a, b)
	local ret = ExtraClasses.TableNumber.new(a)
	if type(b) == "number" then
		for k, v in pairs(a) do ret[k] = v % (b or (v+1)) end
	else
		for k, v in pairs(a) do ret[k] = v % (b[k] or (v+1)) end
	end
	return ret
end
function ExtraClasses.TableNumber.__pow(a, b)
	local ret = {}
	for k, v in pairs(a) do ret[k] = v ^ (b) end
	return ExtraClasses.TableNumber.new(ret)
end
function ExtraClasses.TableNumber.__eq(a, b)
	for k, v in pairs(a) do
		if v ~= b[k] then return false end
	end
	return true
end

---@return TableNumber
---@param t table | nil -- must only contain numbers
ExtraClasses.TableNumber.new = function(t)
	local ret = setmetatable({}, ExtraClasses.TableNumber)
	for k, v in pairs(t or {}) do
		ret[k] = v
	end
	return ret
end

rawset(ExtraClasses.TableNumber, "__index", ExtraClasses.TableNumber)
rawset(ExtraClasses.TableNumber, "__call", function(self, ...)
	return ExtraClasses.TableNumber.new(...)
end)
