--[[
	PickUpDirectionForLevelerFix.lua
	
	Autor: 		Ifko[nator]
	Datum: 		23.09.2023
	Version: 	1.0
	
	Changelog:	v1.0 @23.09.2023 - initial implementation in FS 22
]]

PickUpDirectionForLevelerFix = {};


function PickUpDirectionForLevelerFix.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Leveler, specializations);
end;

function PickUpDirectionForLevelerFix.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "doCheckSpeedLimit", PickUpDirectionForLevelerFix.doCheckSpeedLimit);
end;

function PickUpDirectionForLevelerFix.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", PickUpDirectionForLevelerFix);
end;

function PickUpDirectionForLevelerFix:onLoad(savegame)
	self.spec_leveler.allowPickupInBothDirections = Utils.getNoNil(getXMLBool(self.xmlFile.handle, "vehicle.leveler#allowPickupInBothDirections"), false);

	Leveler.onUpdate = Utils.overwrittenFunction(Leveler.onUpdate, PickUpDirectionForLevelerFix.onUpdateFix);
end;

function PickUpDirectionForLevelerFix:doCheckSpeedLimit(superFunc)
	return superFunc(self) or self:getIsTurnedOn() and self:getIsLowered();
end;

function PickUpDirectionForLevelerFix:onUpdateFix(superFunc, dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
	local specLeveler = self.spec_leveler;

	if self.isClient then
		local fillType = FillType.UNKNOWN;

		for _, levelerNode in pairs(specLeveler.nodes) do
			fillType = self:getFillUnitLastValidFillType(levelerNode.fillUnitIndex);

			if fillType ~= FillType.UNKNOWN then
				break;
			end;
		end;

		if fillType ~= FillType.UNKNOWN and specLeveler.lastFillLevelMovedPct > 0 then
			g_effectManager:setFillType(specLeveler.effects, fillType);
			g_effectManager:startEffects(specLeveler.effects);

			for _, effect in pairs(specLeveler.effects) do
				if effect:isa(LevelerEffect) or effect:isa(SnowPlowMotionPathEffect) then
					effect:setFillLevel(specLeveler.lastFillLevelMovedPct);
					effect:setLastVehicleSpeed(self.movingDirection * self:getLastSpeed());
				end;
			end;
		else
			g_effectManager:stopEffects(specLeveler.effects);
		end;
	end;

	if self.isServer then
		for _, levelerNode in pairs(specLeveler.nodes) do
			local x0, y0, z0 = localToWorld(levelerNode.node, -levelerNode.halfWidth, levelerNode.yOffset, levelerNode.maxDropDirOffset);
			local x1, y1, z1 = localToWorld(levelerNode.node, levelerNode.halfWidth, levelerNode.yOffset, levelerNode.maxDropDirOffset);

			if not specLeveler.ignoreFarmlandState then
				local ownerFarmId = self:getOwnerFarmId();

				if not g_farmlandManager:getCanAccessLandAtWorldPosition(ownerFarmId, x0, z0) or not g_farmlandManager:getCanAccessLandAtWorldPosition(ownerFarmId, x1, z1) then
					break;
				end;
			end;

			local pickedUpFillLevel = 0;
			local fillType = self:getFillUnitFillType(levelerNode.fillUnitIndex);
			local fillLevel = self:getFillUnitFillLevel(levelerNode.fillUnitIndex);

			if fillType == FillType.UNKNOWN or fillLevel < g_densityMapHeightManager:getMinValidLiterValue(fillType) + 0.001 then
				local newFillType = DensityMapHeightUtil.getFillTypeAtLine(x0, y0, z0, x1, y1, z1, 0.5 * levelerNode.maxDropDirOffset);

				if newFillType ~= FillType.UNKNOWN and newFillType ~= fillType and self:getFillUnitSupportsFillType(levelerNode.fillUnitIndex, newFillType) then
					self:addFillUnitFillLevel(self:getOwnerFarmId(), levelerNode.fillUnitIndex, -math.huge);

					fillType = newFillType;
				end;
			end;

			local heightType = g_densityMapHeightManager:getDensityMapHeightTypeByFillTypeIndex(fillType);

			if fillType ~= FillType.UNKNOWN and heightType ~= nil then
				local innerRadius = 0.5;
				local outerRadius = 2;
				local capacity = self:getFillUnitCapacity(levelerNode.fillUnitIndex);
				local dirY = 0;

				if levelerNode.alignToWorldY then
					local dirX, dirZ = nil;
					
					dirX, dirY, dirZ = localDirectionToWorld(levelerNode.referenceFrame, 0, 0, 1);

					I3DUtil.setWorldDirection(levelerNode.node, dirX, math.max(dirY, 0), dirZ, 0, 1, 0);
				end;

				if self:getIsLevelerPickupNodeActive(levelerNode) and (specLeveler.pickUpDirection == self.movingDirection or specLeveler.allowPickupInBothDirections) and self.lastSpeed > 0.0001 then
					local sx, sy, sz = localToWorld(levelerNode.node, -levelerNode.halfWidth, levelerNode.yOffset, levelerNode.zOffset);
					local ex, ey, ez = localToWorld(levelerNode.node, levelerNode.halfWidth, levelerNode.yOffset, levelerNode.zOffset);

					if dirY >= 0 then
						local _, sy2, _ = localToWorld(levelerNode.node, -levelerNode.halfWidth, levelerNode.yOffset, levelerNode.zOffset + innerRadius);
						local _, ey2, _ = localToWorld(levelerNode.node, levelerNode.halfWidth, levelerNode.yOffset, levelerNode.zOffset + innerRadius);
						
						sy = math.max(sy, sy2);
						ey = math.max(ey, ey2);
					end;

					fillLevel = self:getFillUnitFillLevel(levelerNode.fillUnitIndex);
					
					local delta = -(capacity - fillLevel);
					local numHeightLimitChecks = levelerNode.numHeightLimitChecks;

					if numHeightLimitChecks > 0 then
						local movementY = 0;

						for heightNumber = 0, numHeightLimitChecks do
							local time = heightNumber / numHeightLimitChecks;
							local xi = sx + (ex - sx) * time;
							local yi = sy + (ey - sy) * time;
							local zi = sz + (ez - sz) * time;
							local hi = DensityMapHeightUtil.getHeightAtWorldPos(xi, yi, zi);

							movementY = math.max(movementY, hi - 0.05 - yi);
						end;

						if movementY > 0 then
							sy = sy + movementY;
							ey = ey + movementY;
						end;
					end;

					levelerNode.lastPickUp, levelerNode.lineOffsetPickUp = DensityMapHeightUtil.tipToGroundAroundLine(self, delta, fillType, sx, sy, sz, ex, ey, ez, innerRadius, outerRadius, levelerNode.lineOffsetPickUp, true, nil);

					if levelerNode.lastPickUp < 0 then
						if self.notifiyBunkerSilo ~= nil then
							self:notifiyBunkerSilo(levelerNode.lastPickUp, fillType, (sx + ex) * 0.5, (sy + ey) * 0.5, (sz + ez) * 0.5);
						end;

						levelerNode.lastPickUp = levelerNode.lastPickUp + specLeveler.litersToPickup;
						specLeveler.litersToPickup = 0;

						self:addFillUnitFillLevel(self:getOwnerFarmId(), levelerNode.fillUnitIndex, -levelerNode.lastPickUp, fillType, ToolType.UNDEFINED, nil);

						pickedUpFillLevel = levelerNode.lastPickUp;
					end;
				end;

				local lastPickUpPerMS = -pickedUpFillLevel;
				
				specLeveler.lastFillLevelMovedBuffer = specLeveler.lastFillLevelMovedBuffer + lastPickUpPerMS;
				specLeveler.lastFillLevelMovedBufferTimer = specLeveler.lastFillLevelMovedBufferTimer + dt;

				if specLeveler.lastFillLevelMovedBufferTime < specLeveler.lastFillLevelMovedBufferTimer then
					specLeveler.lastFillLevelMovedTarget = specLeveler.lastFillLevelMovedBuffer / specLeveler.lastFillLevelMovedBufferTimer;
					specLeveler.lastFillLevelMovedBufferTimer = 0;
					specLeveler.lastFillLevelMovedBuffer = 0;
				end;

				if self.movingDirection < 0 and self.lastSpeed * 3600 > 0.5 then
					specLeveler.lastFillLevelMovedBuffer = 0;
				end;

				fillLevel = self:getFillUnitFillLevel(levelerNode.fillUnitIndex);

				if fillLevel > 0 then
					local percentage = fillLevel / capacity;
					local width = MathUtil.lerp(levelerNode.halfMinDropWidth, levelerNode.halfMaxDropWidth, percentage);
					local sx, sy, sz = localToWorld(levelerNode.node, -width, levelerNode.yOffset, levelerNode.zOffset);
					local ex, ey, ez = localToWorld(levelerNode.node, width, levelerNode.yOffset, levelerNode.zOffset);
					local yOffset = -0.15;
					
					levelerNode.lastDrop1, levelerNode.lineOffsetDrop1 = DensityMapHeightUtil.tipToGroundAroundLine(self, fillLevel, fillType, sx, sy + yOffset, sz, ex, ey + yOffset, ez, innerRadius, outerRadius, levelerNode.lineOffsetDrop1, true, nil);

					if levelerNode.lastDrop1 > 0 then
						local leftOver = fillLevel - levelerNode.lastDrop1;

						if leftOver <= g_densityMapHeightManager:getMinValidLiterValue(fillType) then
							levelerNode.lastDrop1 = fillLevel;
							specLeveler.litersToPickup = specLeveler.litersToPickup + leftOver;
						end;

						self:addFillUnitFillLevel(self:getOwnerFarmId(), levelerNode.fillUnitIndex, -levelerNode.lastDrop1, fillType, ToolType.UNDEFINED, nil);
					end;
				end;

				fillLevel = self:getFillUnitFillLevel(levelerNode.fillUnitIndex);

				if fillLevel > 0 then
					local dropOffset = MathUtil.lerp(levelerNode.minDropDirOffset, levelerNode.maxDropDirOffset, specLeveler.lastFillLevelMovedPct);
					local wx, wy, wz = localToWorld(levelerNode.node, 0, levelerNode.yOffset, 0);
					local tx, ty, tz = localToWorld(levelerNode.node, 0, levelerNode.yOffset, levelerNode.zOffset + dropOffset);
					
					levelerNode.raycastLastFillType = fillType;
					levelerNode.raycastLastRadius = outerRadius;
					levelerNode.raycastHitObject = false;
					
					local rDirX = tx - wx;
					local rDirY = ty - wy;
					local rDirZ = tz - wz;
					local distance = MathUtil.vector3Length(rDirX, rDirY, rDirZ);
					
					rDirX, rDirY, rDirZ = MathUtil.vector3Normalize(rDirX, rDirY, rDirZ);

					raycastAll(wx, wy, wz, rDirX, rDirY, rDirZ, "onLevelerRaycastCallback", distance, levelerNode, CollisionFlag.STATIC_OBJECTS, false, true);
				end;
			else
				specLeveler.lastFillLevelMovedBuffer = 0;
				specLeveler.lastFillLevelMovedTarget = 0;
			end;

			if pickedUpFillLevel < 0 and fillType ~= FillType.UNKNOWN then
				self:notifiyBunkerSilo(pickedUpFillLevel, fillType);
			end;

			if levelerNode.allowsSmoothing and (levelerNode.smoothDirection == 0 or self.movingDirection == levelerNode.smoothDirection) then
				local smoothAmount = 0;

				if self.lastSpeedReal > 0.0002 then
					smoothAmount = specLeveler.smoothAccumulation + math.max(self.lastMovedDistance * 0.5, 0.0003 * dt);
					
					local rounded = DensityMapHeightUtil.getRoundedHeightValue(smoothAmount);
					
					specLeveler.smoothAccumulation = smoothAmount - rounded;
				else
					specLeveler.smoothAccumulation = 0;
				end

				if smoothAmount > 0 then
					DensityMapHeightUtil.smoothAroundLine(levelerNode.node, levelerNode.width, levelerNode.smoothGroundRadius, levelerNode.smoothOverlap, smoothAmount);
				end;
			end;
		end;

		local smoothFactor = 0.05;

		if specLeveler.lastFillLevelMovedTarget == 0 then
			smoothFactor = 0.2;
		end;

		specLeveler.lastFillLevelMoved = specLeveler.lastFillLevelMoved * (1 - smoothFactor) + specLeveler.lastFillLevelMovedTarget * smoothFactor;

		if specLeveler.lastFillLevelMoved < 0.005 then
			specLeveler.lastFillLevelMoved = 0;
		end;

		local oldPercentage = specLeveler.lastFillLevelMovedPct;
		
		specLeveler.lastFillLevelMovedPct = math.max(math.min(specLeveler.lastFillLevelMoved / specLeveler.maxFillLevelPerMS, 1), 0);

		if specLeveler.lastFillLevelMovedPct ~= oldPercentage then
			self:raiseDirtyFlags(specLeveler.dirtyFlag);
		end;

		if specLeveler.forceNode ~= nil and self.movingDirection == specLeveler.forceDir and specLeveler.lastFillLevelMoved > 0 then
			specLeveler.lastForce = -specLeveler.maxForce * specLeveler.lastFillLevelMovedPct;
			
			local dx, dy, dz = localDirectionToWorld(specLeveler.forceDirNode, 0, 0, specLeveler.lastForce);
			local px, py, pz = getCenterOfMass(specLeveler.forceNode);

			addForce(specLeveler.forceNode, dx, dy, dz, px, py, pz, true);
		end;
	end;
end;