--[[
DynamicPalletAttach

Author: 	Ifko[nator]
Date:		30.09.2020
Version: 	1.2

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
]]

DynamicPalletAttach = {};
DynamicPalletAttach.currentModName = g_currentModName;

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

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

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

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

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

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

    if self.isServer and specDynamicPalletAttach.palletTriggerId ~= nil then
        addTrigger(specDynamicPalletAttach.palletTriggerId, "findPalletInTriggerCallback", self);
	end;

	if savegame ~= nil and not savegame.resetVehicles then
		specDynamicPalletAttach.palletIsAttached = Utils.getNoNil(getXMLBool(savegame.xmlFile, savegame.key .. "." .. DynamicPalletAttach.currentModName .. ".dynamicPalletAttach#palletIsAttached"), specDynamicPalletAttach.palletIsAttached);
	end;
	
	self:setDynamicPalletAttach(specDynamicPalletAttach.palletIsAttached, false);
end;

function DynamicPalletAttach:onReadStream(streamId, connection)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

	specDynamicPalletAttach.palletIsAttached = streamReadBool(streamId);
	specDynamicPalletAttach.doStateChange = streamReadBool(streamId);
	specDynamicPalletAttach.allowAttachPallet = streamReadBool(streamId);
end;

function DynamicPalletAttach:onWriteStream(streamId, connection)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

	streamWriteBool(streamId, specDynamicPalletAttach.palletIsAttached);
	streamWriteBool(streamId, specDynamicPalletAttach.doStateChange);
	streamWriteBool(streamId, specDynamicPalletAttach.allowAttachPallet);
end;

