-- utility functions, in alpha order

local util = {}

-- Allow MyClass(ctor args...) equivalent to MyClass:new(ctor args...)
-- Usage:
-- local MyClass = {}
-- function MyClass:new(...)
--     ...
-- end
-- ...
-- util.classctor(MyClass)
-- or if your constructor is named something other than MyClass:new(), e.g.
-- MyClass:construct():
-- util.classctor(MyClass, MyClass.construct)
-- return MyClass
function util.classctor(class, ctor)
    -- set class's __call metamethod to the specified constructor function
    -- (class.new if not specified)
    util.setmetamethods{class, __call=(ctor or class.new)}
end

-- check if array-like table contains certain value
function util.contains(t, v)
    return table.find(t, v) ~= nil
end

-- reliable count of the number of entries in table t
-- (since #t is unreliable)
function util.count(t)
    local count = 0
    for _ in pairs(t) do
        count += 1
    end
    return count
end

-- cheap test whether table t is empty
function util.empty(t)
    return not next(t)
end

-- recursive table equality
function util.equal(t1, t2)
    if not (type(t1) == 'table' and type(t2) == 'table') then
        return t1 == t2
    end
    -- both t1 and t2 are tables: get modifiable copy of t2
    local temp = table.clone(t2)
    for k, v in pairs(t1) do
        -- if any key in t1 doesn't have same value in t2, not equal
        if not util.equal(v, temp[k]) then
            return false
        end
        -- temp[k] == t1[k], delete temp[k]
        temp[k] = nil
    end
    -- All keys in t1 have equal values in t2; t2 == t1 if there are no extra keys in t2
    return util.empty(temp)
end

-- Find or create the metatable for a specified table (a new empty table if
-- omitted), and to that metatable assign the specified keys.
-- Setting multiple keys at once is more efficient than a function to set only
-- one at a time, e.g. setametamethod().
-- t = util.setmetamethods{__index=readfunc, __len=lenfunc}
-- returns a new table with specified metamethods __index, __len
-- util.setmetamethods{t, __call=action}
-- finds or creates the metatable for existing table t and sets __call
-- util.setmetamethods{table=t, __call=action}
-- same as util.setmetamethods{t, __call=action}
function util.setmetamethods(specs)
    -- first determine the target table
    assert(not (specs.table and specs[1]),
           "Pass setmetamethods table either as positional or table=, not both")
    local t = specs.table or specs[1] or {}
    -- remove both ways of specifying table, leaving only the metamethods
    specs.table = nil
    specs[1] = nil
    local mt = getmetatable(t)
    if not mt then
        -- t doesn't already have a metatable: just set specs
        setmetatable(t, specs)
    else
        -- t already has a metatable: copy specs into it
        local key, value
        for key, value in pairs(specs) do
            mt[key] = value
        end
    end
    -- having set or enriched t's metatable, return t
    return t
end

-- On the passed module (i.e. table), set an __index metamethod such that
-- referencing module.submodule lazily requires(path/submodule).
-- The loaded submodule is cached in the module table so it need not be passed
-- to require() again.
-- 'path', like any require() string, can be relative to LuaRequirePath.
-- Returns the enriched module, permitting e.g.
-- mymod = util.submoduledir({}, 'mymod')
function util.submoduledir(module, path)
    return util.setmetamethods{
        module,
        __index=function(t, key)
            local mod = require(`{path}/{key}`)
            -- cache the submodule
            t[key] = mod
            return mod
        end
    }
end

return util