--[[
LiftAxle

Specialization for lift axle

Author:		Ifko[nator]
Datum:		07.02.2023

Version:	v2.3

History:	v1.0 @17.11.2019 - initial implementation in FS 19
			--------------------------------------------------------------------------------
			v2.0 @11.05.2022 - convert to FS 22
			--------------------------------------------------------------------------------
			v2.1 @14.12.2022 - added "lockSteeringAxleWhenUp"
			--------------------------------------------------------------------------------
			v2.2 @18.12.2022 - added auto move down function
			--------------------------------------------------------------------------------
			v2.3 @07.02.2023 - fixed auto move up function when silage additive is available
]]

LiftAxle = {};
LiftAxle.debugPriority = 0;
LiftAxle.currentModName = g_currentModName;

local function printError(errorMessage, isWarning, isInfo)
	local prefix = "::ERROR:: ";

	if isWarning then
		prefix = "::WARNING:: ";
	elseif isInfo then
		prefix = "::INFO:: ";
	end;

	print(prefix .. "from the LiftAxle.lua: " .. tostring(errorMessage));
end;

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

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

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

local function getL10nText(text)
	if text ~= nil then
		if text:sub(1, 6) == "$l10n_" then
			text = g_i18n:getText(text:sub(7), LiftAxle.currentModName);
		elseif g_i18n:hasText(text, LiftAxle.currentModName) then
			text = g_i18n:getText(text, LiftAxle.currentModName);
		end;
	end;

	return tostring(text);
end;

function LiftAxle.initSpecialization()
	local xmlSchemaSavegame = Vehicle.xmlSchemaSavegame;

	xmlSchemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?)." .. LiftAxle.currentModName .. ".liftAxle#isDown", "Is lift axle down.", false);
	xmlSchemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?)." .. LiftAxle.currentModName .. ".liftAxle#hasChangedLockSteeringAxles", "Has changed lock steering axles.", false);
end;

function LiftAxle.prerequisitesPresent(specializations)
    return true;
end;

function LiftAxle.registerFunctions(vehicleType)
	local functionNames = {
		"setLiftAxleState",
		"getIsMaxFillLevelPercentReached",
		"getIsBought"
	};
	
	for _, functionName in ipairs(functionNames) do
		SpecializationUtil.registerFunction(vehicleType, functionName, LiftAxle[functionName]);
	end;
end;


function LiftAxle.registerEventListeners(vehicleType)
	local functionNames = {
		"onLoad",
		"onUpdate",
		"onWriteStream",
		"onReadStream",
		"onRegisterActionEvents"
	};

	for _, functionName in ipairs(functionNames) do
		SpecializationUtil.registerEventListener(vehicleType, functionName, LiftAxle);
	end;
end;

