--[[
IndependentLinkSuspension

Author: 	Ifko[nator]
Date:		20.12.2024
Version: 	2.0

History:	v1.0 @08.09.2022 - initial implementation in FS 22
			----------------------------------------------------------------------------------------
			v1.1 @04.11.2023 - it's now required to start the motor to raise or lower the supension
			----------------------------------------------------------------------------------------
			v2.0 @20.12.2024 - convert to FS 25
]]

IndependentLinkSuspension = {};
IndependentLinkSuspension.currentModName = g_currentModName;

local function getSpecByName(self, specName, currentModName)
    local spec = self["spec_" .. Utils.getNoNil(currentModName, IndependentLinkSuspension.currentModName) .. "." .. specName];

	if spec ~= nil then
        return spec;
    end;

    return self["spec_" .. specName];
end;

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

	return text;
end;

local function getVectorN(str, num)
	if str == nil then
		return nil;
	end;

	local vals = str:trim():split(" ");

	num = num or #vals;

	if num == 0 then
		return nil;
	end;

	if #vals ~= num then
		print("Error: Invalid " .. num .. "-vector '" .. str .. "'");

		return nil;
	end;

	local results = {};

	for i = 1, num do
		table.insert(results, vals[i] == "-" and 0 or tonumber(vals[i]));
	end;

	return results;
end;

function IndependentLinkSuspension.initSpecialization()
	Vehicle.xmlSchemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?)." .. IndependentLinkSuspension.currentModName .. ".independentLinkSuspension#isActive", "Is ILS active.", false);
end;

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

function IndependentLinkSuspension.registerFunctions(vehicleType)
	local newFunctions = {
		"setIndependentLinkSuspensionState"
	};
	
	for _, newFunction in ipairs(newFunctions) do
		SpecializationUtil.registerFunction(vehicleType, newFunction, IndependentLinkSuspension[newFunction]);
	end;
end;

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

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

