AdditionalContractPointManager = {};
AdditionalContractPointManager.GROUND_ROAD = 1;
AdditionalContractPointManager.GROUND_HARD_TERRAIN = 2;
AdditionalContractPointManager.GROUND_SOFT_TERRAIN = 3;
AdditionalContractPointManager.GROUND_FIELD = 4;
AdditionalContractPointManager.GROUND_UNKNOWN = 5;
AdditionalContractPointManager.COLLISION_MASK = CollisionMask.ALL - CollisionFlag.TERRAIN - CollisionFlag.TERRAIN_DELTA - CollisionFlag.TERRAIN_DISPLACEMENT - CollisionFlag.TRIGGER - CollisionFlag.FILLABLE;
AdditionalContractPointManager.OFFSETS = {{-1,-1},{-1,0},{-1,1},{0,-1},{0,0},{0,1},{1,-1},{1,0},{1,1}};
AdditionalContractPointManager.DEFAULT_SIZE = 2;
AdditionalContractPointManager.TEST_HEIGHT = 70;

local AdditionalContractPointManager_mt = Class(AdditionalContractPointManager);

function AdditionalContractPointManager.new()	
	local self = {};
	setmetatable(self, AdditionalContractPointManager_mt);	
	self.position = {};
	return self;
end;

function AdditionalContractPointManager:removePosition(name, index)
	if name == nil or self.position[name] == nil then return false;end;
	if index ~= nil and self.position[name][index] ~= nil then table.remove(self.position[name], index);return true;end;
	self.position[name] = nil;
	return true;
end;

function AdditionalContractPointManager:insertPosition(name, x, y, z, node)
	if name == nil then return false;end;
	if self.position[name] == nil then self.position[name] = {};end;
	table.insert(self.position[name], {x,y,z,node});
	return true;
end;

function AdditionalContractPointManager:getCollisionMask()
	return AdditionalContractPointManager.COLLISION_MASK;
end;

function AdditionalContractPointManager:getPosition(name, index)
	if name == nil then return self.position;end;
	if index == nil then return self.position[name];end;
	return self.position[name][index];
end;

function AdditionalContractPointManager:getNonBlockingPosition(x, z, dirX, dirZ, size, testHeight, collisionMask)
	local size = size or AdditionalContractPointManager.DEFAULT_SIZE;
	local dirX, dirZ = dirX or 1, dirZ or 0;
	local testHeight = testHeight or AdditionalContractPointManager.TEST_HEIGHT;
	local rotY = MathUtil.getYRotationFromDirection(dirX, dirZ);
	local offsets = g_additionalContractUtils:shuffle(AdditionalContractPointManager.OFFSETS);
	local worldSizeX = g_currentMission.mapWidth;
	local worldSizeZ = g_currentMission.mapHeight;
	local normalizedPosX = math.clamp((x + worldSizeX * 0.5) / worldSizeX, 0, 1);
	local normalizedPosZ = math.clamp((z + worldSizeZ * 0.5) / worldSizeZ, 0, 1);
	local maxPos = 0.995;
	local minPos = 0.005;

	if normalizedPosX < minPos or maxPos < normalizedPosX or normalizedPosZ < minPos or maxPos < normalizedPosZ then
		return nil, nil;
	end;
	local mask = collisionMask or self:getCollisionMask();	
	for _, offset in ipairs(offsets) do
		local offsetX, offsetZ = unpack(offset);
		local ox, _, oz = g_additionalContractUtils:transform(x, 0, z, dirX, 0, dirZ, 0, 1, 0, size * offsetX, size, size * offsetZ);
		local oy = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ox, 0, oz) + 0.05;
		self.positionBlocked = false;
		local s = size * 0.5;

		overlapBox(ox, oy, oz, 0, rotY, 0, s, testHeight * 0.5, s, "overlapCallback", self, mask);

		if not self.positionBlocked then
			return ox, oz;
		end;
	end;
	return nil, nil;
end;

function AdditionalContractPointManager:overlapCallback(transformId)
	if transformId ~= 0 and getHasClassId(transformId, ClassIds.SHAPE) then
		self.positionBlocked = true;
		return false;
	end;
	return true;
end;

function AdditionalContractPointManager:getTerrainLimitedYPosition(x, y, z, offset, collisionMask)
	local terrainY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z);
	local mask = collisionMask or (CollisionFlag.TERRAIN + CollisionFlag.STATIC_OBJECT);
	self.correctedY = -1;

	raycastClosest(x, y + offset, z, 0, -1, 0, 10, "groundRaycastCallback", self, mask);

	return math.max(terrainY, self.correctedY) + offset;
end;

function AdditionalContractPointManager:groundRaycastCallback(hitObjectId, x, y, z, _, _, _, _, _, _, _)
	if getHasTrigger(hitObjectId) then
		return true;
	end;
	self.correctedY = y;
	return true;
end;

function AdditionalContractPointManager:getUnknownGroundTyp()
	return AdditionalContractPointManager.GROUND_UNKNOWN;
end;

function AdditionalContractPointManager:getGroundType(isField, isRoad, groundDepth)
	if isField then
		return AdditionalContractPointManager.GROUND_FIELD;
	elseif isRoad or groundDepth < 0.1 then
		return AdditionalContractPointManager.GROUND_ROAD;
	elseif groundDepth > 0.8 then
		return AdditionalContractPointManager.GROUND_SOFT_TERRAIN;
	else
		return AdditionalContractPointManager.GROUND_HARD_TERRAIN;
	end;
end;
g_additionalContractPointManager = AdditionalContractPointManager.new();