--[[
VariableTipHeight

Specialization for different tip angles on an trailer

Author:		Ifko[nator]
Datum:		29.11.2022

Version:	v1.1

History:	v1.0 @26.11.2022 - initial implementation in FS 22
			--------------------------------------------------
			v1.1 @29.11.2002 - some improvements
]]

VariableTipHeight = {};

VariableTipHeight.currentModName = g_currentModName;

for currentStepNumber, currentStep in pairs({"25", "50", "75", "100"}) do
	VariableTipHeight["STEP_" .. currentStep .. "_PERCENT"] = currentStepNumber;
end;

local function getSpecByName(self, specName, currentModName)
	local spec = self["spec_" .. Utils.getNoNil(currentModName, VariableTipHeight.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), VariableTipHeight.currentModName);
	elseif g_i18n:hasText(text, VariableTipHeight.currentModName) then
		text = g_i18n:getText(text, VariableTipHeight.currentModName);
	end;

	return text;
end;

function VariableTipHeight.initSpecialization()
	Vehicle.xmlSchemaSavegame:register(XMLValueType.INT, "vehicles.vehicle(?)." .. VariableTipHeight.currentModName .. ".variableTipHeight#currentStep", "Current selected tip height.", 4);
end;

function VariableTipHeight.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Trailer, specializations);
end;

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

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

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

function VariableTipHeight:onLoad(savegame)
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");
	local specTrailer = getSpecByName(self, "trailer");

	local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];

	if tipSide ~= nil then
		specVariableTipHeight.currentStep = VariableTipHeight.STEP_100_PERCENT;
		specVariableTipHeight.currentStepSync = VariableTipHeight.STEP_100_PERCENT;

		if savegame ~= nil then
			specVariableTipHeight.currentStep = savegame.xmlFile:getValue(savegame.key .. "." .. VariableTipHeight.currentModName .. ".variableTipHeight#currentStep", specVariableTipHeight.currentStep);
		end;

		self:setTipHeight(specVariableTipHeight.currentStep, false);

		specVariableTipHeight.l10nTexts = {};

		local l10nTexts = {
			"input_CHANGE_TIP_HEIGHT_BUTTON",
			"STEP_25_PERCENT",
			"STEP_50_PERCENT",
			"STEP_75_PERCENT",
			"STEP_100_PERCENT"
		};

		for _, l10nText in pairs(l10nTexts) do
			specVariableTipHeight.l10nTexts[l10nText] = getL10nText(l10nText);
		end;
	end;
end;

function VariableTipHeight:onRegisterActionEvents(isActiveForInput)
	if self.isClient then
		local specVariableTipHeight = getSpecByName(self, "variableTipHeight");
		local specTrailer = getSpecByName(self, "trailer");

		local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];
	
		if tipSide ~= nil then
			self:clearActionEventsTable(specVariableTipHeight.actionEvents);

			if self:getIsActiveForInput(true) then
        	    local newFunctions = {
					"CHANGE_TIP_HEIGHT_BUTTON"
				};

				for _, newFunction in ipairs(newFunctions) do
					local _, actionEventId = self:addActionEvent(specVariableTipHeight.actionEvents, InputAction[newFunction], self, VariableTipHeight.actionEventChangeTipHeight, false, true, false, true, nil);
				
					g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH);
					g_inputBinding:setActionEventTextVisibility(actionEventId, true);
					g_inputBinding:setActionEventActive(actionEventId, true);

					local _, currentText = self:getCurrentMaxTipHeightAndCurrentText();

					g_inputBinding:setActionEventText(actionEventId, specVariableTipHeight.l10nTexts.input_CHANGE_TIP_HEIGHT_BUTTON .. " " .. currentText);
				end;
			end;
		end;
	end;
end;

function VariableTipHeight:actionEventChangeTipHeight(actionName, inputValue, callbackState, isAnalog)
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");

	if specVariableTipHeight.currentStep < VariableTipHeight.STEP_100_PERCENT then
		specVariableTipHeight.currentStepSync = specVariableTipHeight.currentStepSync + 1;
	else
		specVariableTipHeight.currentStepSync = VariableTipHeight.STEP_25_PERCENT;
	end;

	self:setTipHeight(specVariableTipHeight.currentStepSync, false);
