-- Individual Lift
-- Specialization script for windrowers wo add key bindings for the individual lift of the rotors. Also includes automatic mode.
-- Version 1.0.0.0
-- @author Vector Man
-- @date 2023-11-27
-- copyright (c) Vector Man, All Rights Reserved.

source(g_currentModDirectory .. "data/scripts/events/ToggleRotorArmEvent.lua")
source(g_currentModDirectory .. "data/scripts/events/SetRotorArmEvent.lua")
source(g_currentModDirectory .. "data/scripts/events/SetFieldWorkerStateEvent.lua")
source(g_currentModDirectory .. "data/scripts/events/ToggleBlockAutomaticModeEvent.lua")

IndividualLowering = {
	prerequisitesPresent = function (specializations)
		return SpecializationUtil.hasSpecialization(Windrower, specializations) and SpecializationUtil.hasSpecialization(WorkMode, specializations)
	end,
	initSpecialization = function ()
		local schema = Vehicle.xmlSchema

		schema:setXMLSpecializationType("IndividualLowering")
		schema:register(XMLValueType.NODE_INDEX, "vehicle.individualLowering.rotorArms.rotorArm(?)#detectionAreaStart", "Start node for detection area (automatic mode)")
		schema:register(XMLValueType.NODE_INDEX, "vehicle.individualLowering.rotorArms.rotorArm(?)#detectionAreaWidth", "Width node for detection area (automatic mode)")
		schema:register(XMLValueType.NODE_INDEX, "vehicle.individualLowering.rotorArms.rotorArm(?)#detectionAreaHeight", "Height node for detection area (automatic mode)")
		schema:register(XMLValueType.STRING, "vehicle.individualLowering.rotorArms.rotorArm(?)#lowerAnimationName", "Name of the lower animation for this rotor")
		schema:register(XMLValueType.FLOAT, "vehicle.individualLowering.rotorArms.rotorArm(?)#lowerAnimationSpeedScale", "Speed scale of the lower animation for this rotor")
		schema:register(XMLValueType.BOOL, "vehicle.workModes.workMode(?)#isAutomaticMode", "Mode allows automatic lowering")
		schema:register(XMLValueType.STRING, "vehicle.workModes.workMode(?)#activeText", "Text to display when automatic mode is active")
		schema:register(XMLValueType.STRING, "vehicle.workModes.workMode(?)#inactiveText", "Text to display when automatic mode is inactive")
		
		schema:register(XMLValueType.BOOL, "vehicle.workModes.workModeConfigurations.workModeConfiguration(?).workMode(?)#isAutomaticMode", "Mode allows automatic lowering")
		schema:register(XMLValueType.STRING, "vehicle.workModes.workModeConfigurations.workModeConfiguration(?).workMode(?)#activeText", "Text to display when automatic mode is active")
		schema:register(XMLValueType.STRING, "vehicle.workModes.workModeConfigurations.workModeConfiguration(?).workMode(?)#inactiveText", "Text to display when automatic mode is inactive")
		schema:setXMLSpecializationType()

		local schemaSavegame = Vehicle.xmlSchemaSavegame
	end,
	
	SPEC_NAME = string.format("spec_%s.individualLowering", g_currentModName),
}

