--[[
DynamicPalletAttacher

Author: 	Ifko[nator]
Date:		13.12.2020
Version: 	1.4

History:	v1.0 @16.08.2020 - initial implemation in FS 19
			V1.1 @28.08.2020 - added missing onDelete function for the trigger
			V1.2 @30.09.2020 - added possiblity to fill fillabel pallets in an trigger, when they are attached
			V1.3 @06.12.2020 - fixed following errors: 
								Error: Running LUA method 'update'.
								dataS/scripts/utils/DynamicMountUtil.lua(94) : attempt to call method 'addDynamicMountedObject' (a nil value)

								Error: Running LUA method 'update'.
								dataS/scripts/utils/DynamicMountUtil.lua(104) : attempt to call method 'removeDynamicMountedObject' (a nil value)
			V1.4 @13.12.2020 - fixed following error: 
								Error: Running LUA method 'update'.
								dataS/scripts/utils/DynamicMountUtil.lua(211) : attempt to call method 'palletOnFork' (a nil value)
]]

DynamicPalletAttacher = {};
DynamicPalletAttacher.currentModName = g_currentModName;

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

function DynamicPalletAttacher.registerEventListeners(vehicleType)
	local functionNames = {
		"onLoad",
		"onUpdateTick",
		"onReadStream",
		"onWriteStream",
		"onRegisterActionEvents",
		"onDelete"
	};

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

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

function DynamicPalletAttacher.registerOverwrittenFunctions(vehicleType)
	local overwrittenFunctions = {
		"getFillLevelInformation",
		"addNodeObjectMapping",
		"removeNodeObjectMapping"
	};
	
	for _, overwrittenFunction in ipairs(overwrittenFunctions) do
		SpecializationUtil.registerOverwrittenFunction(vehicleType, overwrittenFunction, DynamicPalletAttacher[overwrittenFunction]);
	end;
end;

function DynamicPalletAttacher:onLoad(savegame)
	if JohnDeerePackUtil == nil then
        return;
    end;
	
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");
	
	specDynamicPalletAttacher.palletIsAttached = false;
	specDynamicPalletAttacher.doStateChange = false;
	
	specDynamicPalletAttacher.palletsInRange = {};
	specDynamicPalletAttacher.palletsToJoint = {};
	
	specDynamicPalletAttacher.raycastNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.dynamicAttacher#raycastNode"), self.i3dMappings);
	specDynamicPalletAttacher.palletTriggerId = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.dynamicAttacher#palletTriggerIndex"), self.i3dMappings);
	specDynamicPalletAttacher.boxSizeX = getXMLFloat(self.xmlFile, "vehicle.dynamicAttacher#boxSizeX", 1);
	specDynamicPalletAttacher.boxSizeY = getXMLFloat(self.xmlFile, "vehicle.dynamicAttacher#boxSizeY", 1);
	specDynamicPalletAttacher.boxSizeZ = getXMLFloat(self.xmlFile, "vehicle.dynamicAttacher#boxSizeZ", 1);
	
	if specDynamicPalletAttacher.raycastNode == nil then
		g_logManager:xmlWarning(self.configFileName, "'dynamicAttacher#raycastNode' is not defined - DynamicAttacher will not work");
	end;

	if savegame ~= nil and not savegame.resetVehicles then
		specDynamicPalletAttacher.palletIsAttached = Utils.getNoNil(getXMLBool(savegame.xmlFile, savegame.key .. "." .. DynamicPalletAttacher.currentModName .. ".dynamicPalletAttacher#palletIsAttached"), specDynamicPalletAttacher.palletIsAttached);
	end;
	
	self:setDynamicPalletAttacher(specDynamicPalletAttacher.palletIsAttached, false);
end;

function DynamicPalletAttacher:onReadStream(streamId, connection)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	specDynamicPalletAttacher.palletIsAttached = streamReadBool(streamId);
	specDynamicPalletAttacher.doStateChange = streamReadBool(streamId);
