EVENTS = {
    "CALL_FUNCTION",

    -- "MAIN_INIT",
    "MAIN_FIRST_TICK",
    "FORCE_DELETE",
    "SPAWN_SET",
    "REQUEST",
    "RESPONSE",
    "UPDATE_COMPARISON",
    "UPDATE_STATE",
    "UPDATE_HEALTH",

    "ENTER_GARAGE",
    "UPDATE_GARAGE",
    "DELETE_GARAGE_VEH",
    "DELETE_CARLIFT_VEH",
    "SET_GARAGE_VEH",
    "SET_SPECIAL_VEH",
    "CHOOSE_VEHICLE",
    "WAIT_EVENT",
    "CINEMATIC",
    "PICK_UP_MONEY",

    "RACE_START",
    "GAMEMODE_INIT",
    "COUNTDOWN_OVER",
    "STATS_FILLED",
    "GAME_OVER",
    "SPAWN_VEHICLES_START",

    "KILL_PLAYER",
    "KILL_AI",
    "VEHICLE_SCORE",
    "VEHICLE_DAMAGE",
    "VEHICLE_EXPLODED",
    "VEHICLE_DESTROYED",
    "VEHICLE_DISQUALIFIED",
    "VEHICLE_ACTIVATED_TRAP",

    "LAP_FINISHED",
    "AI_LAP_FINISHED",

    "SAVE",

    "UI_EFFECT",
    "UI_SWITCH_CLASS",
    "UI_ALERT_SHOW",
    "UI_ALERT_HIDE",
    "UI_COMPUTER_HOME_BTN",
    "SHOW_MODAL",
    -- "MODALS_CLOSED",

    "SET_COOLDOWN_RESPAWN",
    "GADGET_RAM",
    "ACTION_RESPONSE_INFO",
    "ACTION_REQUEST_START",
    "ACTION_REQUEST_STOP",

    "AI_CHEAT_SKIP",
    "AI_CANCEL_CHEAT_SKIP",
    -- "DRIVE_DRIVER_BANGER",
    "BANGER_RESPAWN",
    "AI_RUBBERBAND",

    "PLAYER_HUNT",
    "SECOND_PHASE",

    "TUTORIAL_INIT",
    "TUTORIAL_RESET",
    "PHONE_MESSAGE_RECEIVED",
    "PHONE_MESSAGE_READ",
}

-- Some safety measures
local chrs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
local prefix_n = 10
local rand_prefix = HasKey("savegame.mod.event_prefix") and GetString("savegame.mod.event_prefix")
if not rand_prefix or #rand_prefix < prefix_n then
    local new_rand_prefix = ""
    for i = 1, prefix_n do
        local r = math.random(#chrs)
        new_rand_prefix = new_rand_prefix .. chrs:sub(r, r)
    end
    rand_prefix = new_rand_prefix
    SetString("savegame.mod.event_prefix", new_rand_prefix)
end

for i = 1, #EVENTS do
    EVENTS[rand_prefix .. EVENTS[i]], EVENTS[i] = EVENTS[i], nil
end
setmetatable(EVENTS, { __index = function(_, k) return rand_prefix .. k end })

local function GetFuncFromString(_str)
    local t, func = _str:match '([^.:]*)[.:]?(.*)'
    if func == "" then
        t, func = nil, t
    end
    return t, func
end

string.unpack_gmatch = string.unpack_gmatch or function(str, pattern, i, j, c)
    i = i or 1
    j = j or math.huge
    c = c or 0
    if c >= j then
        return
    end
    for n in str:gmatch(pattern) do
        c = c + 1
        if c == i then
            local endpos = select(2, str:find(n, 0, true))
            if endpos then
                return n, str:sub(endpos + 2):unpack_gmatch(pattern, i+1, j, c)
            else
                return n
            end
        end
    end
end

function RegisterListenerToFiltered(event, fn_name, whitelisted_fns_map)
    _G[event .. "_" .. fn_name] = function(data)
        return CallWithArgs(data, whitelisted_fns_map)
    end
    RegisterListenerTo(event, event .. "_" .. fn_name)
end

function UnregisterListenerFiltered(event, fn_name)
    UnregisterListener(event, event .. "_" .. fn_name)
end

function CallWithArgs(str, whitelisted_fns_map, recursive)
    if not recursive and str:find(",") then
        for call in str:gmatch("[^,]+") do
            CallWithArgs(call, whitelisted_fns_map, true)
        end
        return
    end

    local tbl, func = GetFuncFromString(str:match("[^;]+"))
    if tbl and  _G[tbl] and _G[tbl][func] then
        if not (whitelisted_fns_map[tbl] and whitelisted_fns_map[tbl][func]) then return end
        return _G[tbl][func](_G[tbl], str:unpack_gmatch("[^;]+", 2))
    elseif _G[func] then
        if not whitelisted_fns_map[func] then return end
        return _G[func](str:unpack_gmatch("[^;]+", 2))
    else
        return
    end
end