function IndividualLowering.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "inputAction_windrowerLower1", IndividualLowering.inputAction_WINDROWER_LOWER1)
	SpecializationUtil.registerFunction(vehicleType, "inputAction_windrowerLower2", IndividualLowering.inputAction_WINDROWER_LOWER2)
	SpecializationUtil.registerFunction(vehicleType, "inputAction_windrowerLower3", IndividualLowering.inputAction_WINDROWER_LOWER3)
	SpecializationUtil.registerFunction(vehicleType, "inputAction_windrowerLower4", IndividualLowering.inputAction_WINDROWER_LOWER4)
	SpecializationUtil.registerFunction(vehicleType, "toggleRotorArm", IndividualLowering.toggleRotorArm)
	SpecializationUtil.registerFunction(vehicleType, "setRotorArm", IndividualLowering.setRotorArm)
	SpecializationUtil.registerFunction(vehicleType, "getIsRotorArmLowered", IndividualLowering.getIsRotorArmLowered)
	SpecializationUtil.registerFunction(vehicleType, "actionEventFoldMiddleExt", IndividualLowering.actionEventFoldMiddleExt)
	SpecializationUtil.registerFunction(vehicleType, "toggleBlockAutomaticMode", IndividualLowering.toggleBlockAutomaticMode)
	SpecializationUtil.registerFunction(vehicleType, "setBlockAutomaticMode", IndividualLowering.setBlockAutomaticMode)
	SpecializationUtil.registerFunction(vehicleType, "actionEventFoldExt", IndividualLowering.actionEventFoldExt)
	SpecializationUtil.registerFunction(vehicleType, "setFieldWorkerState", IndividualLowering.setFieldWorkerState)
	SpecializationUtil.registerFunction(vehicleType, "setRotorArms", IndividualLowering.setRotorArms)
end

function IndividualLowering.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "doCheckSpeedLimit", IndividualLowering.doCheckSpeedLimit)
end

function IndividualLowering.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", IndividualLowering)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", IndividualLowering)
	SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", IndividualLowering)
	SpecializationUtil.registerEventListener(vehicleType, "onUpdate", IndividualLowering)
	SpecializationUtil.registerEventListener(vehicleType, "onReadStream", IndividualLowering)
	SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", IndividualLowering)
end

function IndividualLowering:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
	if self.isClient then
		local spec = self.spec_individualLowering
		self:clearActionEventsTable(spec.actionEvents)

		if isActiveForInputIgnoreSelection then
			local actionEventId
			_, actionEventId = self:addPoweredActionEvent(spec.actionEvents, "WINDROWER_LOWER1", self, IndividualLowering.inputAction_WINDROWER_LOWER1, false, true, false, true, true, nil )
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
			g_inputBinding:setActionEventTextVisibility(actionEventId, false)
			g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("input_WINDROWER_LOWER1"))
			_, actionEventId = InputBinding.registerActionEvent(g_inputBinding, "WINDROWER_LOWER1", self, IndividualLowering.inputAction_WINDROWER_LOWER1, false, true, false, true)
			
			_, actionEventId = self:addPoweredActionEvent(spec.actionEvents, "WINDROWER_LOWER2", self, IndividualLowering.inputAction_WINDROWER_LOWER2, false, true, false, true, true, nil )
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
			g_inputBinding:setActionEventTextVisibility(actionEventId, false)
			g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("input_WINDROWER_LOWER2"))
			_, actionEventId = InputBinding.registerActionEvent(g_inputBinding, "WINDROWER_LOWER2", self, IndividualLowering.inputAction_WINDROWER_LOWER2, false, true, false, true)
			
			_, actionEventId = self:addPoweredActionEvent(spec.actionEvents, "WINDROWER_LOWER3", self, IndividualLowering.inputAction_WINDROWER_LOWER3, false, true, false, true, true, nil )
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
			g_inputBinding:setActionEventTextVisibility(actionEventId, false)
			g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("input_WINDROWER_LOWER3"))
			_, actionEventId = InputBinding.registerActionEvent(g_inputBinding, "WINDROWER_LOWER3", self, IndividualLowering.inputAction_WINDROWER_LOWER3, false, true, false, true)
			
			_, actionEventId = self:addPoweredActionEvent(spec.actionEvents, "WINDROWER_LOWER4", self, IndividualLowering.inputAction_WINDROWER_LOWER4, false, true, false, true, true, nil )
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
			g_inputBinding:setActionEventTextVisibility(actionEventId, false)
			g_inputBinding:setActionEventText(actionEventId, g_i18n:getText("input_WINDROWER_LOWER4"))
			_, actionEventId = InputBinding.registerActionEvent(g_inputBinding, "WINDROWER_LOWER4", self, IndividualLowering.inputAction_WINDROWER_LOWER4, false, true, false, true)
		end
	end
end