function LiftAxle:onLoad(savegame)
	self.spec_liftAxle = {};

	local specLiftAxle = self.spec_liftAxle;
	local specAttachable = self.spec_attachable;
	
	specLiftAxle.liftAxleAnimation = Utils.getNoNil(getXMLString(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#name"), "notDefined");
	
	printDebug("lift axle animation name = " .. specLiftAxle.liftAxleAnimation, 1, true);

	if specLiftAxle.liftAxleAnimation ~= "notDefined" then
		specLiftAxle.configurationName = Utils.getNoNil(getXMLString(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#configurationName"), "");
    	specLiftAxle.activeConfiguration = Utils.getNoNil(getXMLString(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#activeConfiguration"), "");
		specLiftAxle.isBought = self:getIsBought(specLiftAxle);
		specLiftAxle.invertHelpText = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#invertHelpText"), false);
		specLiftAxle.lockSteeringAxleWhenUp = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#lockSteeringAxleWhenUp"), false);
		specLiftAxle.isTrailer = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#isTrailer"), true);
		
		if not specLiftAxle.isBought then
			specLiftAxle.isBought = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#isAlwaysBuyed"), false);
		end;

		printDebug("Lift axle Is bought = '" .. tostring(specLiftAxle.isBought) .. "' in '" .. self:getFullName() .. "'.", 1, true);

		if specLiftAxle.isBought then
			specLiftAxle.isDown = true;
			specLiftAxle.hasFinishedFirstRun = false;
			specLiftAxle.hasChangedLockSteeringAxles = false;
			specLiftAxle.foundSteeringAxle = false;
			specLiftAxle.wasMovedDown = false;
			specLiftAxle.maxFillLevelPercentage = Utils.getNoNil(getXMLFloat(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#maxFillLevelPercentage"), 45) / 100;

			if savegame ~= nil and not savegame.resetVehicles then
				specLiftAxle.isDown = savegame.xmlFile:getValue(savegame.key .. "." .. LiftAxle.currentModName .. ".liftAxle#isDown", specLiftAxle.isDown);
				specLiftAxle.hasChangedLockSteeringAxles = savegame.xmlFile:getValue(savegame.key .. "." .. LiftAxle.currentModName .. ".liftAxle#hasChangedLockSteeringAxles", specLiftAxle.hasChangedLockSteeringAxles);
				
				self:setLiftAxleState(specLiftAxle.isDown);
			end;

			specLiftAxle.l10nTexts = {};

			specLiftAxle.l10nTexts.moveDown = getL10nText(Utils.getNoNil(getXMLString(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#moveDownText"), "MOVE_LIFT_AXLE_DOWN"));
			specLiftAxle.l10nTexts.moveUp = getL10nText(Utils.getNoNil(getXMLString(self.xmlFile.handle, "vehicle.wheels.liftAxleAnimation#moveUpText"), "MOVE_LIFT_AXLE_UP"));

			if specAttachable ~= nil and self.xmlFile:hasProperty("vehicle.attachable.steeringAxleAngleScale") and specAttachable.steeringAxleTargetNode == nil and specAttachable.steeringAxleReferenceComponentNode == nil then
				specLiftAxle.foundSteeringAxle = true;
			end;
		end;
	end;
end;

function LiftAxle:getIsBought(specLiftAxle)
	local isBought = false;

	if specLiftAxle.configurationName ~= "" and specLiftAxle.activeConfiguration ~= "" then
		local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName);

		if storeItem ~= nil then
			if storeItem.configurations ~= nil and storeItem.configurations[specLiftAxle.configurationName] ~= nil then
				local configurations = storeItem.configurations[specLiftAxle.configurationName];
				local config = configurations[self.configurations[specLiftAxle.configurationName]];
				local configurationName = getL10nText(specLiftAxle.activeConfiguration);

				isBought = config.name == configurationName;
			end;
		end;
	end;

	return isBought;
end;

function LiftAxle:onRegisterActionEvents(isActiveForInput)
	if self.isClient then
		local specLiftAxle = self.spec_liftAxle;

		self:clearActionEventsTable(self.spec_wheels.actionEvents);

		if self:getIsActiveForInput(true) then
            local newFunctions = {
				"LIFT_AXLE_BUTTON"
			};
			
			for _, newFunction in ipairs(newFunctions) do
				local _, actionEventId = self:addActionEvent(self.spec_wheels.actionEvents, InputAction[newFunction], self, LiftAxle.actionEventLiftAxleAnimation, false, true, false, true, nil);
			
				g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH);
				g_inputBinding:setActionEventTextVisibility(actionEventId, specLiftAxle.isBought);
				g_inputBinding:setActionEventActive(actionEventId, specLiftAxle.isBought);
			end;
		end;
	end;
end;

function LiftAxle:actionEventLiftAxleAnimation(actionName, inputValue, callbackState, isAnalog)
	local specLiftAxle = self.spec_liftAxle;

	if specLiftAxle ~= nil and specLiftAxle.liftAxleAnimation ~= "notDefined" then
		self:setLiftAxleState(not specLiftAxle.isDown);
	end;
end;

function LiftAxle:setLiftAxleState(isDown, noEventSend)
    local specLiftAxle = self.spec_liftAxle;

	if isDown ~= specLiftAxle.isDown then
		local direction = -1;

		if specLiftAxle.isDown then
			direction = 1;
		end;

		self:playAnimation(specLiftAxle.liftAxleAnimation, direction, nil, true);
	
		printDebug("specLiftAxle.isDown = " .. tostring(specLiftAxle.isDown), 1, true);
	
		specLiftAxle.isDown = isDown;

		SetLiftAxleStateEvent.sendEvent(self, isDown, noEventSend);
	end;
end;


function LiftAxle:onWriteStream(streamId, connection)
	if not connection:getIsServer() then 
		local specLiftAxle = self.spec_liftAxle;
		
		if specLiftAxle.liftAxleAnimation ~= "notDefined" and specLiftAxle.isBought then
			streamWriteBool(streamId, specLiftAxle.isDown);
		end;
	end;
end;

function LiftAxle:onReadStream(streamId, connection)
	if connection:getIsServer() then
		local specLiftAxle = self.spec_liftAxle;
		
		if specLiftAxle.liftAxleAnimation ~= "notDefined" and specLiftAxle.isBought then
			specLiftAxle.isDown = streamReadBool(streamId);
		end;
	end;
end;

function LiftAxle:getIsMaxFillLevelPercentReached(specLiftAxle, specFillUnit)
	local fillLevel = 0;
	local capacity = 0;
	local isMaxFillLevelPercentReached = false;
		
	if specFillUnit ~= nil then	
		for _, fillUnit in pairs(specFillUnit.fillUnits) do
			if fillUnit.showOnHud and g_fillTypeManager:getFillTypeByIndex(fillUnit.fillType).name ~= "SILAGE_ADDITIVE" then	
				fillLevel = fillLevel + fillUnit.fillLevel;
				capacity = capacity + fillUnit.capacity;
			end;
		end;

		isMaxFillLevelPercentReached = fillLevel / capacity >= specLiftAxle.maxFillLevelPercentage;
	end;

	return fillLevel == 0 and specLiftAxle.isTrailer, isMaxFillLevelPercentReached;
end;

function LiftAxle:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specLiftAxle = self.spec_liftAxle;

	if specLiftAxle.liftAxleAnimation ~= "notDefined" and specLiftAxle.isBought then
		if not specLiftAxle.hasFinishedFirstRun then	
			local direction = -1;

			if not specLiftAxle.isDown then
				direction = 1;
			end;
			
			specLiftAxle.wasMovedDown = specLiftAxle.isDown;

			self:playAnimation(specLiftAxle.liftAxleAnimation, direction, nil, true);
			AnimatedVehicle.updateAnimationByName(self, specLiftAxle.liftAxleAnimation, 9999999);

			specLiftAxle.hasFinishedFirstRun = true;
		end;

		local isEmpty, isMaxFillLevelPercentReached = self:getIsMaxFillLevelPercentReached(specLiftAxle, self.spec_fillUnit);
		
		if isMaxFillLevelPercentReached then
			if not specLiftAxle.isDown then
				self:setLiftAxleState(true);

				specLiftAxle.wasMovedDown = true;
			end;
		elseif isEmpty then
			if specLiftAxle.wasMovedDown then
				self:setLiftAxleState(false);

				specLiftAxle.wasMovedDown = false;
			end;
		end;

		local playAnimationButton = self.spec_wheels.actionEvents[InputAction.LIFT_AXLE_BUTTON];
		
		if playAnimationButton ~= nil then
			local specAttachable = self.spec_attachable;
			local currentText = specLiftAxle.l10nTexts.moveDown;

			local specLockSteeringAxles = self.spec_lockSteeringAxles;
			local currentTrailer = self;

			if specLockSteeringAxles == nil then
				if self.getAttachedImplements ~= nil then
					for _, trailer in pairs(self:getAttachedImplements()) do
						specLockSteeringAxles = trailer.object.spec_lockSteeringAxles;

						if specLockSteeringAxles ~= nil then
							currentTrailer = trailer.object;

							break;
						end;
					end;
				end;
			end;

			if specLiftAxle.invertHelpText then
				currentText = specLiftAxle.l10nTexts.moveUp;
			end;
			
			g_inputBinding:setActionEventActive(playAnimationButton.actionEventId, not isMaxFillLevelPercentReached);
			
			if specLiftAxle.isDown then
				currentText = specLiftAxle.l10nTexts.moveUp;
	
				if specLiftAxle.invertHelpText then
					currentText = specLiftAxle.l10nTexts.moveDown;
				end;

				if specLockSteeringAxles ~= nil then
					--## found my F22_lockSteeringAxles Mod
					
					if currentTrailer == self and specLiftAxle.hasChangedLockSteeringAxles then
						specLockSteeringAxles.lockSteeringAxle = false;
						specLiftAxle.hasChangedLockSteeringAxles = false;

						if self.isClient then
							local lockSteeringAxleButton = specLockSteeringAxles.actionEvents[InputAction.TOGGLE_LOCK_STEERING_AXLE_BUTTON];

							g_inputBinding:setActionEventActive(lockSteeringAxleButton.actionEventId, true);
							g_inputBinding:setActionEventTextVisibility(lockSteeringAxleButton.actionEventId, true);
						end;
					end;
				elseif currentTrailer == self and specLiftAxle.foundSteeringAxle and specAttachable ~= nil and not specAttachable.updateSteeringAxleAngle then
					specAttachable.updateSteeringAxleAngle = true;
				end;
			else
				if specLiftAxle.lockSteeringAxleWhenUp then
					if specLockSteeringAxles ~= nil then
						--## found my F22_lockSteeringAxles Mod

						if self.isClient then
							local lockSteeringAxleButton = specLockSteeringAxles.actionEvents[InputAction.TOGGLE_LOCK_STEERING_AXLE_BUTTON];

							g_inputBinding:setActionEventActive(lockSteeringAxleButton.actionEventId, false);
							g_inputBinding:setActionEventTextVisibility(lockSteeringAxleButton.actionEventId, false);
						end;

						if currentTrailer == self and not specLockSteeringAxles.lockSteeringAxle and currentTrailer.setSteeringAxleActive ~= nil then
							currentTrailer:setSteeringAxleActive(true, false);

							specLiftAxle.hasChangedLockSteeringAxles = true;
						end;
					elseif currentTrailer == self and specLiftAxle.foundSteeringAxle and specAttachable ~= nil and specAttachable.updateSteeringAxleAngle then
						if specAttachable.steeringAxleAngle ~= 0 then	
							if specAttachable.steeringAxleAngle > 0 then
								specAttachable.steeringAxleAngle = math.max(specAttachable.steeringAxleAngle - specAttachable.steeringAxleAngleSpeed / 2 * dt, 0);
							else
								specAttachable.steeringAxleAngle = math.min(specAttachable.steeringAxleAngle + specAttachable.steeringAxleAngleSpeed / 2 * dt, 0);
							end;
						else
							specAttachable.updateSteeringAxleAngle = false;
						end;
					end;
				end;
			end;
			
			g_inputBinding:setActionEventText(playAnimationButton.actionEventId, currentText);
		end;
	end;
end;

function LiftAxle:saveToXMLFile(xmlFile, key)
	local specLiftAxle = self.spec_liftAxle;
	
	if specLiftAxle.isDown then
		xmlFile:setValue(key .. "#isDown", specLiftAxle.isDown);
	end;
	
	if specLiftAxle.hasChangedLockSteeringAxles then
		xmlFile:setValue(key .. "#hasChangedLockSteeringAxles", specLiftAxle.hasChangedLockSteeringAxles);
	end;
end;

-----------------------------------------------------------------------------
--## Multiplayer Event
-----------------------------------------------------------------------------
SetLiftAxleStateEvent = {};
SetLiftAxleStateEvent_mt = Class(SetLiftAxleStateEvent, Event);

InitEventClass(SetLiftAxleStateEvent, "SetLiftAxleStateEvent");

function SetLiftAxleStateEvent.emptyNew()
	local self = Event.new(SetLiftAxleStateEvent_mt);
    
	return self;
end;

function SetLiftAxleStateEvent.new(trailer, isLiftAxleDown)
	local self = SetLiftAxleStateEvent.emptyNew();
	
	self.trailer = trailer;
	self.isLiftAxleDown = isLiftAxleDown;
	
	return self;
end;

function SetLiftAxleStateEvent:readStream(streamId, connection)
	self.trailer = NetworkUtil.readNodeObject(streamId);
	self.isLiftAxleDown = streamReadBool(streamId);
    
	self:run(connection);
end;

function SetLiftAxleStateEvent:writeStream(streamId, connection)
	NetworkUtil.writeNodeObject(streamId, self.trailer);
	streamWriteBool(streamId, self.isLiftAxleDown);
end;

function SetLiftAxleStateEvent:run(connection)
	if not connection:getIsServer() then
		g_server:broadcastEvent(SetLiftAxleStateEvent.new(self.trailer, self.isLiftAxleDown), nil, connection, self.trailer);
	end;
	
    if self.trailer ~= nil then
        self.trailer:setLiftAxleState(self.isLiftAxleDown, true);
	end;
end;

function SetLiftAxleStateEvent.sendEvent(trailer, isLiftAxleDown, noEventSend)
	if not noEventSend then
		if g_server ~= nil then
			g_server:broadcastEvent(SetLiftAxleStateEvent.new(trailer, isLiftAxleDown), nil, nil, trailer);
		else
			g_client:getServerConnection():sendEvent(SetLiftAxleStateEvent.new(trailer, isLiftAxleDown));
		end;
	end;
end;

