AdditionalContractSplineManager = {};
AdditionalContractSplineManager.MAX_TRIES = 5;

local AdditionalContractSplineManager_mt = Class(AdditionalContractSplineManager);

function AdditionalContractSplineManager.new()	
	local self = AbstractManager.new(AdditionalContractSplineManager_mt)
	self.isServer = g_server ~= nil;
	self:initDataStructures();
	return self;
end;

function AdditionalContractSplineManager:initDataStructures()
	self.splines = {};
	self.groundSplines = {[1]={},[2]={},[3]={},[4]={},[5]={}};
	self.loadedSplines = false;
	self.activeSplines = {};
	self.numSplines = 0;
end;

function AdditionalContractSplineManager:loadMapData(xmlFile, missionInfo)
	AdditionalContractSplineManager:superClass().loadMapData(self);	
	if self.isServer then self:load();end;
	return true;
end;

function AdditionalContractSplineManager:unloadMapData()	
	AdditionalContractSplineManager:superClass().unloadMapData(self);
end;

function AdditionalContractSplineManager:load()	
	if self.loadedSplines then return;end;
	local mapSize = Utils.getNoNil(g_currentMission.terrainSize, 2048) * 0.5;
	function getMapSplines()
		if g_currentMission.aiSystem ~= nil and g_currentMission.aiSystem.roadSplines ~= nil and #g_currentMission.aiSystem.roadSplines > 0 then		
			return g_currentMission.aiSystem.roadSplines;
		end;
		return {};
	end;
	function inMapRange(spline)
		local startX, startY, startZ = getSplinePosition(spline, 0);
		local endX, endY, endZ = getSplinePosition(spline, 1);				
		if startX ~= nil and endX ~= nil then
			if math.abs(startX) < mapSize and math.abs(startZ) < mapSize and math.abs(endX) < mapSize and math.abs(endZ) < mapSize then
				return true;
			end;
		end;
		return false;
	end;
	if self.splines == nil then self.splines = {};end;
	for _, splineOrNode in ipairs(getMapSplines()) do
		if I3DUtil.getIsSpline(splineOrNode) and inMapRange(splineOrNode) then
			table.insert(self.splines, splineOrNode);
		end;
		I3DUtil.iterateRecursively(splineOrNode, function (node)
			if I3DUtil.getIsSpline(node) and inMapRange(node) then
				table.insert(self.splines, node);
			end;
		end);		
	end;	
	function setGroundType(spline)
		local startX, startY, startZ = getSplinePosition(spline, 0);
		local _, _, _, groundDepth, _ = getTerrainAttributesAtWorldPos(g_terrainNode, startX, startY, startZ, true, true, true, true, false);						
		local groundTypeMapId, groundTypeFirstChannel, groundTypeNumChannels = g_currentMission.fieldGroundSystem:getDensityMapData(FieldDensityMap.GROUND_TYPE);
		local densityBits = getDensityAtWorldPos(groundTypeMapId, startX, startY, startZ);
		local densityHeightBits = bit32.rshift(densityBits, groundTypeFirstChannel);
		local numChannels = 2 ^ groundTypeNumChannels - 1;
		local heightType = bit32.band(densityHeightBits, numChannels);
		local densityType = FieldGroundType.getTypeByValue(heightType);						
		local groundType = g_additionalContractPointManager:getGroundType(densityType ~= FieldGroundType.NONE, false, groundDepth);
		if self.groundSplines[groundType] ~= nil then table.insert(self.groundSplines[groundType], spline);else table.insert(self.groundSpline[g_additionalContractPointManager:getUnknownGroundTyp()], spline);end;
	end;
	for _, splineOrNode in ipairs(self.splines) do
		setGroundType(splineOrNode);
	end;
	self.numSplines = #self.splines;
	self.loadedSplines = true;	
end;

function AdditionalContractSplineManager:hasSplines()
	return self.numSplines > 0 or #self.splines > 0;
end;

function AdditionalContractSplineManager:addActiveSpline(splineNode)
	self.activeSplines[splineNode] = splineNode;
end;

function AdditionalContractSplineManager:removeActiveSpline(splineNode)
	table.remove(self.activeSplines, splineNode);	
end;

function AdditionalContractSplineManager:getSplines(groundType)
	if groundType ~= nil then return self:getGroundSplines(groundType);end;
	return self.splines;