function IndividualLowering:onLoad(savegame)
	-- fold middle overwritten function
	Foldable.actionEventFoldMiddle = Utils.overwrittenFunction(Foldable.actionEventFoldMiddle, self.actionEventFoldMiddleExt)

	-- fold prepended function
	Foldable.actionEventFold = Utils.prependedFunction(Foldable.actionEventFold, self.actionEventFoldExt)
	
	-- Create spec table in self
    self.spec_individualLowering = self[IndividualLowering.SPEC_NAME]
	local spec = self.spec_individualLowering

	-- create rotor arms table
	local baseKey = "vehicle.individualLowering"
	spec.rotorArms = {}
	self.xmlFile:iterate(baseKey .. ".rotorArms.rotorArm", function(i, key)
		local rotorArm = {}
		rotorArm.detectionArea = {
			start = self.xmlFile:getValue(key .. "#detectionAreaStart", nil, self.components, self.i3dMappings),
			width = self.xmlFile:getValue(key .. "#detectionAreaWidth", nil, self.components, self.i3dMappings),
			height = self.xmlFile:getValue(key .. "#detectionAreaHeight", nil, self.components, self.i3dMappings)
		}
		rotorArm.isLowered = false
		
		rotorArm.lowerAnimation = {
			name = self.xmlFile:getValue(key .. "#lowerAnimationName"),
			speedScale = self.xmlFile:getValue(key .. "#lowerAnimationSpeedScale")
		}
		
		table.insert(spec.rotorArms, rotorArm)
	end)
	
	spec.numRotors = #spec.rotorArms
	spec.animTimeUnfolded = -1
	spec.speedLimit = self:getSpeedLimit()
	spec.isAutomaticMode = false
	spec.blockAutomaticMode = true
	spec.fieldWorkerActive = false
	spec.initialized = false
	
	-- add attributes to work modes
	local spec_workMode = self.spec_workMode
	
	if spec_workMode ~= nil then
		local xmlKey = ""
		if self.xmlFile:hasProperty("vehicle.workModes.workModeConfigurations") then
			if self.configurations.workMode == nil then
				self.configurations.workMode = 1
			end
		
			xmlKey = string.format("vehicle.workModes.workModeConfigurations.workModeConfiguration(%d).workMode", self.configurations.workMode - 1)
		else
			xmlKey = "vehicle.workModes.workMode"
		end
		
		self.xmlFile:iterate(xmlKey, function(i, key)
			local mode = spec_workMode.workModes[i]
			
			mode.isAutomaticMode = Utils.getNoNil(self.xmlFile:getValue(key .. "#isAutomaticMode"), false)
			
			local activeText = self.xmlFile:getValue(key .. "#activeText", "Missing activeText for current workMode")
			if activeText:sub(1, 6) == "$l10n_" then
				activeText = g_i18n:getText(activeText:sub(7))
			end
			mode.activeText = activeText
			
			local inactiveText = self.xmlFile:getValue(key .. "#inactiveText", "Missing inactiveText for current workMode")
			if inactiveText:sub(1, 6) == "$l10n_" then
				inactiveText = g_i18n:getText(inactiveText:sub(7))
			end
			mode.inactiveText = inactiveText
		end)
	end
end

function IndividualLowering:onPostLoad(savegame)
end

function IndividualLowering:onReadStream(streamId, connection)
	local spec = self.spec_individualLowering

	for _, rotorArm in ipairs(spec.rotorArms) do
		rotorArm.isLowered = streamReadBool(streamId)
	end
	spec.blockAutomaticMode = streamReadBool(streamId)
end

function IndividualLowering:onWriteStream(streamId, connection)
	local spec = self.spec_individualLowering
	
	for _, rotorArm in ipairs(spec.rotorArms) do
		streamWriteBool(streamId, rotorArm.isLowered)
	end
	streamWriteBool(streamId, spec.blockAutomaticMode)
end

