--[[

Game:    Farming Simulator 19
Script:  ExtendedExhaust
Author:  ThundR
Created: 12/15/2020
Version: 1.0.0.1

Game:    Farming Simulator 22
Script:  ExtendedExhaust
Author:  [AGDM] Haubi0105
Created: 08/08/2022
Version: 1.1.0.0

original script by modelleicher
www.schwabenmodding.bplaced.net

Notes:
    - Adds exhaust features
    - Adds dynamic exhaust particleSystem effects

ChangeLog FS19
	v1.0.0.1
    - Fixed issue with objectChange not hiding smoke particles properly
ChangeLog FS22
	v1.0.0.0
    - First Version for FS22
	v1.1.0.0
    - small Fix for normal Exhaust

--]]

-- luacheck: ignore 111 112 113 212 631

local g_modName = g_currentModName
local g_specName = "spec_"..g_modName..".extendedExhaust"

ExtendedExhaust = {}
ExtendedExhaust.debugPriority = 0

local function printDebug(debugMessage, priority, addString)
	if ExtendedExhaust.debugPriority >= priority then
		local prefix = "";

		if addString then
			prefix = "::DEBUG:: from the ExtendedExhaust.lua: ";
		end;

		setFileLogPrefixTimestamp(false);
		print(prefix .. tostring(debugMessage));
		setFileLogPrefixTimestamp(true);
	end;
end;

function ExtendedExhaust.Logging_table(x)
	t = x or "nothing to print"
    setFileLogPrefixTimestamp(false)

    local print_r_cache = {}
	if ExtendedExhaust.debugPriority == 2 then

		local function sub_print_r(t, indent)
			if (print_r_cache[tostring(t)]) then
				print(indent .. "*" .. tostring(t))
			else
				print_r_cache[tostring(t)] = true
				if (type(t) == "table") then
					for pos, val in pairs(t) do
						pos = tostring(pos)

						if (type(val) == "table") then
							print(indent .. "[" .. pos .. "] => " .. tostring(t) .. " {")
							sub_print_r(val, indent .. string.rep(" ", string.len(pos) + 8))
							print(indent .. string.rep(" ", string.len(pos) + 6) .. "}")
						elseif (type(val) == "string") then
							print(indent .. "[" .. pos .. '] => "' .. val .. '"')
						else
							print(indent .. "[" .. pos .. "] => " .. tostring(val))
						end
					end
				else
					print(indent .. tostring(t))
				end
			end
		end

		if (type(t) == "table") then
			print(tostring(t) .. " {")
			sub_print_r(t, "  ")
			print("}")
		else
			sub_print_r(t, "  ")
		end

		print()
	end

    setFileLogPrefixTimestamp(true)
end



-- <<[Initialization]>> ---------------------------------------------------------------------------

-- <<prerequisitesPresent>>
function ExtendedExhaust.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Motorized, specializations)
end

-- <<registerOverwrittenFunctions>>
function ExtendedExhaust.registerOverwrittenFunctions(vehicleType)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadExhaustEffects", ExtendedExhaust.loadExhaustEffects)
end

-- <<registerEventListeners>>
function ExtendedExhaust.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onStartMotor", ExtendedExhaust)
    SpecializationUtil.registerEventListener(vehicleType, "onStopMotor", ExtendedExhaust)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", ExtendedExhaust)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", ExtendedExhaust)
end

-- <<[Event Functions]>> --------------------------------------------------------------------------

