--[[
BuyableSteeringAxle

Script to buy steering axle for trailers with an configuration

@Author:	Ifko[nator]
@Date:		09.02.2020

@Version:	V2.0

@History:	V 1.0 @06.12.2017 - initial release in FS 17
			V 1.5 @08.03.2018 - support for steering axles with an steering node (Thanks to Crouwler who tell me about this!)
			V 2.0 @09.02.2020 - initial release in FS 19
]]

BuyableSteeringAxle = {};
BuyableSteeringAxle.currentModName = g_currentModName;

function BuyableSteeringAxle.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Wheels, specializations);
end;

function BuyableSteeringAxle.registerEventListeners(vehicleType)
	local functionNames = {
		"onLoad",
		"saveToXMLFile",
		"onUpdate",
		"onRegisterActionEvents",
		"onWriteStream",
		"onReadStream"
	};
	
	for _, functionName in ipairs(functionNames) do
		SpecializationUtil.registerEventListener(vehicleType, functionName, BuyableSteeringAxle);
	end;
end;

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

function BuyableSteeringAxle:onLoad(savegame)
	if FlieglTransportPackUtil == nil then
        return;
    end;

	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	local specWheels = FlieglTransportPackUtil.getSpecByName(self, "wheels");
	local specAttachable = FlieglTransportPackUtil.getSpecByName(self, "attachable");

	specBuyableSteeringAxle.isBought = false;
	
	if specWheels == nil or specAttachable == nil or #specWheels.wheels == 0 then
		return;
	end;
	
	specBuyableSteeringAxle.wheels = {};
    
    local steeringAxleNumber = 0;
    
    while true do
        local steeringAxleKey = "vehicle.buyableSteeringAxles.buyableSteeringAxle(" .. tostring(steeringAxleNumber) .. ")";
        
        if not hasXMLProperty(self.xmlFile, steeringAxleKey) then
            break;
        end;
        
        local steeringWheel = {};
        
        local repr = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, steeringAxleKey .. "#wheelRepr"), self.i3dMappings);
        local wheelNumber = getXMLInt(self.xmlFile, steeringAxleKey .. "#wheelNumber");
		
        if repr ~= nil or wheelNumber ~= nil then
			if repr ~= nil then
				steeringWheel.repr = repr;
			elseif wheelNumber ~= nil then
				steeringWheel.wheelNumber = wheelNumber;
			end;
			
			steeringWheel.configName = getXMLString(self.xmlFile, steeringAxleKey .. "#configName");
			steeringWheel.activeConfig = getXMLString(self.xmlFile, steeringAxleKey .. "#activeConfig");
        	
            if steeringWheel.configName ~= nil and steeringWheel.configName ~= "" and steeringWheel.activeConfig ~= nil and steeringWheel.activeConfig ~= "" then
				steeringWheel.steeringAxleScale = Utils.getNoNil(getXMLFloat(self.xmlFile, steeringAxleKey .. "#steeringAxleScale"), 0.3);
                steeringWheel.steeringAxleRotMin = MathUtil.degToRad(Utils.getNoNil(getXMLFloat(self.xmlFile, steeringAxleKey .. "#steeringAxleRotMin"), -15));
                steeringWheel.steeringAxleRotMax = MathUtil.degToRad(Utils.getNoNil(getXMLFloat(self.xmlFile, steeringAxleKey .. "#steeringAxleRotMax"), 15));
				
				local steeringNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, steeringAxleKey .. "#steeringNode"), self.i3dMappings);
				
				if steeringNode ~= nil then
					steeringWheel.steeringNode = steeringNode;
					steeringWheel.steeringNodeMinTransX = Utils.getNoNil(getXMLFloat(self.xmlFile, steeringAxleKey .. "#steeringNodeMinTransX"), 0.116);
					steeringWheel.steeringNodeMaxTransX = Utils.getNoNil(getXMLFloat(self.xmlFile, steeringAxleKey .. "#steeringNodeMaxTransX"), -0.116);
				end;
				
                table.insert(specBuyableSteeringAxle.wheels, steeringWheel);
            end;
        end;
        
        steeringAxleNumber = steeringAxleNumber + 1;
    end;
	
	if #specBuyableSteeringAxle.wheels > 0 then
        local storeItem = g_storeManager:getItemByXMLFilename(self.configFileName)
        
        for _, steeringWheel in pairs(specBuyableSteeringAxle.wheels) do
            if storeItem.configurations ~= nil and storeItem.configurations[steeringWheel.configName] ~= nil then
				local configurations = storeItem.configurations[steeringWheel.configName];
                local config = configurations[self.configurations[steeringWheel.configName]];
                
                for wheelNumber, wheel in pairs(specWheels.wheels) do
                    if (steeringWheel.repr ~= nil and wheel.repr == steeringWheel.repr) or wheelNumber == steeringWheel.wheelNumber then	
                        if g_i18n:hasText(steeringWheel.activeConfig) then
							steeringWheel.activeConfig = g_i18n:getText(steeringWheel.activeConfig);
						end;
						
						if config.name == steeringWheel.activeConfig then
                            wheel.steeringAxleScale = steeringWheel.steeringAxleScale;
                            wheel.steeringAxleRotMin = steeringWheel.steeringAxleRotMin;
                            wheel.steeringAxleRotMax = steeringWheel.steeringAxleRotMax;
							
							if steeringWheel.steeringNode ~= nil then
								wheel.steeringNode = steeringWheel.steeringNode;
								wheel.steeringNodeMinTransX = steeringWheel.steeringNodeMinTransX;
								wheel.steeringNodeMaxTransX = steeringWheel.steeringNodeMaxTransX;
								
								if wheel.steeringNodeMaxRot == 0 then	
									wheel.steeringNodeMaxRot = wheel.steeringAxleRotMax;
								end;
								
								if wheel.steeringNodeMinRot == -0 then
									wheel.steeringNodeMinRot = wheel.steeringAxleRotMin;
								end;
							end;
							
							specBuyableSteeringAxle.isBought = true;
							specAttachable.steeringAxleUpdateBackwards = true;
                        else
                            wheel.steeringAxleScale = 0;
                            wheel.steeringAxleRotMin = 0;
                            wheel.steeringAxleRotMax = 0;
							wheel.steeringNodeMaxRot = 0;
							wheel.steeringNodeMinRot = 0;
							
							if steeringWheel.steeringNode ~= nil then
								wheel.steeringNodeMinTransX = 0;
								wheel.steeringNodeMaxTransX = 0;
							end;
                        end;
                    end;
                end;
            end;
        end;
    end;
	
	specBuyableSteeringAxle.lockSteeringAxle = false;
	specBuyableSteeringAxle.steerAxleIsLocked = false;
	
	if savegame ~= nil and not savegame.resetVehicles then
		specBuyableSteeringAxle.lockSteeringAxle = Utils.getNoNil(getXMLBool(savegame.xmlFile, savegame.key .. BuyableSteeringAxle.currentModName .. ".buyableSteeringAxle#lockSteeringAxle"), false);
	end;
	
	self:setSteeringAxle(specBuyableSteeringAxle.lockSteeringAxle, false);