end;

function DynamicPalletAttacher:onWriteStream(streamId, connection)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	streamWriteBool(streamId, specDynamicPalletAttacher.palletIsAttached);
	streamWriteBool(streamId, specDynamicPalletAttacher.doStateChange);
end;

function DynamicPalletAttacher:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
	if self.isClient then
		local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

		self:clearActionEventsTable(specDynamicPalletAttacher.actionEvents);
		
		if isActiveForInputIgnoreSelection then
			local _, actionEventId = self:addActionEvent(specDynamicPalletAttacher.actionEvents, InputAction.DYNAMICPALLET_ATTACH_BUTTON, self, DynamicPalletAttacher.actionEventDynamicPalletAttach, false, true, false, true, nil)
			
			g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH);
			g_inputBinding:setActionEventTextVisibility(actionEventId, true);
			g_inputBinding:setActionEventActive(actionEventId, true);

			local actionEventText = JohnDeerePackUtil.getL10nText("action_dynamicPalletAttach");

			if specDynamicPalletAttacher.palletIsAttached then
				actionEventText = JohnDeerePackUtil.getL10nText("action_dynamicPalletDetach");
			end;

			g_inputBinding:setActionEventText(actionEventId, actionEventText);
		end;
	end;
end;

function DynamicPalletAttacher:onUpdateTick(dt)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");
	
	if specDynamicPalletAttacher.doStateChange and specDynamicPalletAttacher.raycastNode ~= nil then
		local actionEventText = JohnDeerePackUtil.getL10nText("action_dynamicPalletAttach");

		specDynamicPalletAttacher.palletsInRange = {};

		local x, y, z = getWorldTranslation(specDynamicPalletAttacher.raycastNode);
		local rx, ry, rz = getWorldRotation(specDynamicPalletAttacher.raycastNode);
		local sizeX, sizeY, sizeZ = specDynamicPalletAttacher.boxSizeX, specDynamicPalletAttacher.boxSizeY, specDynamicPalletAttacher.boxSizeZ;

		overlapBox(x, y, z, rx, ry, rz, sizeX, sizeY, sizeZ, "attachPalletInTriggerCallback", self, nil, true, false, true);
		
		if specDynamicPalletAttacher.palletIsAttached then
			--## create joints
			for index, pallet in pairs(specDynamicPalletAttacher.palletsInRange) do			
				local palletOnFork = g_currentMission:getNodeObject(pallet.physics);
				
				if palletOnFork ~= nil and specDynamicPalletAttacher.palletsToJoint[index] == nil then
					local constr = JointConstructor:new();
					constr:setActors(self.components[1].node, pallet.physics);
							
					--## create joint at center of mass to avoid jittering (e.g. tree trunks pivots are not at center of mass position)
					local x,y,z = localToWorld(pallet.physics, getCenterOfMass(pallet.physics));
							
					constr:setJointWorldPositions(x,y,z, x,y,z);
					constr:setRotationLimit(0, 0, 0);
					constr:setRotationLimit(1, 0, 0);
					constr:setRotationLimit(2, 0, 0);
							
					pallet.dynamicAttacherJoint = constr:finalize();

					if palletOnFork.addDynamicMountedObject ~= nil then
						palletOnFork:mountDynamic(self, pallet.physics, specDynamicPalletAttacher.palletTriggerId, DynamicMountUtil.TYPE_FORK, 500);
					end;
					
					specDynamicPalletAttacher.palletsToJoint[index] = pallet;
				end;
			end;

			actionEventText = JohnDeerePackUtil.getL10nText("action_dynamicPalletDetach");
		else
			--## remove joints
			for index, pallet in pairs(specDynamicPalletAttacher.palletsToJoint) do
				local palletOnFork = g_currentMission:getNodeObject(pallet.physics);
				
				if palletOnFork ~= nil and specDynamicPalletAttacher.palletsToJoint[index] ~= nil then
					removeJoint(pallet.dynamicAttacherJoint);

					if palletOnFork.removeDynamicMountedObject ~= nil then
						palletOnFork:unmountDynamic();
					end;
							
					specDynamicPalletAttacher.palletsToJoint[index] = nil;
				end;
			end;
		end;

		local actionEvent = specDynamicPalletAttacher.actionEvents[InputAction.DYNAMICPALLET_ATTACH_BUTTON];

		if actionEvent ~= nil then
			g_inputBinding:setActionEventText(actionEvent.actionEventId, actionEventText);
		end;

		specDynamicPalletAttacher.doStateChange = false;
	end;

	if specDynamicPalletAttacher.palletIsAttached then
		for index, pallet in pairs(specDynamicPalletAttacher.palletsInRange) do
			local palletOnFork = g_currentMission:getNodeObject(pallet.physics);

			if palletOnFork ~= nil then
				local specFillUnit = palletOnFork.spec_fillUnit;
				
				if specFillUnit ~= nil then
					--## update fill level information of the pallet for the hud

					specDynamicPalletAttacher.palletsInRange[pallet.physics] = {
						physics = pallet.physics, 
						fillLevel = specFillUnit.fillUnits[1].fillLevel,
						capacity = specFillUnit.fillUnits[1].capacity,
						fillType = specFillUnit.fillUnits[1].fillType
					};
				end;
			end;
		end;
	end;

	for index, pallet in pairs(specDynamicPalletAttacher.palletsToJoint) do
		--## set attach status to false, if it is true and the pallet is deleted. e. g. pallet is empty...
		
		if not entityExists(pallet.physics) and specDynamicPalletAttacher.palletsToJoint[index] ~= nil then
			removeJoint(pallet.dynamicAttacherJoint);
			self:setDynamicPalletAttacher(false, false);
					
			specDynamicPalletAttacher.palletsToJoint[index] = nil;
		end;
	end;