function IndividualLowering:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local spec = self.spec_individualLowering
	local spec_workMode = self.spec_workMode
	
	-- check how many rotors are lowered
	spec.numRotorsLowered = 0
	for _, rotorArm in ipairs(spec.rotorArms) do
		if rotorArm.isLowered then
			spec.numRotorsLowered = spec.numRotorsLowered + 1
		end
	end
	
	spec.isAutomaticMode = spec_workMode.workModes[spec_workMode.state].isAutomaticMode
	
	-- initialize
	if not spec.initialized then
		-- "wake up" the windrower to set the correct foldAnimTime
		if self:getIsUnfolded() then
			self:setFoldState(1, true)
		end
		
		self:setBlockAutomaticMode(true)
		self:setRotorArms(false)
		
		spec.initialized = true
	end
	
	-- calculate main lowering animation anim time when unfolded
	if self.spec_foldable:getIsUnfolded() and self.spec_foldable:getFoldAnimTime() == spec.lastFoldAnimTime and spec.animTimeUnfolded == -1 then
		spec.animTimeUnfolded = self.spec_foldable:getFoldAnimTime()
	elseif not self.spec_foldable:getIsUnfolded() and spec.animTimeUnfolded ~= -1 then
		spec.animTimeUnfolded = -1
	end

	-- set all individual rotor arms to raised / lowered when windrower was raised / lowered with default key
	if spec.animTimeUnfolded ~= -1 and self.spec_foldable:getFoldAnimTime() < spec.lastFoldAnimTime and spec.numRotorsLowered < spec.numRotors then
		for _, rotorArm in ipairs(spec.rotorArms) do
			rotorArm.isLowered = true
		end
	elseif spec.animTimeUnfolded ~= -1 and self.spec_foldable:getFoldAnimTime() > spec.lastFoldAnimTime and spec.numRotorsLowered > 0 then
		for _, rotorArm in ipairs(spec.rotorArms) do
			rotorArm.isLowered = false
		end
	end
	
	-- adjust main lowering animation time when all individual rotor arms were raised / lowered manually
	if spec.animTimeUnfolded ~= -1 and spec.numRotorsLowered == spec.numRotors and self.spec_foldable:getFoldAnimTime() == spec.animTimeUnfolded then
		self.spec_foldable.foldAnimTime = 0
	elseif spec.animTimeUnfolded ~= -1 and spec.numRotorsLowered == 0 and self.spec_foldable:getFoldAnimTime() == 0 then
		self.spec_foldable.foldAnimTime = spec.animTimeUnfolded
	end

	-- block automatic mode if field worker was activated
	if self.isServer then
		local isAIRunning = false
		if self:getAttacherVehicle() ~= nil then
			if self:getAttacherVehicle().spec_aiFieldWorker ~= nil then
				isAIRunning = self:getAttacherVehicle().spec_aiFieldWorker.isActive
			end
		end
		
		if isAIRunning and not spec.fieldWorkerActive then
			spec.fieldWorkerActive = true
			spec.blockAutomaticMode = true
			SetFieldWorkerStateEvent.sendEvent(self, true)
		elseif not isAIRunning and spec.fieldWorkerActive then
			spec.fieldWorkerActive = false
			SetFieldWorkerStateEvent.sendEvent(self, false)
		end
	end

	-- detect processable material on the ground
	if self.spec_foldable:getIsUnfolded() and spec.isAutomaticMode and not spec.blockAutomaticMode then
		for i, rotorArm in ipairs(spec.rotorArms) do
			local x0, y0, z0 = getWorldTranslation(rotorArm.detectionArea.start)
			local x1, y1, z1 = getWorldTranslation(rotorArm.detectionArea.width)
			local x2, _, z2 = getWorldTranslation(rotorArm.detectionArea.height)
			local fillType = DensityMapHeightUtil.getFillTypeAtArea(x0, z0, x1, z1, x2, z2)
			
			if (fillType ~= 1 and not rotorArm.isLowered) then
				self:setRotorArm(i, true, false)
			elseif (fillType == 1 and rotorArm.isLowered) then
				self:setRotorArm(i, false, false)
			end
			
			--renderText(0.15, 0.3 - i*0.03, 0.016, tostring(fillType))
		end
	end

	-- handle work mode active / inactive attribute
	if spec.isAutomaticMode then
		if spec.blockAutomaticMode then
			spec_workMode.workModes[spec_workMode.state].name = spec_workMode.workModes[spec_workMode.state].inactiveText
		else
			spec_workMode.workModes[spec_workMode.state].name = spec_workMode.workModes[spec_workMode.state].activeText
		end
	end

	spec.lastFoldAnimTime = self.spec_foldable:getFoldAnimTime()