function IndependentLinkSuspension:onPreLoad(savegame)
	self.spec_independentLinkSuspension = getSpecByName(self, "independentLinkSuspension");

	local specIndependentLinkSuspension = self.spec_independentLinkSuspension;

	specIndependentLinkSuspension.isBought = false;
	specIndependentLinkSuspension.xmlFile = self.xmlFile.handle;

	local configName = Utils.getNoNil(getXMLString(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#configName"), "");
	local activeConfigs = Utils.getNoNil(getXMLString(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#activeConfigs"), "");
			
    if configName ~= "" and activeConfig ~= "" then
		local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName);
			
        if storeItem ~= nil and storeItem.configurations ~= nil and storeItem.configurations[configName] ~= nil then
			local activeConfigs = string.split(activeConfigs, ", ");
            local configurations = storeItem.configurations[configName];
            local config = configurations[self.configurations[configName]];
				
            for _, activeConfig in pairs(activeConfigs) do
				activeConfig = getL10nText(activeConfig);
						
				if config.name == activeConfig then	
					specIndependentLinkSuspension.isBought = true;

					break;
				end;
            end;
        end;
    end;
end;

function IndependentLinkSuspension:onLoad(savegame)
	local specIndependentLinkSuspension = self.spec_independentLinkSuspension;
	
	if specIndependentLinkSuspension.isBought then
		specIndependentLinkSuspension.animationName = Utils.getNoNil(getXMLString(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#animationName"));
		specIndependentLinkSuspension.activateText = Utils.getNoNil(getXMLString(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#activateText"), "Activate ILS");
		specIndependentLinkSuspension.deactivateText = Utils.getNoNil(getXMLString(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#deactivateText"), "Deactivate ILS");
		specIndependentLinkSuspension.maxSpeed = Utils.getNoNil(getXMLInt(specIndependentLinkSuspension.xmlFile, "vehicle.independentLinkSuspension#maxSpeed"), 21);
		specIndependentLinkSuspension.isActive = false;
		
		if specIndependentLinkSuspension.animationName ~= "" then
			if savegame ~= nil then
				specIndependentLinkSuspension.isActive = savegame.xmlFile:getValue(savegame.key .. "." .. IndependentLinkSuspension.currentModName .. ".independentLinkSuspension#isActive", specIndependentLinkSuspension.isActive);
			end;
		
			self:setIndependentLinkSuspensionState(specIndependentLinkSuspension.isActive);
		end;
	end;
end;

function IndependentLinkSuspension:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specIndependentLinkSuspension = self.spec_independentLinkSuspension;
	
	local independentLinkSuspensionButton = specIndependentLinkSuspension.actionEvents[InputAction.ILS_ACTIVATION_BUTTON];
	local currentSpeed = math.floor(self:getLastSpeed());

	if specIndependentLinkSuspension.isBought then
		local currentText = specIndependentLinkSuspension.deactivateText;

		if not specIndependentLinkSuspension.isActive then
			currentText = specIndependentLinkSuspension.activateText;
		end;

		if independentLinkSuspensionButton ~= nil then
			g_inputBinding:setActionEventText(independentLinkSuspensionButton.actionEventId, getL10nText(currentText));
			g_inputBinding:setActionEventActive(independentLinkSuspensionButton.actionEventId, currentSpeed <= specIndependentLinkSuspension.maxSpeed);
			g_inputBinding:setActionEventTextVisibility(independentLinkSuspensionButton.actionEventId, currentSpeed <= specIndependentLinkSuspension.maxSpeed);
		end;

		if currentSpeed >= specIndependentLinkSuspension.maxSpeed and specIndependentLinkSuspension.isActive then
			--## Lift axle, if tractor is faster than allowed
			self:setIndependentLinkSuspensionState(false);
		end;
	else
		if independentLinkSuspensionButton ~= nil then
			g_inputBinding:setActionEventActive(independentLinkSuspensionButton.actionEventId, false);
			g_inputBinding:setActionEventTextVisibility(independentLinkSuspensionButton.actionEventId, false);
		end;
	end;
end;

function IndependentLinkSuspension:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
	if self.isClient then
		local specIndependentLinkSuspension = self.spec_independentLinkSuspension;

		self:clearActionEventsTable(specIndependentLinkSuspension.actionEvents);
		
		if isActiveForInputIgnoreSelection then
			local _, actionEventId = self:addPoweredActionEvent(specIndependentLinkSuspension.actionEvents, InputAction.ILS_ACTIVATION_BUTTON, self, IndependentLinkSuspension.actionEventToggleState, false, true, false, true, nil);
			
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL);
			g_inputBinding:setActionEventTextVisibility(actionEventId, true);
			g_inputBinding:setActionEventActive(actionEventId, true);
			g_inputBinding:setActionEventText(actionEventId, getL10nText("input_ILS_ACTIVATION_BUTTON"));
		end;
	end;
end;

function IndependentLinkSuspension:setIndependentLinkSuspensionState(isActive, noEventSend)
	local specIndependentLinkSuspension = self.spec_independentLinkSuspension;

    if isActive ~= specIndependentLinkSuspension.isActive then
		if not noEventSend then
			if g_server ~= nil then
				g_server:broadcastEvent(IndependentLinkSuspensionEvent.new(self, isActive), nil, nil, self);
			else
				g_client:getServerConnection():sendEvent(IndependentLinkSuspensionEvent.new(self, isActive));
			end;
		end;

		specIndependentLinkSuspension.isActive = isActive;

		local animDirection = -1;
	
		if specIndependentLinkSuspension.isActive then
			animDirection = 1;
		end;
			
		self:playAnimation(specIndependentLinkSuspension.animationName, animDirection, nil, true);
	end;
end;

function IndependentLinkSuspension:onReadStream(streamId, timestamp, connection)
    self:setIndependentLinkSuspensionState(streamReadBool(streamId), true);
end;

function IndependentLinkSuspension:onWriteStream(streamId, connection)
	streamWriteBool(streamId, self.spec_independentLinkSuspension.isActive);
end;

function IndependentLinkSuspension.actionEventToggleState(self, actionName, inputValue, callbackState, isAnalog)
	self:setIndependentLinkSuspensionState(not self.spec_independentLinkSuspension.isActive);
end;

function IndependentLinkSuspension:saveToXMLFile(xmlFile, key, usedModNames)
	local specIndependentLinkSuspension = self.spec_independentLinkSuspension;

	if specIndependentLinkSuspension.isBought and specIndependentLinkSuspension.isActive then
		xmlFile:setValue(key .. "#isActive", specIndependentLinkSuspension.isActive);
	end;
end;

-----------------------------------------------------------------------------
--## Multiplayer Event
-----------------------------------------------------------------------------

IndependentLinkSuspensionEvent = {};
IndependentLinkSuspensionEvent_mt = Class(IndependentLinkSuspensionEvent, Event);

InitEventClass(IndependentLinkSuspensionEvent, "IndependentLinkSuspensionEvent");

function IndependentLinkSuspensionEvent.emptyNew()
	local self = Event.new(IndependentLinkSuspensionEvent_mt);
    
	return self;
end;

function IndependentLinkSuspensionEvent.new(tractor, isActive)
	local self = IndependentLinkSuspensionEvent.emptyNew();
	
	self.tractor = tractor;
	self.isActive = isActive;

	return self;
end;

function IndependentLinkSuspensionEvent:readStream(streamId, connection)
	self.tractor = NetworkUtil.readNodeObject(streamId);
	self.isActive = streamReadBool(streamId);
	
	self:run(connection);
end;

function IndependentLinkSuspensionEvent:writeStream(streamId, connection)
	NetworkUtil.writeNodeObject(streamId, self.tractor);

	streamWriteBool(streamId, self.isActive);
end;

function IndependentLinkSuspensionEvent:run(connection)
	if not connection:getIsServer() then
		g_server:broadcastEvent(IndependentLinkSuspensionEvent.new(self.tractor, self.isActive), nil, connection, self.tractor);
	end;
	
    if self.tractor ~= nil then
        self.tractor:setIndependentLinkSuspensionState(self.isActive, true);
	end;
end;