-- <<onUpdateTick>>
function ExtendedExhaust:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self[g_specName]

    if self.isClient then
        if self:getIsMotorStarted() then
            if spec.particleSystems ~= nil then
                local motorIsStarting = false
                local motorLoad = self:getMotorLoadPercentage()
                local motorRpm = self:getMotorRpmPercentage()

                if spec.psMotorStartTime > 0 then
                    spec.psMotorStartTime = math.max(0, spec.psMotorStartTime - dt)
                    motorIsStarting = true
                end
				printDebug("getIsMotorStarted 'true'",2,true)
                if spec.particleSystems ~= nil then
					printDebug("particleSystems '~= nil'",2,true)
					-- ExtendedExhaust.Logging_table(spec.particleSystems)
                    for _, ps in ipairs(spec.particleSystems) do
						if getVisibility(ps.linkNode) and not ps.visible then
							local parent = getParent(ps.linkNode)
							if getVisibility(parent) and not ps.visible then
								printDebug("visibility true of node:",2,true)
								ps.visible = true
							else
							ps.visible = false
							end
						elseif not getVisibility(ps.linkNode) and ps.visible then
							printDebug("visibility false of node:",2,true)
							ps.visible = false
						end
						if ps.visible then
							local emitScale, speedScale	-- placeholders
							ps.changedValues = {}
							printDebug("ps ",2,true)
							ExtendedExhaust.Logging_table(ps)

							if ps.startSmoke or ps.lastSmoke and not ps.normalSmoke then
								if motorIsStarting and ps.startSmoke then
									local startInfo = ps.startInfo
									ps.changedValues.emitScale = startInfo.emitScale
									ps.changedValues.speedScale = startInfo.speedScale
								else
									printDebug("motorLoad = '" .. motorLoad*100 .. "'",2,true)
									if (motorLoad*100 >= 50 and motorLoad*100 <= 100) and ps.lastSmoke then
									local loadInfo = ps.loadInfo
									local rpmInfo = ps.rpmInfo

									local adjustedLoad = motorLoad * loadInfo.threshold
									local totalEmitScale = loadInfo.emitScale * rpmInfo.emitScale

									ps.changedValues.emitScale = MathUtil.lerp(1, totalEmitScale, adjustedLoad)
									ps.changedValues.speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
									else
									emitScale = 0
									local rpmInfo = ps.rpmInfo
									speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
									end
								end
							end
							if not ps.startSmoke and not ps.lastSmoke and ps.normalSmoke then
								if not motorIsStarting then
									local loadInfo = ps.loadInfo
									local rpmInfo = ps.rpmInfo
									-- if motorLoad <= 0 then motorLoad = motorLoad * -1 end
									local adjustedLoad = motorLoad * loadInfo.threshold
									local totalEmitScale = loadInfo.emitScale * rpmInfo.emitScale

									ps.changedValues.emitScale = MathUtil.lerp(1, totalEmitScale, adjustedLoad)
									ps.changedValues.speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
								end
							end
							if not ps.startSmoke and ps.lastSmoke and ps.normalSmoke then
								if not motorIsStarting then
									if (motorLoad*100 >= 50 and motorLoad*100 <= 100) and ps.lastSmoke then
									local loadInfo = ps.loadInfo
									local rpmInfo = ps.rpmInfo

									local adjustedLoad = motorLoad * loadInfo.threshold
									local totalEmitScale = loadInfo.emitScale * rpmInfo.emitScale

									ps.changedValues.emitScale = MathUtil.lerp(1, totalEmitScale, adjustedLoad)
									ps.changedValues.speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
									else
									local loadInfo = ps.loadInfo
									local rpmInfo = ps.rpmInfo
									if motorLoad <= 0 then motorLoad = motorLoad * -1 end
									local adjustedLoad = motorLoad * loadInfo.threshold
									local totalEmitScale = loadInfo.emitScale * rpmInfo.emitScale

									ps.changedValues.emitScale = MathUtil.lerp(1, totalEmitScale, adjustedLoad)
									ps.changedValues.speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
									end
								end
							end
							if not ps.startSmoke and not ps.normalSmoke and not ps.lastSmoke then
								if motorIsStarting then
									local startInfo = ps.startInfo
									ps.changedValues.emitScale = startInfo.emitScale
									ps.changedValues.speedScale = startInfo.speedScale
								else
									local loadInfo = ps.loadInfo
									local rpmInfo = ps.rpmInfo

									local adjustedLoad = motorLoad * loadInfo.threshold
									local totalEmitScale = loadInfo.emitScale * rpmInfo.emitScale

									ps.changedValues.emitScale = MathUtil.lerp(1, totalEmitScale, adjustedLoad)
									ps.changedValues.speedScale = MathUtil.lerp(1, rpmInfo.speedScale, motorRpm)
								end
							end
							if ps.changedValues.emitScale ~= nil and ps.changedValues.speedScale ~= nil then
							printDebug("emitScale = '" .. ps.changedValues.emitScale .. "'",2,true)
							printDebug("speedScale = '" .. ps.changedValues.speedScale .. "'",2,true)

							ParticleUtil.setEmitCountScale(ps, ps.changedValues.emitScale)
							ParticleUtil.setParticleSystemTimeScale(ps, ps.changedValues.speedScale)
							else
							printDebug("emitScale == '0'",2,true)
							printDebug("speedScale == '0'",2,true)

							ParticleUtil.setEmitCountScale(ps, 0)
							end
							ps.changedValues = nil
						else
							printDebug("emitScale = '0'",2,true)
							printDebug("speedScale = '0'",2,true)
							ParticleUtil.setEmitCountScale(ps, 0)
							ParticleUtil.setParticleSystemTimeScale(ps, 0)
						end
                    end
                end
            end
        end
    end