end;

function BuyableSteeringAxle:onWriteStream(streamId, connection)
	if self.setSteeringAxle ~= nil then	
		local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");

		streamWriteBool(streamId, specBuyableSteeringAxle.steerAxleIsLocked);
	end;
end;

function BuyableSteeringAxle:onReadStream(streamId, connection)
	if self.setSteeringAxle ~= nil then	
		local steerAxleIsLocked = streamReadBool(streamId);

		self:setSteeringAxle(steerAxleIsLocked, true);
	end;
end;

function BuyableSteeringAxle:onRegisterActionEvents(isActiveForInput)
	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	
	if self.isClient and specBuyableSteeringAxle.isBought then
		self:clearActionEventsTable(specBuyableSteeringAxle.actionEvents);
        
		if self:getIsActiveForInput(true) then
            local actionEventId;
            
			_, actionEventId = self:addActionEvent(specBuyableSteeringAxle.actionEvents, InputAction.SET_STEERING_AXLE_BUTTON, self, BuyableSteeringAxle.toggleSteeringAxleActive, false, true, false, true, nil);
			
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL);
			g_inputBinding:setActionEventTextVisibility(actionEventId, true);
			g_inputBinding:setActionEventActive(actionEventId, false);
		end;
	end;
end;

function BuyableSteeringAxle.toggleSteeringAxleActive(self, actionName, inputValue, callbackState, isAnalog)
	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	
	self:setSteeringAxle(not specBuyableSteeringAxle.lockSteeringAxle, false);
end;

