if (SERVER and not MENU_DLL) then
    AddCSLuaFile()
end

--[[
    local i_fn       = 1
    local i_id       = 2
    local i_next     = 3
    local i_real_fn  = 4
    local i_last     = 5
    local i_priority = 6
]]

local event_table = {}

local function GetTable()
    local out = {}
    for event, hooklist in pairs(event_table) do
        out[event] = {}
        ::startloop::
            out[event][hooklist[2 --[[i_id]]]] = hooklist[4 --[[i_real_fn]]]
            hooklist = hooklist[3 --[[i_next]]]
        if (hooklist) then
            goto startloop
        end
    end
    return out
end

local function Remove(event, name)
    if (not name) then
        return
    end

    local hook = event_table[event]
    if (not hook) then
        return
    end

    local found = false

    local event_start = hook
    ::startloop::
        if (hook[2 --[[i_id]]] == name) then

            if (hook == event_start) then
                event_table[event] = hook[3 --[[i_next]]]
            end

            local last, next = hook[5 --[[i_last]]], hook[3 --[[i_next]]]
            if (last) then
                last[3 --[[i_next]]] = hook[3 --[[i_next]]]
            end
            if (next) then
                next[5 --[[i_last]]] = hook[5 --[[i_last]]]
            end
            found = true
            goto breakloop
        end
        hook = hook[3 --[[i_next]]]
    if (hook) then
        goto startloop
    end
    ::breakloop::

    if (not found) then
        return
    end

    local start = event_table[event]
    if (not start) then
        return
    end
end

local function Add(event, name, fn, priority)
    assert(event ~= nil, "bad argument #1 to 'Add' (value expected)")
    if (not name or not fn) then
        return
    end
    local real_fn = fn
    if (type(name) ~= "string") then
        fn = function(...)
            if (IsValid(name)) then
                local a, b, c, d, e, f = real_fn(name, ...)
                if (a ~= nil) then
                    return a, b, c, d, e, f
                end
                return
            end

            Remove(event, name)
        end
    end

    local hook = event_table[event]

    local new_hook
    local found = false

    if (hook) then
        ::startloop::
            if (hook[2 --[[i_id]]] == name) then
                new_hook = hook
                found = true
                goto breakloop
            end
            hook = hook[3 --[[i_next]]]
        if (hook) then
            goto startloop
        end
    end
    ::breakloop::

    if (not priority) then
        priority = 0
    end

    if (found) then
        new_hook[1 --[[i_fn]]     ] = fn
        new_hook[4 --[[i_real_fn]]] = real_fn
        if (new_hook[6 --[[i_priority]]] == priority) then
            return
        end

        -- WARNING: UNDEFINED BEHAVIOR WARNING doing this inside another hook with earlier priority than the hook running WILL run the hook again
        Remove(event, name)
        new_hook[6 --[[i_priority]]] = priority
        new_hook[3 --[[i_next]]]     = nil
        new_hook[5 --[[i_last]]]     = nil
    else
        new_hook = {
            -- i_fn
            fn,
            -- i_id
            name,
            -- i_next
            nil,
            -- i_real_fn
            real_fn,
            -- i_last
            nil, 
            -- i_priority
            priority
        }
    end

    -- find link in hook list to add to

    hook = event_table[event]
    local event_start = hook

    if (hook) then
        local lasthook
        ::startloop2::
            if (hook[6 --[[i_priority]]] <= priority) then
                if (lasthook) then
                    lasthook[3 --[[i_next]]] = new_hook
                end
                hook[5 --[[i_last]]] = new_hook
                new_hook[3 --[[i_next]]] = hook
                new_hook[5 --[[i_last]]] = lasthook
                if (event_start ~= hook) then
                    return
                end
                goto breakloop2
            end
            lasthook = hook
            hook = hook[3 --[[i_next]]]
        if (hook) then
            goto startloop2
        else
            -- NOT SUITABLE IN TABLE, update at end
            lasthook[3 --[[i_next]]] = new_hook
            return
        end
    end
    ::breakloop2::

    event_table[event] = new_hook
end

local function Call(event, gm, ...)
    -- TODO: hooks
    local hook = event_table[event]

    if (hook) then
        ::startloop::
            local a, b, c, d, e, f = hook[1 --[[i_fn]]](...)
            if (a ~= nil) then
                return a, b, c, d, e, f
            end

            hook = hook[3 --[[i_next]]]
        if (hook) then
            goto startloop
        end
    end

    if (gm) then
        local fn = gm[event]
        if (fn) then
            return fn(gm, ...)
        end
    end
end

local gmod = gmod

local function Run(event, ...)
    return Call(event, gmod and gmod.GetGamemode() or nil, ...)
end

return {
    Remove = Remove,
    GetTable = GetTable,
    Add = Add,
    Call = Call,
    Run = Run,
    Priority = {
        NO_RETURN = math.huge -- not enforced, just should be followed
    }
}