end;

function DynamicPalletAttacher:onDelete()
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	if specDynamicPalletAttacher.palletTriggerId ~= nil then
		removeTrigger(specDynamicPalletAttacher.palletTriggerId);
	end;

	self:setDynamicPalletAttacher(false, false);
end;

function DynamicPalletAttacher:addNodeObjectMapping(superFunc, list)
	superFunc(self, list)

	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	if specDynamicPalletAttacher.palletTriggerId ~= nil then
		list[specDynamicPalletAttacher.palletTriggerId] = self;
	end;
end;

function DynamicPalletAttacher:removeNodeObjectMapping(superFunc, list)
	superFunc(self, list)

	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	if specDynamicPalletAttacher.palletTriggerId ~= nil then
		list[specDynamicPalletAttacher.palletTriggerId] = nil;
	end;
end;

function DynamicPalletAttacher:attachPalletInTriggerCallback(transformId)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	if transformId ~= 0 and getHasClassId(transformId, ClassIds.SHAPE) then
		local palletName = string.upper(getName(transformId));

		if JohnDeerePackUtil.getIsValidPalletName(palletName) then
			local rigidBodyType = getRigidBodyType(transformId);

			if (rigidBodyType == "Dynamic" or rigidBodyType == "Kinematic") then
				if specDynamicPalletAttacher.palletsInRange[transformId] == nil then

					specDynamicPalletAttacher.palletsInRange[transformId] = {
						physics = transformId
					};
				end;
			end;
		end;
	end;
	
    return true;
end;

function DynamicPalletAttacher:setDynamicPalletAttacher(palletIsAttached, noEventSend)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");

	specDynamicPalletAttacher.palletIsAttached = palletIsAttached;
	specDynamicPalletAttacher.doStateChange = true;
	
	DynamicPalletAttacherEvent.sendEvent(self, palletIsAttached, noEventSend);
end;