function DynamicPalletAttach:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
	if self.isClient then
		local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

		self:clearActionEventsTable(specDynamicPalletAttach.actionEvents);
		
		if isActiveForInputIgnoreSelection then
			local _, actionEventId = self:addActionEvent(specDynamicPalletAttach.actionEvents, InputAction.DYNAMICPALLET_ATTACH_BUTTON, self, DynamicPalletAttach.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 specDynamicPalletAttach.palletIsAttached then
				actionEventText = JohnDeerePackUtil.getL10nText("action_dynamicPalletDetach");
			end;

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

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

		specDynamicPalletAttach.palletsInRange = {};

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

		overlapBox(x, y, z, rx, ry, rz, sizeX, sizeY, sizeZ, "attachPalletInTriggerCallback", self, nil, true, false, true);
		
		if specDynamicPalletAttach.palletIsAttached then
			--## create joints
			for index, pallet in pairs(specDynamicPalletAttach.palletsInRange) do			
				local palletOnFork = g_currentMission:getNodeObject(pallet.physics);
				
				if specDynamicPalletAttach.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();

					palletOnFork:mountDynamic(self, pallet.physics, specDynamicPalletAttach.palletTriggerId, DynamicMountUtil.TYPE_FORK, 500);
					
					specDynamicPalletAttach.palletsToJoint[index] = pallet;
				end;
			end;

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

					palletOnFork:unmountDynamic();
							
					specDynamicPalletAttach.palletsToJoint[index] = nil;
				end;
			end;
		end;

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

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

		specDynamicPalletAttach.doStateChange = false;
	end;

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

			if palletOnFork ~= nil then
				--## update fill level information of the pallet for the hud

				specDynamicPalletAttach.palletsInRange[pallet.physics] = {
					physics = pallet.physics, 
					fillLevel = palletOnFork.spec_fillUnit.fillUnits[1].fillLevel,
					capacity = palletOnFork.spec_fillUnit.fillUnits[1].capacity,
					fillType = palletOnFork.spec_fillUnit.fillUnits[1].fillType
				};
			end;
		end;
	end;

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

	if actionEvent ~= nil then
		--## deactivate action event, if no pallet was found

		g_inputBinding:setActionEventActive(actionEvent.actionEventId, specDynamicPalletAttach.allowAttachPallet);
		g_inputBinding:setActionEventTextVisibility(actionEvent.actionEventId, specDynamicPalletAttach.allowAttachPallet);
	end;

	for index, pallet in pairs(specDynamicPalletAttach.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 specDynamicPalletAttach.palletsToJoint[index] ~= nil then
			removeJoint(pallet.dynamicAttacherJoint);
			self:setDynamicPalletAttach(false, false);
					
			specDynamicPalletAttach.palletsToJoint[index] = nil;
			specDynamicPalletAttach.allowAttachPallet = false;
		end;
	end;
end;

function DynamicPalletAttach:onDelete()
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

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

	self:setDynamicPalletAttach(false, false);
end;

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

	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

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

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

	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

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

function DynamicPalletAttach:attachPalletInTriggerCallback(transformId)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

	if transformId ~= 0 and getHasClassId(transformId, ClassIds.SHAPE) and specDynamicPalletAttach.allowAttachPallet then
		local palletName = string.upper(getName(transformId));
		
        if (not string.find(palletName, "PALLETFORK") and not string.find(palletName, "PALLETTRIGGER")) and (string.find(palletName, "PALLET") or string.find(palletName, "BIGBAG") or string.find(palletName, "TANK")) then
			local rigidBodyType = getRigidBodyType(transformId);

			if (rigidBodyType == "Dynamic" or rigidBodyType == "Kinematic") then
				if specDynamicPalletAttach.palletsInRange[transformId] == nil then
					specDynamicPalletAttach.palletsInRange[transformId] = {
						physics = transformId
					};
				end;
			end;
		end;
	end;
	
    return true;
end;

function DynamicPalletAttach:findPalletInTriggerCallback(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
	local palletName = string.upper(getName(otherId));
	
    if (not string.find(palletName, "PALLETFORK") and not string.find(palletName, "PALLETTRIGGER")) and (string.find(palletName, "PALLET") or string.find(palletName, "BIGBAG") or string.find(palletName, "TANK")) then	
		local object = g_currentMission:getNodeObject(otherId);
		
		if object ~= nil  then
			local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");
			
			if not specDynamicPalletAttach.palletIsAttached then
				if onEnter then
    	    	    specDynamicPalletAttach.allowAttachPallet = true;
    	    	elseif onLeave then
    	    	    specDynamicPalletAttach.allowAttachPallet = false;
				end;
			else
				specDynamicPalletAttach.allowAttachPallet = true;
			end;
		else
			specDynamicPalletAttach.allowAttachPallet = false;
		end;
	end;
end;

function DynamicPalletAttach:setDynamicPalletAttach(palletIsAttached, noEventSend)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");

	specDynamicPalletAttach.palletIsAttached = palletIsAttached;
	specDynamicPalletAttach.doStateChange = true;
	
	DynamicPalletAttachEvent.sendEvent(self, palletIsAttached, noEventSend);
end;

function DynamicPalletAttach.actionEventDynamicPalletAttach(self, actionName, inputValue, callbackState, isAnalog)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");
	
	self:setDynamicPalletAttach(not specDynamicPalletAttach.palletIsAttached, false);
end;

function DynamicPalletAttach:saveToXMLFile(xmlFile, key)
	local specDynamicPalletAttach = JohnDeerePackUtil.getSpecByName(self, "dynamicPalletAttach");
	
	if specDynamicPalletAttach.palletIsAttached then	
		setXMLBool(xmlFile, key .. "#palletIsAttached", specDynamicPalletAttach.palletIsAttached);
	end;
end;

--## Adds the bale fill level information to the list
function DynamicPalletAttach.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 DynamicPalletAttach: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 DynamicPalletAttach.addPalletFillLevelInformation(fillType, fillLevel, fillLevelsInformation) then
				local fillLevelInformationTable = {
					fillType = fillType,
					fillLevel = fillLevel,
					capacity = capacity
				};

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

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

--## MP Event
DynamicPalletAttachEvent = {};
DynamicPalletAttachEvent_mt = Class(DynamicPalletAttachEvent, Event);

InitEventClass(DynamicPalletAttachEvent, "DynamicPalletAttachEvent");

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

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

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

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

function DynamicPalletAttachEvent:run(connection)
    if not connection:getIsServer() then
        g_server:broadcastEvent(self, nil, connection, self.palletFork);
	end;
	
	if self.palletFork ~= nil then
		self.palletFork:setDynamicPalletAttach(true, false);
	end;
end;

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