AdditionalContractUtils = {};
AdditionalContractUtils.TEST_HEIGHT = 70;
AdditionalContractUtils.ACCEPTS_LANGUAGE = {"de","en","pl"};
local AdditionalContractUtils_mt = Class(AdditionalContractUtils);

function AdditionalContractUtils.new()	
	local self = {};
	setmetatable(self, AdditionalContractUtils_mt);	
	return self;
end;

function AdditionalContractUtils:getObjectsSpawnPosition(trigger, checkEmptyTrigger, numObjects, objectConfig, testHeight)
	local canSpawn = 0;
	local objectsSpawnPosition = {};
	local sizeX, _, sizeZ = unpack(objectConfig.size);	
	local offX, _, offZ = unpack(objectConfig.offset);
	local x, y, z;
	local rx, ry, rz;
	if trigger.triggerId ~= nil then
		x, y, z = getWorldTranslation(trigger.triggerId);
		rx, ry, rz = getWorldRotation(trigger.triggerId);
	else
		x, y, z = trigger.x, trigger.y, trigger.z;
		rx, ry, rz = trigger.rx, trigger.ry, trigger.rz;
	end;
	
	local terrainHeightY = getTerrainHeightAtWorldPos(g_terrainNode, x, 0, z);
	if y < terrainHeightY then y = terrainHeightY;end;
	local rowOffset = sizeZ / 2 + 0.3;
	local xCellOffset = sizeX + 0.1;
	local dirX, dirZ = MathUtil.getDirectionFromYRotation(ry);
	local theta = math.atan2(dirZ, dirX);
	local rcos = math.cos(theta);
	local rsin = math.sin(theta);
	if (checkEmptyTrigger == nil or checkEmptyTrigger) and not self:isTriggerEmpty(x, y, z, rx, ry, rz, sizeX, sizeZ, testHeight) then
		return false, objectsSpawnPosition;
	end;
	
	for i=1, numObjects do
		local dx = 0;
		if i >= 5 then
			dx = -xCellOffset;
		elseif i >= 3 then
			dx = xCellOffset;
		end;
		local dz = rowOffset;
		if i % 2 == 0 then
			dz = -rowOffset;		
		end;
		dx = rcos * dx - rsin * dz;
		dz = rsin * dx + rcos * dz;		
		table.insert(objectsSpawnPosition, {x+dx,y,z+dz,rx,ry,rz});
		canSpawn = canSpawn + 1;			
	end;
	return canSpawn == numObjects, objectsSpawnPosition;
end;

function AdditionalContractUtils:isTriggerEmpty(x, y, z, rx, ry, rz, objectSizeX, objectSizeZ, testHeight)
	local testHeight = testHeight or AdditionalContractUtils.TEST_HEIGHT;	
	self.tempHasCollision = false;
	local mask = CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.PLAYER + CollisionFlag.TREE;
	overlapBox(x, y, z, rx, ry, rz, 3 * (objectSizeX + 0.1), testHeight * 0.5, 2 * (objectSizeZ + 0.1), "collisionTestCallback", self, mask);
	return not self.tempHasCollision;
end;

function AdditionalContractUtils:collisionTestCallback(transformId)
	if g_currentMission.nodeToObject[transformId] ~= nil or (g_currentMission.players[transformId] ~= nil or g_currentMission:getNodeObject(transformId) ~= nil) then
		self.tempHasCollision = true;
	end;
end;

function AdditionalContractUtils:isLanguageAvailable()	
	if AdditionalContractUtils.ACCEPTS_LANGUAGE[g_languageShort] ~= nil then return true;else return false;end;
	--if g_languageShort == "de" or g_languageShort == "en" then return true;else return false;end;	
end;

