-- Exhaust Extension
-- Specialization to add real smoke
-- Version 1.0.0.0
-- @author Vector Man
-- @date 2022-05-28
-- copyright Vector Man, All Rights Reserved.

ExhaustExtension = {}
ExhaustExtension.ModDirectory = g_currentModDirectory
ExhaustExtension.EffectFilename = ""
ExhaustExtension.SpecName = ("spec_%s.ExhaustExtension"):format(g_currentModName)

local modName = g_currentModName

function ExhaustExtension.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Motorized, specializations)
end

function ExhaustExtension.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "toggleEffects", ExhaustExtension.toggleEffects)
	SpecializationUtil.registerFunction(vehicleType, "setParticleIntensity", ExhaustExtension.setParticleIntensity)
	SpecializationUtil.registerFunction(vehicleType, "setTimedActive", ExhaustExtension.setTimedActive)
end

function ExhaustExtension.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onUpdate", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onStartMotor", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onStopMotor", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onDelete", ExhaustExtension)
	SpecializationUtil.registerEventListener(vehicleType, "onCutTree", ExhaustExtension)
end

function ExhaustExtension:onLoad(savegame)
	ExhaustExtension.EffectFilename = ExhaustExtension.ModDirectory .. "particles/smokeParticles.i3d"
	ExhaustExtension.EmitterShapeFilename = ExhaustExtension.ModDirectory .. "particles/smokeEmitterShape.i3d"
end

function ExhaustExtension:onDelete()
end

function ExhaustExtension:onEnterVehicle()

	-- Create spec in self
	self.spec_exhaustExtension = self[ExhaustExtension.SpecName]

	local spec = self.spec_exhaustExtension
	
	if self:getIsActive() and self.spec_motorized ~= nil and not spec.initialized and self.isClient then
		
		spec.initialized = true
		spec.particleSystems = {}
		spec.timer = 0
		spec.particleModifiers = {}
		spec.delimbEffectRunning = false
		
		-- Vanilla inserts
		local excludeVehicle = false
		ExhaustExtension.InsertsFile = XMLFile.loadIfExists("vanillaInserts", Utils.getFilename("vanillaInserts.xml", ExhaustExtension.ModDirectory))
		
		ExhaustExtension.InsertsFile:iterate("vanillaInserts.insert", function(_, key)
			if ExhaustExtension.InsertsFile:getString(key .. "#filename", "") == "$" .. self.xmlFile:getFilename() then
				if ExhaustExtension.InsertsFile:getBool(key .. ".exhaustExtension#excludeVehicle", false) then
					excludeVehicle = true
				end
				
				-- Fill modifiers
				spec.particleModifiers.intensityFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension#intensityFactor", 1.0)
				spec.particleModifiers.motorStartFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.motorStart#factor", 1.0)
				spec.particleModifiers.cutTreeFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.cutTree#factor", 1.0)
				spec.particleModifiers.delimbTreeFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.cutTree#factor", 1.0)
				spec.particleModifiers.engineLoadFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.engineLoad#factor", 1.0)
				spec.particleModifiers.engineLoadThreshold = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.engineLoad#threshold", 0.5)
				spec.particleModifiers.damagedVehicleFactor = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.damagedVehicle#factor", 1.0)
				spec.particleModifiers.damagedVehicleThreshold = ExhaustExtension.InsertsFile:getFloat(key .. ".exhaustExtension.damagedVehicle#threshold", 0.85)
			else
				local modifierXmlKey = "vehicle.motorized.exhaustExtension"
				
				if self.xmlFile:getBool(modifierXmlKey .. "#excludeVehicle", false) then
					excludeVehicle = true
				end
				
				-- Fill modifiers	
				spec.particleModifiers.intensityFactor = self.xmlFile:getFloat(modifierXmlKey .. "#intensityFactor", 1.0)
				spec.particleModifiers.motorStartFactor = self.xmlFile:getFloat(modifierXmlKey .. ".motorStart#factor", 1.0)
				spec.particleModifiers.cutTreeFactor = self.xmlFile:getFloat(modifierXmlKey .. ".cutTree#factor", 1.0)
				spec.particleModifiers.delimbTreeFactor = self.xmlFile:getFloat(modifierXmlKey .. ".delimbTree#factor", 1.0)
				spec.particleModifiers.engineLoadFactor = self.xmlFile:getFloat(modifierXmlKey .. ".engineLoad#factor", 1.0)
				spec.particleModifiers.engineLoadThreshold = self.xmlFile:getFloat(modifierXmlKey .. ".engineLoad#threshold", 0.5)
				spec.particleModifiers.damagedVehicleFactor = self.xmlFile:getFloat(modifierXmlKey .. ".damagedVehicle#factor", 1.0)
				spec.particleModifiers.damagedVehicleThreshold = self.xmlFile:getFloat(modifierXmlKey .. ".damagedVehicle#threshold", 0.85)
			end
		end)
	
		if self.xmlFile:hasProperty("vehicle.motorized.exhaustEffects") and not excludeVehicle then
			self.xmlFile:iterate("vehicle.motorized.exhaustEffects.exhaustEffect", function (_, key)
				local linkNodePath = self.xmlFile:getString(key .. "#node")
				if self.xmlFile:hasProperty("vehicle.i3dMappings") then
					self.xmlFile:iterate("vehicle.i3dMappings.i3dMapping", function (_, key2)
						local id = self.xmlFile:getString(key2 .. "#id")
						local node = self.xmlFile:getString(key2 .. "#node")

						if id == linkNodePath then
						linkNodePath = node
						end
				
					end)
				end
				
				local effect = g_i3DManager:loadSharedI3DFile(ExhaustExtension.EffectFilename)
				local emitter = g_i3DManager:loadSharedI3DFile(ExhaustExtension.EmitterShapeFilename)
				local linkNode = I3DUtil.indexToObject(self.components, linkNodePath)
				link(linkNode, effect)
				link(linkNode, emitter)
			
				local ps = {}
			
				local i = 1
				while true do
					if getChildAt(linkNode, i) ~= nil then
						if getNumOfChildren(getChildAt(linkNode, i)) > 0 then
							if getName(getChildAt(getChildAt(linkNode, i), 0)) == "smokeEmitter" then
								ParticleUtil.loadParticleSystemFromNode(getChildAt(getChildAt(linkNode, i), 0), ps, false, false)
								ParticleUtil.setEmitterShape(ps, getChildAt(getChildAt(linkNode, i+1), 0))
								break
							end
						end
					end
					i = i+1
				end
			
				table.insert(spec.particleSystems, ps)
				
			end)
			Logging.info("Successfully added 'ExhaustExtension' particle system to " .. table.size(spec.particleSystems) .. " nodes of vehicle '" .. self:getFullName() .."'")
			
		elseif excludeVehicle then
			Logging.info("No 'ExhaustExtension' particle system added to vehicle '" .. self:getFullName() .."' because it has been excluded by the author")
		else
			Logging.info("No 'ExhaustExtension' particle system added to vehicle '" .. self:getFullName() .."' because it has no exhaust")
		end
	end