end;

function VariableTipHeight:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");
	local specTrailer = getSpecByName(self, "trailer");

	local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];

	if tipSide ~= nil then
		local specTrailer = getSpecByName(self, "trailer");
		local specDischargeable = getSpecByName(self, "dischargeable");
		local specAnimatedVehicle = getSpecByName(self, "animatedVehicle");

		local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];

		local actionEventChangeTipHeightButton = specVariableTipHeight.actionEvents[InputAction.CHANGE_TIP_HEIGHT_BUTTON];

		if tipSide ~= nil and actionEventChangeTipHeightButton ~= nil then
			local dischargeNode = specDischargeable.currentDischargeNode;

			local currentMaxTipHeight, currentText = self:getCurrentMaxTipHeightAndCurrentText();

			if dischargeNode ~= nil then
				if dischargeNode.savedEmptySpeed == nil then
					dischargeNode.savedEmptySpeed = dischargeNode.emptySpeed;
				end;

				dischargeNode.emptySpeed = dischargeNode.savedEmptySpeed * currentMaxTipHeight;
			end;

			g_inputBinding:setActionEventText(actionEventChangeTipHeightButton.actionEventId, specVariableTipHeight.l10nTexts.input_CHANGE_TIP_HEIGHT_BUTTON .. " " .. currentText);
			g_inputBinding:setActionEventActive(actionEventChangeTipHeightButton.actionEventId, specTrailer.tipState == Trailer.TIPSTATE_CLOSED);
		end;
	end;
end;

function VariableTipHeight:onUpdateTrailer(superFunc, dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specTrailer = getSpecByName(self, "trailer");

	if specTrailer.tipSideCount > 1 then
		local actionEvent = specTrailer.actionEvents[InputAction.TOGGLE_TIPSIDE];

		if actionEvent ~= nil then
			local state = self:getCanTogglePreferdTipSide();

			g_inputBinding:setActionEventActive(actionEvent.actionEventId, state);

			if state then
				local text = string.format(specTrailer.infoText, specTrailer.tipSides[specTrailer.preferedTipSideIndex].name);

				g_inputBinding:setActionEventText(actionEvent.actionEventId, text);
			end;
		end;
	end;

	if specTrailer.tipSideUpdateDirty then
		local tipState = self:getTipState();

		if tipState == Trailer.TIPSTATE_CLOSED then
			self:setPreferedTipSide(self:getNextAvailableTipSide(specTrailer.preferedTipSideIndex));

			specTrailer.tipSideUpdateDirty = false;
		end;
	end;

	local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];

	if tipSide ~= nil then
		if tipSide.manualTipToggle then
			local actionEvent = specTrailer.actionEvents[tipSide.manualTipToggleAction];

			if actionEvent ~= nil then
				local text = nil;
				local tipState = self:getTipState();

				if tipState == Trailer.TIPSTATE_CLOSED or tipState == Trailer.TIPSTATE_CLOSING then
					text = tipSide.manualTipToggleActionTextPos;
				else
					text = tipSide.manualTipToggleActionTextNeg;
				end

				g_inputBinding:setActionEventText(actionEvent.actionEventId, text);
			end;
		end;

		if tipSide.manualDoorToggle then
			local actionEvent = specTrailer.actionEvents[tipSide.manualDoorToggleAction];

			if actionEvent ~= nil then
				local text = nil;

				if self:getIsAnimationPlaying(tipSide.doorAnimation.name) then
					if self:getAnimationSpeed(tipSide.doorAnimation.name) > 0 then
						text = tipSide.manualDoorToggleActionTextNeg;
					else
						text = tipSide.manualDoorToggleActionTextPos;
					end;
				elseif self:getAnimationTime(tipSide.doorAnimation.name) <= 0 then
					text = tipSide.manualDoorToggleActionTextPos;
				else
					text = tipSide.manualDoorToggleActionTextNeg;
				end;

				g_inputBinding:setActionEventText(actionEvent.actionEventId, text);
			end;
		end;
	end;

	if specTrailer.tipState == Trailer.TIPSTATE_OPENING then
		tipSide = specTrailer.tipSides[specTrailer.currentTipSideIndex];

		local currentMaxTipHeight = 1;

		if self.getCurrentMaxTipHeightAndCurrentText ~= nil then
			currentMaxTipHeight, _ = self:getCurrentMaxTipHeightAndCurrentText();
		end;

		if tipSide ~= nil and (self:getAnimationTime(tipSide.animation.name) >= 1 * currentMaxTipHeight or self:getAnimationDuration(tipSide.animation.name) == 0) then
			specTrailer.tipState = Trailer.TIPSTATE_OPEN;

			self:stopAnimation(tipSide.animation.name);
		end;
	elseif specTrailer.tipState == Trailer.TIPSTATE_CLOSING then
		tipSide = specTrailer.tipSides[specTrailer.currentTipSideIndex];

		if tipSide ~= nil and (self:getAnimationTime(tipSide.animation.name) <= 0 or self:getAnimationDuration(tipSide.animation.name) == 0 or tipSide.animation.closeSpeedScale == 0) then
			specTrailer.tipState = Trailer.TIPSTATE_CLOSED;

			self:endTipping();
		end;
	end;