end

-- <<onStartMotor>>
function ExtendedExhaust:onStartMotor()
    local spec = self[g_specName]

    if self.isClient then
        if spec.particleSystems ~= nil then
            spec.psMotorStartTime = spec.psMotorStartDuration
            for _, ps in ipairs(spec.particleSystems) do
                ParticleUtil.setEmittingState(ps, true)
				printDebug("ps emmit start true",2,true)
				ExtendedExhaust.Logging_table(ps)
            end
        end
    end
end

-- <<onStopMotor>>
function ExtendedExhaust:onStopMotor()
    local spec = self[g_specName]

    if self.isClient then
        if spec.particleSystems ~= nil then
            for _, ps in ipairs(spec.particleSystems) do
                ParticleUtil.setEmittingState(ps, false)
				printDebug("ps emmit stop false",2,true)
				ExtendedExhaust.Logging_table(ps)
            end
        end
    end
end

-- <<onDelete>>
function ExtendedExhaust:onDelete()
    local spec = self[g_specName]
    if self.isClient then
        if spec.particleSystems ~= nil then
            ParticleUtil.deleteParticleSystems(spec.particleSystems)
        end
    end
end

-- <<[Specialization Overrides]>> -----------------------------------------------------------------

-- <<loadExhaustEffects>>
function ExtendedExhaust:loadExhaustEffects(superFunc, xmlFile)
    local spec = self[g_specName]
    superFunc(self, xmlFile)

    local extendedExhaustKey = "vehicle.motorized.extendedExhaust"

    spec.psMotorStartDuration = Utils.getNoNil(xmlFile:getFloat(extendedExhaustKey.."#motorStartDuration"), 0)
    spec.psMotorStartTime = 0
    spec.particleSystems = {}

    local idx = 0
    while true do
        local psKey = string.format("%s.particleSystems.particleSystem(%d)", extendedExhaustKey, idx)
        if not xmlFile:hasProperty(psKey) then
            break
        end
		local linkIndex = xmlFile:getString(psKey .. "#node")
		if string.find(linkIndex, ">") then
			linkIndex = linkIndex
		else
			local j = 0
			while true do
				local key = string.format("vehicle.i3dMappings.i3dMapping(%d)", j)

				if not xmlFile:hasProperty(key) then
					break
				end
				local id = xmlFile:getString(key .. "#id")
				if id == linkIndex then
					linkIndex = xmlFile:getString(key .. "#node")
					xmlFile:setString(psKey .. "#node", linkIndex)
					break
				end
				j= j + 1
			end
		end
        local psNode = I3DUtil.indexToObject(self.components, linkIndex)

        -- check visibility
        -- local nodeIsVisible = true
        -- while true do
            -- if Utils.getNoNil(psNode, 0) == 0 or not nodeIsVisible then
                -- break
            -- end

            -- nodeIsVisible = getVisibility(psNode)
            -- psNode = getParent(psNode)
        -- end

        -- if nodeIsVisible then
        -- local nodeIsVisible = false
		-- if Utils.getNoNil(psNode, 0) ~= 0 then

			-- nodeIsVisible = getVisibility(psNode)
			-- printDebug("nodeIsVisible '" .. tostring(nodeIsVisible) .. "'",2,true)
			-- linkNode = getParent(psNode)
			-- printDebug("linkNode '" .. psNode .. "'",2,true)
        -- end

        -- if nodeIsVisible then
		   local startSmoke = Utils.getNoNil(xmlFile:getBool(psKey .. "#start"), false)
		   local normalSmoke = Utils.getNoNil(xmlFile:getBool(psKey .. "#normal"), false)
		   local lastSmoke = Utils.getNoNil(xmlFile:getBool(psKey .. "#last"), false)
           local ps = {}
            local rpmInfo = {
                emitScale  = math.max(0, Utils.getNoNil(xmlFile:getFloat(psKey..".rpmInfo#emitScale"), 1)),
                speedScale = math.max(0, Utils.getNoNil(xmlFile:getFloat(psKey..".rpmInfo#speedScale"), 2))
            }
            local loadInfo = {
                emitScale = math.max(0, Utils.getNoNil(xmlFile:getFloat(psKey..".loadInfo#emitScale"), 7.5)),
                threshold = MathUtil.clamp(Utils.getNoNil(xmlFile:getFloat(psKey..".loadInfo#threshold"), 1), 0, 1)
            }
            local startInfo = {
                emitScale  = math.max(0, Utils.getNoNil(xmlFile:getFloat(psKey..".startInfo#emitScale"), 7.5)),
                speedScale = math.max(0, Utils.getNoNil(xmlFile:getFloat(psKey..".startInfo#speedScale"), 1.5))
            }

            ExtendedExhaust.loadParticleSystem(xmlFile, ps, psKey, self.components, false, nil, self.baseDirectory)
		    ps.linkIndex = linkIndex
		    ps.linkNode = psNode
            ps.rpmInfo = rpmInfo
            ps.loadInfo = loadInfo
            ps.startInfo = startInfo
            ps.startSmoke = startSmoke
            ps.normalSmoke = normalSmoke
            ps.lastSmoke = lastSmoke

            table.insert(spec.particleSystems, ps)
        -- end
			printDebug("particleSystems 'on exhaust'",2,true)
			ExtendedExhaust.Logging_table(spec.particleSystems)

        idx = idx + 1
    end

    if #spec.particleSystems == 0 then
        spec.particleSystems = nil
		printDebug("particleSystems 'deleted'",2,true)
    end