end

function ExhaustExtension:onUpdate(dt)
	if self:getIsActive() and self.spec_motorized ~= nil and self.isClient then
		local spec = self.spec_exhaustExtension
		
		if table.size(spec.particleSystems) ~= 0 then
			-- Timer --
			if spec.timer >= 0 and self:getIsActive() then
				spec.timer = spec.timer - dt
		
				if spec.timer <= 0 then
					ExhaustExtension:toggleEffects(spec.particleSystems, false)
				end
			end
		
			-- Make sure particle systems are disabled when motor is turned off
			local isEmitting = false
			for _, effectNode in ipairs(spec.particleSystems) do
				if effectNode.isEmitting then
					isEmitting = true
				end
			end
			if not self.spec_motorized:getIsMotorStarted() and isEmitting then
				isEmitting = false
				for _, effectNode in ipairs(spec.particleSystems) do
					ExhaustExtension:toggleEffects(spec.particleSystems, false)
				end
			end
			-- Auto turn-off when intensity is low
			for _, effectNode in ipairs(spec.particleSystems) do
				if getHasShaderParameter(effectNode.shape, "colorAlpha") then
					local _, _, _, intensity = getShaderParameter(effectNode.shape, "colorAlpha")
					if intensity < 0.1 and isEmitting then
						ExhaustExtension:toggleEffects(spec.particleSystems, false)
					end
				else
					Logging.error("Missing shader attribute 'colorAlpha' in ExhaustExtension particle system material")
				end
			end
		end
	end
	-- /// EVENTS SECTION /// --

	if self:getIsActive() and self.spec_motorized:getIsMotorStarted() and self.isClient then
		local spec = self.spec_exhaustExtension
		
		if table.size(spec.particleSystems) ~= 0 then
			-- Load	
			spec.isActivatedLoad = nil
			if self.spec_motorized:getMotorLoadPercentage() > spec.particleModifiers.engineLoadThreshold then
				if self.spec_wearable ~= nil then
					if self.spec_wearable:getDamageAmount() < spec.particleModifiers.damagedVehicleThreshold then
						spec.isActivatedLoad = true
						ExhaustExtension:toggleEffects(spec.particleSystems, true, 0.1 * spec.particleModifiers.intensityFactor)
						ExhaustExtension:setParticleIntensity(spec.particleSystems, (self.spec_motorized:getMotorLoadPercentage() - spec.particleModifiers.engineLoadThreshold) * 0.8 * spec.particleModifiers.intensityFactor * spec.particleModifiers.engineLoadFactor)
					end
				else
					spec.isActivatedLoad = true
					ExhaustExtension:toggleEffects(spec.particleSystems, true, 0.1 * spec.particleModifiers.intensityFactor)
					ExhaustExtension:setParticleIntensity(spec.particleSystems, (self.spec_motorized:getMotorLoadPercentage() - spec.particleModifiers.engineLoadThreshold) * 0.8 * spec.particleModifiers.intensityFactor * spec.particleModifiers.engineLoadFactor)
				end
			elseif spec.isActivatedLoad then
				ExhaustExtension:toggleEffects(spec.particleSystems, false)
				spec.isActivatedLoad = false
			end
		
			-- Wear		
			spec.isActivatedWear = nil
			if self.spec_wearable ~= nil then
				if self.spec_wearable:getDamageAmount() > spec.particleModifiers.damagedVehicleThreshold then
					spec.isActivatedWear = true
					ExhaustExtension:toggleEffects(spec.particleSystems, true, 0.8 * spec.particleModifiers.intensityFactor)
				elseif spec.isActivatedWear then
					ExhaustExtension:toggleEffects(spec.particleSystems, false)
					spec.isActivatedWear = false
				end
			end
		
			-- Delimb tree
			if self.spec_woodHarvester ~= nil then
				local isDelimbing = self.spec_woodHarvester.isAttachedSplitShapeMoving
				if isDelimbing and not spec.delimbEffectRunning then
					spec.delimbEffectRunning = true
					ExhaustExtension:setTimedActive(self.spec_exhaustExtension, 1000, 0.3 * spec.particleModifiers.intensityFactor)
				elseif not isDelimbing and spec.delimbEffectRunning then
					spec.delimbEffectRunning = false
				end
			end
		end
	end
