maps = {}
gametypes = {}

AddEventHandler('getResourceInitFuncs', function(isPreParse, add)
    if not isPreParse then
        add('map', function(file)
            addMap(file, GetInvokingResource())
        end)

        add('resource_type', function(type)
            return function(params)
                local resourceName = GetInvokingResource()

                if type == 'map' then
                    maps[resourceName] = params
                elseif type == 'gametype' then
                    gametypes[resourceName] = params
                end
            end
        end)
    end
end)

mapFiles = {}

function addMap(file, owningResource)
    if not mapFiles[owningResource] then
        mapFiles[owningResource] = {}
    end

    table.insert(mapFiles[owningResource], file)
end

AddEventHandler('onClientResourceStart', function(res)
    -- parse metadata for this resource

    -- map files
    local num = GetNumResourceMetadata(res, 'map')

    if num then
        for i = 0, num-1 do
            local file = GetResourceMetadata(res, 'map', i)

            if file then
                addMap(file, res)
            end
        end
    end

    -- resource type data
    local type = GetResourceMetadata(res, 'resource_type', 0)

    if type then
        Citizen.Trace("type " .. res .. " " .. type .. "\n")

        local extraData = GetResourceMetadata(res, 'resource_type_extra', 0)

        if extraData then
            extraData = json.decode(extraData)
        else
            extraData = {}
        end

        if type == 'map' then
            maps[res] = extraData
        elseif type == 'gametype' then
            gametypes[res] = extraData
        end
    end

    -- handle starting
    if mapFiles[res] then
        for _, file in ipairs(mapFiles[res]) do
            parseMap(file, res)
        end
    end

    -- defer this to the next game tick to work around a lack of dependencies
    Citizen.CreateThread(function()
        Citizen.Wait(15)

        if maps[res] then
            TriggerEvent('onClientMapStart', res)
        elseif gametypes[res] then
            TriggerEvent('onClientGameTypeStart', res)
        end
    end)
end)

AddEventHandler('onClientResourceStop', function(res)
    if maps[res] then
        TriggerEvent('onClientMapStop', res)
    elseif gametypes[res] then
        TriggerEvent('onClientGameTypeStop', res)
    end

    if undoCallbacks[res] then
        for _, cb in ipairs(undoCallbacks[res]) do
            cb()
        end

        undoCallbacks[res] = nil
        mapFiles[res] = nil
    end
end)

undoCallbacks = {}

function parseMap(file, owningResource)
    if not undoCallbacks[owningResource] then
        undoCallbacks[owningResource] = {}
    end

    local env = {
        math = math, pairs = pairs, ipairs = ipairs, next = next, tonumber = tonumber, tostring = tostring,
        type = type, table = table, string = string, _G = env
    }

    TriggerEvent('getMapDirectives', function(key, cb, undocb)
        env[key] = function(...)
            local state = {}

            state.add = function(k, v)
                state[k] = v
            end

            local result = cb(state, ...)
            local args = table.pack(...)

            table.insert(undoCallbacks[owningResource], function()
                undocb(state)
            end)

            return result
        end
    end)

    local mt = {
        __index = function(t, k)
            if rawget(t, k) ~= nil then return rawget(t, k) end

            -- as we're not going to return nothing here (to allow unknown directives to be ignored)
            local f = function()
                return f
            end

            return function() return f end
        end
    }

    setmetatable(env, mt)
    
    local fileData = LoadResourceFile(owningResource, file)
    local mapFunction, err = load(fileData, file, 't', env)

    if not mapFunction then
        Citizen.Trace("Couldn't load map " .. file .. ": " .. err .. " (type of fileData: " .. type(fileData) .. ")\n")
        return
    end

    mapFunction()
end

AddEventHandler('getMapDirectives', function(add)
    add('vehicle_generator', function(state, name)
        return function(opts)
            local x, y, z, heading
            local color1, color2

            if opts.x then
                x = opts.x
                y = opts.y
                z = opts.z
            else
                x = opts[1]
                y = opts[2]
                z = opts[3]
            end

            heading = opts.heading or 1.0
            color1 = opts.color1 or -1
            color2 = opts.color2 or -1

            local hash = GetHashKey(name)
            RequestModel(hash)

            LoadAllObjectsNow()

            local carGen = CreateScriptVehicleGenerator(x, y, z, heading, 5.0, 3.0, hash, color1, color2, -1, -1, true, false, false, true, true, -1)
            SetScriptVehicleGenerator(carGen, true)
            SetAllVehicleGeneratorsActive(true)

            state.add('cargen', carGen)
        end
    end, function(state, arg)
        Citizen.Trace("deleting car gen " .. tostring(state.cargen) .. "\n")

        DeleteScriptVehicleGenerator(state.cargen)
    end)
end)
