-- enum.lua
--[[提供枚举机制
示例:
    local enum = require(enum)
    用法1：
    local TEST1 = enum("TEST1", 0, "ONE", "THREE", "TWO")
    print(TEST1.TWO)
    用法2：
    local TEST2 = enum("TEST2", 1, "ONE", "THREE", "TWO")
    TEST2.FOUR = TEST2()
    print(TEST2.TWO, TEST2.FOUR)
    用法3：
    local TEST3 = enum("TEST3", 2)
    TEST3("ONE")
    TEST3("TWO")
    TEST3("FOUR", 4)
    local five = TEST3("FIVE")
    print(TEST3.TWO, TEST3.FOUR, TEST3.FIVE, five)
--]]

local skynet = require "skynet"

local ipairs = ipairs
local rawget = rawget
local rawset = rawset
local tconcat = table.concat
local sformat = string.format
local dgetinfo = debug.getinfo
local setmetatable = setmetatable

local enum_tpls = {}

local function enum_tostring(eo)
    return sformat("enum:%s(max:%s, elems: {%s})", eo.__name, eo.__vmax, tconcat(eo.__vlist, ","))
end

local function enum_new(emobj, field, value)
    value = value or emobj.__vmax
    if field then
        emobj.__vlist[field] = value
        if value >= emobj.__vmax then
            emobj.__vmax = value + 1
        end
    end
    return value
end

local function enum_index(emobj, field)
    return emobj.__vlist[field]
end

local function enum_newindex(emobj, field, value)
    local vlist = emobj.__vlist
    if vlist[field] then
        skynet.error("enum %s redefine field %s!", emobj.__name, field)
    end
    vlist[field] = value
    if value >= emobj.__vmax then
        emobj.__vmax = value + 1
    end
end

local enumMT = {
    __call = enum_new,
    __index = enum_index,
    __newindex = enum_newindex,
    __tostring = enum_tostring,
}

local function enum_init(emobj, base, ...)
    emobj.__vlist = {}
    emobj.__vmax = base
    for _, field in ipairs({...}) do
        emobj.__vlist[field] = emobj.__vmax
        emobj.__vmax = emobj.__vmax + 1
    end
end

local function enum_list(ems)
    local elist = rawget(ems, "__list")
    if not elist then
        elist = {}
        rawset(ems, "__list", elist)
    end
    return elist
end

local function new(ems, name, base, ...)
    local info = dgetinfo(2, "S")
    local moudle = info.short_src
    local lists = enum_list(ems)
    local eobj = lists[name]
    if eobj then
        if eobj.__moudle ~= moudle then
            skynet.error("enum %s redefined! moudle:%s", name, moudle)
        end
    else
        eobj = {
            __name = name,
            __moudle = moudle,
        }
    end
    enum_init(eobj, base, ...)
    setmetatable(eobj, enumMT)
    lists[name] = eobj
    return eobj
end

local function index(ems, field)
    local lists = enum_list(ems)
    return lists[field]
end

local MT = {
    __call = new,
    __index = index,
}
setmetatable(enum_tpls, MT)

function Enum(name, base, ...)
    if base then
        return enum_tpls(name, base, ...)
    end
    -- 没有传base参数表示查询
    return enum_tpls[name]
end