function BuyableSteeringAxle:onUpdate(dt)
	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	local specAttachable = FlieglTransportPackUtil.getSpecByName(self, "attachable");
	
	if specBuyableSteeringAxle.isBought then
		if self.isClient then	
			specAttachable.updateSteeringAxleAngle = not specBuyableSteeringAxle.lockSteeringAxle;
			
			local lockSteeringAxleButton = specBuyableSteeringAxle.actionEvents[InputAction.SET_STEERING_AXLE_BUTTON];
			
			if lockSteeringAxleButton ~= nil and self:getIsActive() then
				local currentText = g_i18n:getText("LOCK_STEERING_AXLE");

				if specAttachable.steeringAxleTargetNode ~= nil then
					currentText = g_i18n:getText("LOCK_STEERING_DRAWBAR");
				end;
				
				if specBuyableSteeringAxle.lockSteeringAxle then
					currentText = g_i18n:getText("UNLOCK_STEERING_AXLE");

					if specAttachable.steeringAxleTargetNode ~= nil then
						currentText = g_i18n:getText("UNLOCK_STEERING_DRAWBAR");
					end;
				end;
				
				g_inputBinding:setActionEventActive(lockSteeringAxleButton.actionEventId, true);
				g_inputBinding:setActionEventText(lockSteeringAxleButton.actionEventId, currentText);
			end;
		end;

		if specBuyableSteeringAxle.lockSteeringAxle 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;

				if specAttachable.steeringAxleTargetNode ~= nil then
           	   		local angle = MathUtil.clamp(specAttachable.steeringAxleAngle, specAttachable.steeringAxleAngleMinRot, specAttachable.steeringAxleAngleMaxRot)

					if angle > 0 then
						angle = math.max(angle - specAttachable.steeringAxleAngleSpeed * dt, 0);
					else
						angle = math.min(angle + specAttachable.steeringAxleAngleSpeed * dt, 0);
					end;

					setRotation(specAttachable.steeringAxleTargetNode, 0, angle, 0)

					self:setMovingToolDirty(specAttachable.steeringAxleTargetNode);
				end;
			end;
		end;
	end;

	if specAttachable.updateSteeringAxleAngle then
		local baseVehicle = self:getSteeringAxleBaseVehicle();

		if baseVehicle ~= nil and (self.movingDirection >= 0 or specAttachable.steeringAxleUpdateBackwards) then
			local yRot = Utils.getYRotationBetweenNodes(self.steeringAxleNode, baseVehicle.steeringAxleNode);
			
			if math.abs(yRot) > 1.57 then
				if yRot > 0 then
					yRot = yRot - 3.14;
				else
					yRot = yRot + 3.14;
				end;
			end;
			
			local startSpeed = specAttachable.steeringAxleAngleScaleStart;
			local endSpeed = specAttachable.steeringAxleAngleScaleEnd;
			local scale = MathUtil.clamp(1 + (self:getLastSpeed() - startSpeed) * 1.0 / (startSpeed - endSpeed), 0, 1);
			
			specAttachable.steeringAxleTargetAngle = yRot * scale;
		elseif self:getLastSpeed() > 0.2 then
			specAttachable.steeringAxleTargetAngle = 0;
		end;

		local dir = MathUtil.sign(specAttachable.steeringAxleTargetAngle - specAttachable.steeringAxleAngle);
		
		if dir == 1 then
			specAttachable.steeringAxleAngle = math.min(specAttachable.steeringAxleAngle + dir * dt * specAttachable.steeringAxleAngleSpeed, specAttachable.steeringAxleTargetAngle);
		else
			specAttachable.steeringAxleAngle = math.max(specAttachable.steeringAxleAngle + dir * dt * specAttachable.steeringAxleAngleSpeed, specAttachable.steeringAxleTargetAngle);
		end;
		
		if specAttachable.steeringAxleTargetNode ~= nil then
			local angle = MathUtil.clamp(specAttachable.steeringAxleAngle, specAttachable.steeringAxleAngleMinRot, specAttachable.steeringAxleAngleMaxRot);
			
			setRotation(specAttachable.steeringAxleTargetNode, 0, angle, 0);
			self:setMovingToolDirty(specAttachable.steeringAxleTargetNode);
		end;
	end;
end;

function BuyableSteeringAxle:saveToXMLFile(xmlFile, key)
	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	
	if specBuyableSteeringAxle.isBought then
		setXMLBool(xmlFile, key .. "#lockSteeringAxle", specBuyableSteeringAxle.lockSteeringAxle);
	end;
end;

function BuyableSteeringAxle:setSteeringAxle(lockSteeringAxle, noEventSend)
	local specBuyableSteeringAxle = FlieglTransportPackUtil.getSpecByName(self, "buyableSteeringAxle");
	
	if lockSteeringAxle ~= specBuyableSteeringAxle.lockSteeringAxle then
		specBuyableSteeringAxle.lockSteeringAxle = lockSteeringAxle;
		
		if noEventSend == nil or noEventSend == false then
			if g_server ~= nil then
				g_server:broadcastEvent(BuyableSteeringAxleEvent:new(self, lockSteeringAxle), nil, nil, self);
			else
				g_client:getServerConnection():sendEvent(BuyableSteeringAxleEvent:new(self, lockSteeringAxle));
			end;
		end;
	end;
end;

--## MP Stuff

--## turn steering axle on/off

BuyableSteeringAxleEvent = {};
BuyableSteeringAxleEvent_mt = Class(BuyableSteeringAxleEvent, Event);

InitEventClass(BuyableSteeringAxleEvent, "BuyableSteeringAxleEvent");

function BuyableSteeringAxleEvent:emptyNew()
	local self = Event:new(BuyableSteeringAxleEvent_mt);
    
	return self;
end;

function BuyableSteeringAxleEvent:new(trailer, lockSteeringAxle)
	local self = BuyableSteeringAxleEvent:emptyNew();
	
	self.trailer = trailer;
	self.steerAxleIsLocked = lockSteeringAxle;
	
	return self;
end;

function BuyableSteeringAxleEvent:readStream(streamId, connection)
	self.trailer = NetworkUtil.readNodeObject(streamId);
	self.steerAxleIsLocked = streamReadBool(streamId);

    self:run(connection);
end;

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

function BuyableSteeringAxleEvent:run(connection)
	self.trailer:setSteeringAxle(self.steerAxleIsLocked, true);
	
	if not connection:getIsServer() then
		g_server:broadcastEvent(BuyableSteeringAxleEvent:new(self.trailer, self.steerAxleIsLocked), nil, connection, self.trailer);
	end;
end;