MudExtraParticlePresets = {}

MudExtraParticlePresets.PRESETS = {
    { id = 1, key = "mud",       file = "particleSystems/mud.i3d",       l10n = "mudsys_extraParticlePreset_standard", fallback = "Standard" },
    { id = 2, key = "mudField",  file = "particleSystems/mudField.i3d",  l10n = "mudsys_extraParticlePreset_dark",     fallback = "Dark standard" },
    { id = 3, key = "mudField2", file = "particleSystems/mudField2.i3d", l10n = "mudsys_extraParticlePreset_small",    fallback = "Small particles" },
    { id = 4, key = "mudField3", file = "particleSystems/mudField3.i3d", l10n = "mudsys_extraParticlePreset_medium",        fallback = "Medium particles" },
    { id = 5, key = "mudField4", file = "particleSystems/mudField4.i3d", l10n = "mudsys_extraParticlePreset_mediumHeavy",   fallback = "Medium heavy particles" }
}

local function getTextSafe(key, fallback)
    if g_i18n ~= nil and key ~= nil and g_i18n.hasText ~= nil and g_i18n:hasText(key) then
        return g_i18n:getText(key)
    end

    return fallback or key or ""
end

local function safeDeleteNode(node)
    if node == nil or node == 0 then
        return
    end

    if type(entityExists) == "function" and not entityExists(node) then
        return
    end

    if type(mpSafeDelete) == "function" then
        mpSafeDelete(node)
    elseif type(delete) == "function" then
        delete(node)
    end
end

-- Particle geometry may keep its native emitter-shape pointer until the end
-- of the current render/update frame. Deleting both nodes immediately while a
-- vehicle is being sold or reloaded can therefore leave a one-frame dangling
-- pointer inside the engine. Keep detached emitter shapes alive briefly after
-- their particle system has been stopped and deleted.
MudExtraParticlePresets.retiredEmitterShapes = {}
MudExtraParticlePresets.retiredEmitterShapeSet = {}

function MudExtraParticlePresets.retireEmitterShape(node, delayMs)
    if node == nil or node == 0
        or (type(entityExists) == "function" and not entityExists(node)) then
        return
    end

    if MudExtraParticlePresets.retiredEmitterShapeSet[node] == true then
        return
    end

    local root = type(getRootNode) == "function" and getRootNode() or nil
    if root ~= nil and root ~= 0 and type(link) == "function" then
        link(root, node)
    end

    MudExtraParticlePresets.retiredEmitterShapeSet[node] = true
    MudExtraParticlePresets.retiredEmitterShapes[#MudExtraParticlePresets.retiredEmitterShapes + 1] = {
        node = node,
        remainingMs = math.max(250, tonumber(delayMs) or 1500),
        remainingFrames = 3
    }
end

function MudExtraParticlePresets.updateRetiredEmitterShapes(dt)
    local retired = MudExtraParticlePresets.retiredEmitterShapes
    for i = #retired, 1, -1 do
        local item = retired[i]
        item.remainingMs = (item.remainingMs or 0) - math.max(0, tonumber(dt) or 0)
        item.remainingFrames = (item.remainingFrames or 0) - 1
        local node = item.node
        local exists = node ~= nil and node ~= 0
            and (type(entityExists) ~= "function" or entityExists(node))

        if not exists or (item.remainingMs <= 0 and item.remainingFrames <= 0) then
            MudExtraParticlePresets.retiredEmitterShapeSet[node] = nil
            if exists then
                safeDeleteNode(node)
            end
            table.remove(retired, i)
        end
    end
end

function MudExtraParticlePresets.flushRetiredEmitterShapes()
    for _, item in ipairs(MudExtraParticlePresets.retiredEmitterShapes) do
        safeDeleteNode(item.node)
    end
    MudExtraParticlePresets.retiredEmitterShapes = {}
    MudExtraParticlePresets.retiredEmitterShapeSet = {}
end

function MudExtraParticlePresets.getPresetById(id)
    id = math.floor(tonumber(id) or 1)

    for _, preset in ipairs(MudExtraParticlePresets.PRESETS) do
        if preset.id == id then
            return preset
        end
    end

    return MudExtraParticlePresets.PRESETS[1]
end

function MudExtraParticlePresets.getSettingValues()
    local values = {}

    for _, preset in ipairs(MudExtraParticlePresets.PRESETS) do
        values[#values + 1] = preset.id
    end

    return values
end

function MudExtraParticlePresets.getSettingStrings()
    local strings = {}

    for _, preset in ipairs(MudExtraParticlePresets.PRESETS) do
        strings[#strings + 1] = getTextSafe(preset.l10n, preset.fallback)
    end

    return strings
end

function MudExtraParticlePresets.clearCache(target)
    if target == nil or target._extraPS == nil then
        return
    end

    local referenceShapeRetired = false
    if target._extraPS.referencePS ~= nil then
        local particleSystem = target._extraPS.referencePS
        if ParticleUtil ~= nil and ParticleUtil.setEmittingState ~= nil then
            ParticleUtil.setEmittingState(particleSystem, false)
        end
        if ParticleUtil ~= nil and ParticleUtil.resetNumOfEmittedParticles ~= nil then
            ParticleUtil.resetNumOfEmittedParticles(particleSystem)
        end

        local sourceEmitterShape = particleSystem.emitterShape
        if sourceEmitterShape ~= nil and sourceEmitterShape ~= 0
            and sourceEmitterShape ~= particleSystem.shape then
            MudExtraParticlePresets.retireEmitterShape(sourceEmitterShape)
            referenceShapeRetired = sourceEmitterShape == target._extraPS.referenceShape
        end

        if ParticleUtil ~= nil and ParticleUtil.deleteParticleSystem ~= nil then
            ParticleUtil.deleteParticleSystem(particleSystem)
        else
            safeDeleteNode(particleSystem.shape)
        end
        particleSystem.isValid = false
        particleSystem.isEmitting = false
        particleSystem.geometry = nil
        particleSystem.emitterShape = nil
        target._extraPS.referencePS = nil
    end

    if target._extraPS.referenceShape ~= nil then
        if not referenceShapeRetired then
            safeDeleteNode(target._extraPS.referenceShape)
        end
        target._extraPS.referenceShape = nil
    end

    target._extraPS.sourcePsRoot = nil
    target._extraPS.sourceShapeRoot = nil
    target._extraPS.sourcePsNode = nil
    target._extraPS = nil
end

function MudExtraParticlePresets.applyPreset(target, presetId)
    if target == nil then
        return nil
    end

    local preset = MudExtraParticlePresets.getPresetById(presetId or target.extraParticlesPreset)
    target.extraParticlesPreset = preset.id
    target.extraParticlesI3D = preset.file

    return preset
end

function MudExtraParticlePresets.applyMudPhysics(target)
    return MudExtraParticlePresets.applyPreset(target or rawget(_G, "MudPhysics"))
end

function MudExtraParticlePresets.applyFieldGroundMudPhysics(target)
    return MudExtraParticlePresets.applyPreset(target or rawget(_G, "FieldGroundMudPhysics"))
end