end

function ExhaustExtension:toggleEffects(effects, state, intensity)
	for _, effectNode in ipairs(effects) do
		if effectNode.isEmitting ~= state then
			ParticleUtil.setEmittingState(effectNode, state)
		end
		if intensity ~= nil then
			ExhaustExtension:setParticleIntensity(effects, intensity)
		end
	end
end

function ExhaustExtension:setParticleIntensity(effect, intensity)
	for _, effectNode in ipairs(effect) do
		if getHasShaderParameter(effectNode.shape, "colorAlpha") then
			setShaderParameter(effectNode.shape, "colorAlpha", 0.05, 0.05, 0.05, intensity, false)
		else
			Logging.error("Missing shader attribute 'colorAlpha' in ExhaustExtension particle system material")
		end
	end
end

function ExhaustExtension:setTimedActive(spec, duration, intensity)
	spec.timer = duration
	ExhaustExtension:toggleEffects(spec.particleSystems, true, intensity)
end

-- /// EVENTS SECTION /// --

-- Start motor
function ExhaustExtension:onStartMotor()
	if self:getIsActive() and self.isClient then
		local spec = self.spec_exhaustExtension
		if spec.particleSystems ~= nil and table.size(spec.particleSystems) ~= 0 then
			ExhaustExtension:setTimedActive(spec, 500, 0.3 * spec.particleModifiers.intensityFactor * spec.particleModifiers.motorStartFactor)
		end
	end
end
-- Stop motor
function ExhaustExtension:onStopMotor()
	if self:getIsActive() and self.isClient then
		local spec = self.spec_exhaustExtension
		if spec.particleSystems ~= nil and table.size(spec.particleSystems) ~= 0 then
			ExhaustExtension:toggleEffects(spec.particleSystems, false)
		end
	end
end
-- WoodHarvester cut tree
function ExhaustExtension:onCutTree()
	if self:getIsActive() and self.isClient then
		local spec = self.spec_exhaustExtension
		if spec.particleSystems ~= nil and table.size(spec.particleSystems) ~= 0 then
			ExhaustExtension:setTimedActive(spec, 800, 0.3 * spec.particleModifiers.intensityFactor * spec.particleModifiers.cutTreeFactor)
		end
	end
end