end

function ExtendedExhaust.loadParticleSystem(xmlFile, particleSystem, baseString, linkNodes, defaultEmittingState, defaultPsFile, baseDir, defaultLinkNode)
	local data = {}

	ParticleUtil.loadParticleSystemData(xmlFile, data, baseString)

	return ExtendedExhaust.loadParticleSystemFromData(data, particleSystem, linkNodes, defaultEmittingState, defaultPsFile, baseDir, defaultLinkNode)
end

function ExtendedExhaust.loadParticleSystemFromData(data, particleSystem, linkNodes, defaultEmittingState, defaultPsFile, baseDir, defaultLinkNode)
	if defaultLinkNode == nil then
		defaultLinkNode = linkNodes

		if type(linkNodes) == "table" then
			defaultLinkNode = linkNodes[1].node
		end
	end

	local linkNode = Utils.getNoNil(I3DUtil.indexToObject(linkNodes, data.nodeStr), defaultLinkNode)
	local psFile = data.psFile

	if psFile == nil then
		psFile = defaultPsFile
	end

	if psFile == nil then
		return
	end

	psFile = Utils.getFilename(psFile, baseDir)
	particleSystem.isValid = false
	local node = loadI3DFile(psFile, true, true, false)
	particleSystem.node = node
	particleSystem.failedReason = 0
	local i3dNode = node
	local arguments = {
		data = data,
		particleSystem = particleSystem,
		linkNode = linkNode,
		psFile = psFile,
		defaultEmittingState = defaultEmittingState
	}
	-- particleSystem.sharedLoadRequestId = g_i3DManager:loadSharedI3DFileAsync(psFile, true, true, ParticleUtil.particleI3DFileLoaded, ParticleUtil, arguments)
	ParticleUtil.particleI3DFileLoaded(_, i3dNode, failedReason, arguments)

	return true
end