function DynamicPalletAttacher.actionEventDynamicPalletAttach(self, actionName, inputValue, callbackState, isAnalog)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");
	
	self:setDynamicPalletAttacher(not specDynamicPalletAttacher.palletIsAttached, false);
end;

function DynamicPalletAttacher:saveToXMLFile(xmlFile, key)
	local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");
	
	if specDynamicPalletAttacher.palletIsAttached then	
		setXMLBool(xmlFile, key .. "#palletIsAttached", specDynamicPalletAttacher.palletIsAttached);
	end;
end;

--## Adds the bale fill level information to the list
function DynamicPalletAttacher.addPalletFillLevelInformation(fillType, fillLevel, fillLevelsInformation)
    for _, fillLevelInformation in ipairs(fillLevelsInformation) do
        if fillLevelInformation.fillType == fillType then
            fillLevelInformation.fillLevel = fillLevelInformation.fillLevel + fillLevel;
            fillLevelInformation.capacity = fillLevelInformation.capacity + fillLevel;
			
			return true;
        end;
    end;

    return false;
end;

--## Gets the fill level information based on the attached pallets
function DynamicPalletAttacher:getFillLevelInformation(superFunc, fillLevelsInformation)
    local function addFillLevelInformation(pallet)
        local fillType = pallet.fillType;
		local fillLevel = pallet.fillLevel;
		local capacity = Utils.getNoNil(pallet.capacity, fillLevel);
		
		if fillType ~= nil then
        	if not DynamicPalletAttacher.addPalletFillLevelInformation(fillType, fillLevel, fillLevelsInformation) then
				local fillLevelInformationTable = {
					fillType = fillType,
					fillLevel = fillLevel,
					capacity = capacity
				};

				table.insert(fillLevelsInformation, fillLevelInformationTable);
			end;
		end;
    end;

    local specDynamicPalletAttacher = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttacher");
	   
	for _, pallet in pairs(specDynamicPalletAttacher.palletsInRange) do
		if specDynamicPalletAttacher.palletIsAttached then	
			addFillLevelInformation(pallet);
		end;
    end;
end;

--## MP Event
DynamicPalletAttacherEvent = {};
DynamicPalletAttacherEvent_mt = Class(DynamicPalletAttacherEvent, Event);

InitEventClass(DynamicPalletAttacherEvent, "DynamicPalletAttacherEvent");

function DynamicPalletAttacherEvent:emptyNew()
	local self = Event:new(DynamicPalletAttacherEvent_mt);
	
    self.className = "DynamicPalletAttacherEvent";
	
	return self;
end;

function DynamicPalletAttacherEvent:new(palletFork, palletIsAttached)
	local self = DynamicPalletAttacherEvent:emptyNew();
	
    self.palletFork = palletFork;
	self.palletIsAttached = palletIsAttached;
	
	return self;
end;

function DynamicPalletAttacherEvent:readStream(streamId, connection)
	self.palletFork = NetworkUtil.readNodeObject(streamId);
	self.palletIsAttached = streamReadBool(streamId);
	
    self:run(connection);
end;

function DynamicPalletAttacherEvent:writeStream(streamId, connection)
	NetworkUtil.writeNodeObject(streamId, self.palletFork);
    streamWriteBool(streamId, self.palletIsAttached);
end;

function DynamicPalletAttacherEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, nil, connection, self.palletFork);
	end;
	
	if self.palletFork ~= nil then
		self.palletFork:setDynamicPalletAttacher(self.palletIsAttached, true);
	end;
end;

function DynamicPalletAttacherEvent.sendEvent(palletFork, palletIsAttached, noEventSend)
	if noEventSend == nil or noEventSend == false then
		if g_server ~= nil then
			g_server:broadcastEvent(DynamicPalletAttacherEvent:new(palletFork, palletIsAttached), nil, nil, palletFork);
		else
			g_client:getServerConnection():sendEvent(DynamicPalletAttacherEvent:new(palletFork, palletIsAttached));
		end;
	end;
end;