end;

Trailer.onUpdate = Utils.overwrittenFunction(Trailer.onUpdate, VariableTipHeight.onUpdateTrailer)

function VariableTipHeight:getCurrentMaxTipHeightAndCurrentText()
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");

	local currentMaxTipHeight = 0.25;
	local currentText = specVariableTipHeight.l10nTexts.STEP_25_PERCENT;
	
	for _, currentStep in pairs({"50", "75", "100"}) do
		if specVariableTipHeight.currentStep == VariableTipHeight["STEP_" .. currentStep .. "_PERCENT"] then
			currentText = specVariableTipHeight.l10nTexts["STEP_" .. currentStep .. "_PERCENT"];
			currentMaxTipHeight = tonumber(currentStep) / 100;
		end;
	end;

	return currentMaxTipHeight, currentText;
end;

function VariableTipHeight:setTipHeight(currentStep, noEventSend)
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");
	
	if currentStep ~= specVariableTipHeight.currentStep then
		specVariableTipHeight.currentStep = currentStep;
		
		VariableTipHeightSetTipHeightEvent.sendEvent(self, currentStep, noEventSend);
	end;
end;

function VariableTipHeight:saveToXMLFile(xmlFile, key)
	local specVariableTipHeight = getSpecByName(self, "variableTipHeight");
	local specTrailer = getSpecByName(self, "trailer");

	local tipSide = specTrailer.tipSides[specTrailer.preferedTipSideIndex];

	if tipSide ~= nil then
		xmlFile:setValue(key .. "#currentStep", specVariableTipHeight.currentStep);
	end;
end;

--------------------------------------------------------------------------------------------------------------------------
----------------------------------------VariableTipHeightSetTipHeightEvent------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------

VariableTipHeightSetTipHeightEvent = {};

local VariableTipHeightSetTipHeightEvent_mt = Class(VariableTipHeightSetTipHeightEvent, Event);

InitEventClass(VariableTipHeightSetTipHeightEvent, "VariableTipHeightSetTipHeightEvent");

function VariableTipHeightSetTipHeightEvent.emptyNew()
	local self = Event.new(VariableTipHeightSetTipHeightEvent_mt);
    
	return self;
end;

function VariableTipHeightSetTipHeightEvent.new(trailer, currentStep)
	local self = VariableTipHeightSetTipHeightEvent.emptyNew();
	
	self.trailer = trailer;
	self.currentStep = currentStep;

	return self;
end;

function VariableTipHeightSetTipHeightEvent:readStream(streamId, connection)
	self.trailer = NetworkUtil.readNodeObject(streamId);
	self.currentStep = streamReadUInt8(streamId);
	
	self:run(connection);
end;

function VariableTipHeightSetTipHeightEvent:writeStream(streamId, connection)
	NetworkUtil.writeNodeObject(streamId, self.trailer);

	streamWriteUInt8(streamId, self.currentStep);
end;

function VariableTipHeightSetTipHeightEvent:run(connection)
	if not connection:getIsServer() then
		g_server:broadcastEvent(VariableTipHeightSetTipHeightEvent.new(self.trailer, self.currentStep), nil, connection, self.trailer);
	end;
	
    if self.trailer ~= nil then
        self.trailer:setTipHeight(self.currentStep, true);
	end;
end;

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