end;

function AdditionalContractSplineManager:getGroundSplines(groundType)
	return self.groundSplines[groundType];
end;

function AdditionalContractSplineManager:getRandomSpline(groundType, nonBlocking, size)
	local splines = self:getSplines(groundType);	
	if splines == nil or #splines <= 0 then return nil;end;
	local randomSplines = {};
	for _, splineNode in pairs(splines) do
		if splineNode ~= nil and self.activeSplines[splineNode] == nil then
			local position = self:getSplinePosition(splineNode, nonBlocking, size);		
			if position ~= nil then table.insert(randomSplines, position);end;
		end;		
	end;	
	if #randomSplines > 0 then return randomSplines[math.random(1, #randomSplines)];else return nil;end;
end;

function AdditionalContractSplineManager:getRandomSplineByMinDistance(x, z, distance, groundType, nonBlocking, size)
	local splines = self:getSplines(groundType);	
	if splines == nil or #splines <= 0 then return nil;end;
	local randomSplines = {};
	for _, splineNode in pairs(splines) do
		if splineNode ~= nil and self.activeSplines[splineNode] == nil then
			local position = self:getSplinePosition(splineNode, nonBlocking, size);
			if position ~= nil and g_additionalContractUtils:getObjectDistance( {x=x, z=z}, {x=position.x, z=position.z} ) >= distance then
				table.insert(randomSplines, position);			
			end;
		end;
	end;
	if #randomSplines > 0 then return randomSplines[math.random(1, #randomSplines)];else return nil;end;
end;

function AdditionalContractSplineManager:getRandomSplineByMaxDistance(x, z, distance, groundType, nonBlocking, size)
	local splines = self:getSplines(groundType);	
	if splines == nil or #splines <= 0 then return nil;end;
	local randomSplines = {};
	for _, splineNode in pairs(splines) do
		if splineNode ~= nil and self.activeSplines[splineNode] == nil then
			local position = self:getSplinePosition(splineNode, nonBlocking, size);
			if position ~= nil and g_additionalContractUtils:getObjectDistance( {x=x, z=z}, {x=position.x, z=position.z} ) <= distance then
				table.insert(randomSplines, position);			
			end;
		end;
	end;	
	if #randomSplines > 0 then return randomSplines[math.random(1, #randomSplines)];else return nil;end;
end;

function AdditionalContractSplineManager:getSplineByPosition(x, z)	
	local splines = self:getSplines();
	if splines == nil or #splines <= 0 then return nil;end;
	for _, splineNode in pairs(splines) do
		if splineNode ~= nil then
			local positions = self:getSplinePositions(splineNode);
			for _, position in pairs(positions) do
				if position ~= nil and position.x == x and position.z == z then
					return position;
				end;
			end;
		end;
	end;	
	return nil;	
end;

function AdditionalContractSplineManager:getSplinePosition(splineNode, nonBlocking, size, testHeight, collisionMask)
	local positions = self:getSplinePositions(splineNode);
	local position = table.getRandomElement(positions);
	if position ~= nil and nonBlocking then
		local x, z = g_additionalContractPointManager:getNonBlockingPosition(position.x, position.z, position.dirX, position.dirZ, size, testHeight, collisionMask);
		if x ~= nil and z ~= nil then position.x = x;position.z = z;else return nil;end;
	end;
	if position ~= nil then return position;else return nil;end;
end;

function AdditionalContractSplineManager:getSplinePositions(splineNode)
	local positions = {};

	function getSplineProperties(node, p)
		local x, y, z = getSplinePosition(node, p);
		local dirX, dirY, dirZ = getSplineDirection(node, p);
		
		local rotY = MathUtil.getYRotationFromDirection(dirX, dirZ);

		return {
			x = x;
			y = y;
			z = z;
			rotY = rotY;
			dirX = dirX;
			dirY = dirY;
			dirZ = dirZ;
			splineNode = node;
		};
	end;

	local splineLength = getSplineLength(splineNode);

	table.insert(positions, getSplineProperties(splineNode, 0.5));
	table.insert(positions, getSplineProperties(splineNode, 0.5 / splineLength));
	table.insert(positions, getSplineProperties(splineNode, 1 - 0.5 / splineLength));

	return positions;
end;

g_additionalContractSplineManager = AdditionalContractSplineManager.new();