function AdditionalContractUtils:getStringToVector(vector)
	local stringV = "";
	if vector == nil or type(vector) ~= "table" then return stringV;end;
	if vector[2] == nil then stringV = tostring(vector[1]);else stringV = tostring(vector[math.random(1, tonumber(#vector))]);end;
	return stringV;
end;

function AdditionalContractUtils:getIntegerToVector(vector)
	local integer = -1;
	if vector == nil or type(vector) ~= "table" then return integer;end;
	local vector1, vector2 = unpack(vector);	
	if vector2 == nil then integer = tonumber(vector1);else integer = math.random(tonumber(vector1), tonumber(vector2));end;
	return integer;
end;

function AdditionalContractUtils:getTypToString(str, numberTyp)
	if tonumber(str) then 
		if numberTyp ~= nil and true then return AdditionalContractUtils:getTypToNumber(tonumber(str));end;
		return tonumber(str);
	elseif string.lower(str) == "true" then return true;elseif string.lower(str) == "false" then return false;end;
	return str;
end;

function AdditionalContractUtils:getTypToNumber(number)	
	local numberTyp = "float"
	if number % 1 == 0 then
		numberTyp = "integer";	
	end;
	return numberTyp, number;
end;

function AdditionalContractUtils:getObjectDistance(object1, object2)
	if object2 == nil or object1 == nil or object2.x == nil or object2.z == nil or object1.x == nil or object1.z == nil then return -1;end;
	return MathUtil.vector2Length(object1.x-object2.x, object1.z-object2.z);
end;

function AdditionalContractUtils:getVectorNFromString(input, num, returnForce, isString)
	function trim(str)
		local n = str:find("%S");

		return n and str:match(".*%S", n) or "";
	end;
	function splitString(splitPattern, text)
		local results = {};

		if text ~= nil then
			local start = 1;
			local splitStart, splitEnd = string.find(text, splitPattern, start, true);

			while splitStart ~= nil do
				table.insert(results, string.sub(text, start, splitStart - 1));

				start = splitEnd + 1;
				splitStart, splitEnd = string.find(text, splitPattern, start, true);
			end;

			table.insert(results, string.sub(text, start));
		end;

		return results;
	end;
	
	if input == nil then
		return nil;
	end;

	local strings = splitString(" ", trim(input));

	if num == nil then
		num = table.getn(strings);

		if num == 0 then
			return nil;
		end;
	end;

	if table.getn(strings) ~= num then
		if returnForce == nil or not returnForce then print("Error: Invalid " .. num .. "-vector '" .. input .. "'");end;
		if returnForce then return {input};end;
		return nil;
	end;

	local results = {};

	for i = 1, num do
		if isString == nil or not isString then
			table.insert(results, tonumber(strings[i]));
		else
			table.insert(results, tostring(strings[i]));
		end;
	end;

	return results;
end;

function AdditionalContractUtils:loadMissionsXml(xmlFilename)
	local missionsTable = {};
	local xmlFile = loadXMLFile("MissionsXml", xmlFilename);

	if not xmlFile then
		g_logManager:xmlError(xmlFilename, "File could not be opened");
		return nil;
	end;

	local i = 0;

	while true do
		local key = string.format("missions.mission(%d)", i);

		if not hasXMLProperty(xmlFile, key) then
			break;
		end;

		local mission = {
			rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, key.. "#rewardScale"), 1);
			name = getXMLString(xmlFile, key.. "#name");
			title = getXMLString(xmlFile, key.. "#title");
			typ = getXMLString(xmlFile, key.. "#typ");
			description = getXMLString(xmlFile, key.. ".description");
			npc = getXMLInt(xmlFile, key.. "#npcIndex");
			id = i + 1;
		};
		local npc = g_npcManager:getNPCByIndex(getXMLInt(xmlFile, key.. "#npcIndex"));
		mission.npcIndex = g_npcManager:getRandomIndex();

		if npc ~= nil then
			mission.npcIndex = npc.index;
		end;

		local npc = g_npcManager:getNPCByName(getXMLString(xmlFile, key.. "#npcName"));

		if npc ~= nil then
			mission.npcIndex = npc.index;
		end;

		if mission.name == nil then
			print("ERROR: universal mission definition requires name")
		else
			mission.pickupTriggers = {};
			mission.dropoffTriggers = {};
			mission.objects = {};
			mission.fillTypes = {};
			local j = 0;

			while true do
				local subKey = string.format("%s.pickupTrigger(%d)", key, j);

				if not hasXMLProperty(xmlFile, subKey) then
					break;
				end;

				local index = getXMLString(xmlFile, subKey.. "#index");

				if index == nil then
					g_logManager:xmlError(xmlFilename, "Pickup trigger requires valid index");
				else
					table.insert(mission.pickupTriggers, {
						index = index;
						rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, subKey.. "#rewardScale"), 1);
						title = getXMLString(xmlFile, subKey.. "#title");
					});
				end;

				j = j + 1;
			end;

			j = 0;

			while true do
				local subKey = string.format("%s.dropoffTrigger(%d)", key, j);

				if not hasXMLProperty(xmlFile, subKey) then
					break;
				end;

				local index = getXMLString(xmlFile, subKey.. "#index");

				if index == nil then
					g_logManager:xmlError(xmlFilename, "Dropoff trigger requires valid index");
				else
					table.insert(mission.dropoffTriggers, {
						index = index;
						rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, subKey.. "#rewardScale"), 1);
						title = getXMLString(xmlFile, subKey.. "#title");
					});
				end;

				j = j + 1;
			end;

			j = 0;

			while true do
				local subKey = string.format("%s.object(%d)", key, j);

				if not hasXMLProperty(xmlFile, subKey) then					
					break;
				end;

				local filename = Utils.getFilename(getXMLString(xmlFile, subKey.. "#filename"), AdditionalContracts.modDir);
				
				if filename == nil then
					g_logManager:xmlError(xmlFilename, "Object requires valid filename");
				else
					function getConfiguration()
						local c = 0;
						local configuration = nil;
						if hasXMLProperty(xmlFile, subKey .. ".configuration") then							
							while true do							
								local configurationSubKey = string.format("%s.configuration(%d)", subKey, c);
								if not hasXMLProperty(xmlFile, configurationSubKey) then return configuration;end;
								local configurationName = getXMLString(xmlFile, configurationSubKey.. "#name");
								local configurationId_STRING = getXMLString(xmlFile, configurationSubKey.. "#id_string");
								local configurationId_INT = getXMLInt(xmlFile, configurationSubKey.. "#id_int");								
								configuration = configuration or {};
								if configurationId_INT ~= nil then
									configuration[configurationName] = tonumber(configurationId_INT);
								elseif configurationId_STRING ~= nil then								
									configuration[configurationName] = configurationId_STRING;
								end;								
								c = c + 1;
							end;
						end;
						return configuration;						
					end;					
					table.insert(mission.objects, {
						index = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#index"), j + 1);
						filename = filename;
						minMax = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#minMax"), nil), 2, true);
						size = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#size"), "1 1 1"), 3);
						offset = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#offset"), "0 0 0"), 3);
						access = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#access"), false);						
						propertyState = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#propertyState"), nil);
						storeSpawn = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#storeSpawn"), nil);
						category = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#category"), nil);	
						targetTriggerDistance = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#targetTriggerDistance"), nil);	
						setOperatingTime = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#setOperatingTime"), nil);
						canBeReset = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#canBeReset"), nil);
						maxSpeed = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#maxSpeed"), nil), 2, true);
						damagePercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#damagePercent"), nil), 2, true);
						wearPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#wearPercent"), nil), 2, true);
						dirtPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#dirtPercent"), nil), 2, true);						
						fuelPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#fuelPercent"), nil), 2, true);
						fillPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#fillPercent"), nil), 2, true);						
						fillType = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#fillType"), nil);						
						rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, subKey.. "#rewardScale"), 1);
						groundType = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#groundType"), nil);
						pickupHotspotTyp = getXMLString(xmlFile, subKey.. "#pickupHotspotTyp", "unknown1");
						dropoffHotspotTyp = getXMLString(xmlFile, subKey.. "#dropoffHotspotTyp", "dropoff");
						typTitle = getXMLString(xmlFile, subKey.. "#typTitle");
						typDescription = getXMLString(xmlFile, subKey.. "#typDescription");						
						leasingSize = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#leasingSize"), "NONE"); --leasing
						leasingVariant = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#leasingVariant"), "default"):upper(); --leasing
						classOverlay = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#classOverlay"), "tractor"); --Mod Missions Display
						typOverlay = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#typOverlay"), "unknownTrailer"); --Mod Missions Display
						configuration = getConfiguration();
					});
					
				end;

				j = j + 1;
			end;
			
			j = 0;
			
			while true do
				local subKey = string.format("%s.fillTypes(%d)", key, j);

				if not hasXMLProperty(xmlFile, subKey) then					
					break;
				end;	
										
				table.insert(mission.fillTypes, {
					index = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#index"), j + 1);					
					minMax = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#minMax"), nil), 2, true);
					fillVolume = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#fillVolume"), nil), 2, true);						
					fillType = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#fillType"), nil), nil, true, true);						
					rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, subKey.. "#rewardScale"), 1);					
					typDescription = getXMLString(xmlFile, subKey.. "#typDescription");	
					
					
				});				

				j = j + 1;
			end;
			table.insert(missionsTable, mission);
			
		end;

		i = i + 1;
	end;

	delete(xmlFile);
	
	return missionsTable;
end;

function AdditionalContractUtils:isLoadedHotspotOverlays()
	return g_currentMission.hlUtils ~= nil and g_currentMission.hlUtils.overlays ~= nil and g_currentMission.hlUtils.overlays["AdditionalContracts"] ~= nil and g_currentMission.hlUtils.overlays["AdditionalContracts"]["mapHotspots"] ~= nil;
end;

function AdditionalContractUtils:addMissionLeasingVehicles(xmlFilename)
	g_missionManager:addPendingMissionVehiclesFile(xmlFilename, AdditionalContracts.modDir);
end;

g_additionalContractUtils = AdditionalContractUtils.new();