end

function IndividualLowering:toggleRotorArm(index, noEventSend)
	local spec = self.spec_individualLowering

	ToggleRotorArmEvent.sendEvent(self, index, noEventSend)

	if self.spec_foldable:getIsUnfolded() then
		if not spec.rotorArms[index].isLowered then
			if self.isServer then
				if not self:getIsAnimationPlaying(spec.rotorArms[index].lowerAnimation.name) then
					self:setAnimationTime(spec.rotorArms[index].lowerAnimation.name, self:getAnimationDuration(spec.rotorArms[index].lowerAnimation.name))
				end
				self:playAnimation(spec.rotorArms[index].lowerAnimation.name, -spec.rotorArms[index].lowerAnimation.speedScale, self:getAnimationTime(spec.rotorArms[index].lowerAnimation.name))
			end
			spec.rotorArms[index].isLowered = true
		else
			if self.isServer then
				if not self:getIsAnimationPlaying(spec.rotorArms[index].lowerAnimation.name) then
					self:setAnimationTime(spec.rotorArms[index].lowerAnimation.name, 0)
				end
				self:playAnimation(spec.rotorArms[index].lowerAnimation.name, spec.rotorArms[index].lowerAnimation.speedScale, self:getAnimationTime(spec.rotorArms[index].lowerAnimation.name))
			end
			spec.rotorArms[index].isLowered = false
		end
	end
end

function IndividualLowering:setRotorArm(index, state, noEventSend)
	local spec = self.spec_individualLowering

	SetRotorArmEvent.sendEvent(self, index, state, noEventSend)

	if self.spec_foldable:getIsUnfolded() then
		if state == true and not spec.rotorArms[index].isLowered then
			if self.isServer then
				if not self:getIsAnimationPlaying(spec.rotorArms[index].lowerAnimation.name) then
					self:setAnimationTime(spec.rotorArms[index].lowerAnimation.name, self:getAnimationDuration(spec.rotorArms[index].lowerAnimation.name))
				end
				self:playAnimation(spec.rotorArms[index].lowerAnimation.name, -spec.rotorArms[index].lowerAnimation.speedScale, self:getAnimationTime(spec.rotorArms[index].lowerAnimation.name))
			end
			spec.rotorArms[index].isLowered = true
		elseif state == false and spec.rotorArms[index].isLowered then
			if self.isServer then
				if not self:getIsAnimationPlaying(spec.rotorArms[index].lowerAnimation.name) then
					self:setAnimationTime(spec.rotorArms[index].lowerAnimation.name, 0)
				end
				self:playAnimation(spec.rotorArms[index].lowerAnimation.name, spec.rotorArms[index].lowerAnimation.speedScale, self:getAnimationTime(spec.rotorArms[index].lowerAnimation.name))
			end
			spec.rotorArms[index].isLowered = false
		end
	end
end

function IndividualLowering:getIsRotorArmLowered(index)
	local spec = self.spec_individualLowering
	
	return spec.rotorArms[index].isLowered
end

function IndividualLowering:doCheckSpeedLimit(superFunc)
	local spec = self.spec_individualLowering
	local turnOn = true

	if self.getIsTurnedOn ~= nil then
		turnOn = self:getIsTurnedOn()
	end

	return superFunc(self) or turnOn and spec.numRotorsLowered > 0
end

function IndividualLowering:toggleBlockAutomaticMode()
	local spec = self.spec_individualLowering
	
	if spec.isAutomaticMode then
		self:setBlockAutomaticMode(not spec.blockAutomaticMode)
	end
end

function IndividualLowering:setBlockAutomaticMode(state, noEventSend)
	local spec = self.spec_individualLowering

	ToggleBlockAutomaticModeEvent.sendEvent(self, state, noEventSend)

	spec.blockAutomaticMode = state
end

function IndividualLowering:setFieldWorkerState(state, noEventSend)
	local spec = self.spec_individualLowering
	
	if not self.isServer then
		spec.fieldWorkerActive = state
		
		if state then
			spec.blockAutomaticMode = true
		end
	end
end

function IndividualLowering:setRotorArms(lowered)
	local spec = self.spec_individualLowering

	for i, rotorArm in ipairs(spec.rotorArms) do
		if rotorArm.isLowered ~= lowered then
			self:toggleRotorArm(i, false)
		end
	end
end

function IndividualLowering:actionEventFoldMiddleExt(actionName, inputValue, callbackState, isAnalog)

	local spec_individualLowering = self.spec_individualLowering
	
	if spec_individualLowering ~= nil and self.spec_foldable:getIsUnfolded() and spec_individualLowering.isAutomaticMode then
		self:toggleBlockAutomaticMode()
	end

	local spec = self.spec_foldable

	if #spec.foldingParts > 0 and self:getIsFoldMiddleAllowed() then
		local ignoreFoldMiddle = false

		if spec.ignoreFoldMiddleWhileFolded and spec.foldMiddleAnimTime < self:getFoldAnimTime() then
			ignoreFoldMiddle = true
		end

		if not ignoreFoldMiddle then
			local direction

			if spec_individualLowering ~= nil then
				if spec_individualLowering.isAutomaticMode then
					if spec_individualLowering.numRotorsLowered > 0 then
						self:setRotorArms(false)
					end
					direction = 0
				else
					direction = self:getToggledFoldMiddleDirection()
				end
			else
				direction = self:getToggledFoldMiddleDirection()
			end
			
			if direction ~= 0 then
				if direction == spec.turnOnFoldDirection then
					self:setFoldState(direction, false)
				else
					self:setFoldState(direction, true)
				end

				if self.getAttacherVehicle ~= nil then
					local attacherVehicle = self:getAttacherVehicle()
					local attacherJointIndex = attacherVehicle:getAttacherJointIndexFromObject(self)

					if attacherJointIndex ~= nil then
						local moveDown = attacherVehicle:getJointMoveDown(attacherJointIndex)
						local targetMoveDown = direction == spec.turnOnFoldDirection

						if targetMoveDown ~= moveDown then
							attacherVehicle:setJointMoveDown(attacherJointIndex, targetMoveDown)
						end
					end
				end
			end
		else
			local attacherVehicle = self:getAttacherVehicle()

			if attacherVehicle ~= nil then
				attacherVehicle:handleLowerImplementEvent(self)
			end
		end
	end
end

function IndividualLowering:actionEventFoldExt()
	local spec_individualLowering = self.spec_individualLowering
	
	if spec_individualLowering ~= nil then
		if spec_individualLowering.numRotorsLowered > 0 then
			if spec_individualLowering.isAutomaticMode then
				self:setBlockAutomaticMode(true)
			end
			
			self:setRotorArms(false)
		end
	end
end

function IndividualLowering:inputAction_WINDROWER_LOWER1(actionName, inputValue, callbackState, isAnalog)
	local spec = self.spec_individualLowering
	
	if not spec.isAutomaticMode then
		self:toggleRotorArm(1)
	end
end

function IndividualLowering:inputAction_WINDROWER_LOWER2(actionName, inputValue, callbackState, isAnalog)
	local spec = self.spec_individualLowering
	
	if not spec.isAutomaticMode then
		self:toggleRotorArm(2)
	end
end

function IndividualLowering:inputAction_WINDROWER_LOWER3(actionName, inputValue, callbackState, isAnalog)
	local spec = self.spec_individualLowering
	
	if not spec.isAutomaticMode then
		self:toggleRotorArm(3)
	end
end

function IndividualLowering:inputAction_WINDROWER_LOWER4(actionName, inputValue, callbackState, isAnalog)
	local spec = self.spec_individualLowering
	
	if not spec.isAutomaticMode then
		self:toggleRotorArm(4)
	end
end