AdditionalContractUtils = {};
AdditionalContractUtils.TEST_HEIGHT = 70;
AdditionalContractUtils.ACCEPTS_LANGUAGE = {de=true,en=true,pl=true,cz=true,it=true,fr=true}; --jp=false, es=false, ru=false, pt=false, hu=false, nl=false, cs=false, ct=false, br=false, tr=false, ro=false, kr=false, ea=false, da=false, fi=false, no=false, sv=false, fc=false, uk=false, vi=false, id=false
local AdditionalContractUtils_mt = Class(AdditionalContractUtils);

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

function AdditionalContractUtils:generateTypId(r1, r2)
	local randomNr = math.random (r1 or 1, r2 or 9);			
	local typIdDate_D = string.format("%0.2i", Utils.getNoNil(getDate("%d"), tostring(randomNr)));		
	local typIdDate_M = string.format("%0.2i", Utils.getNoNil(getDate("%m"), tostring(randomNr))); 
	local typIdDate_Y = string.format("%0.2i", Utils.getNoNil(getDate("%y"), tostring(randomNr))); 
	local typIdTimeH = string.format("%0.2i", Utils.getNoNil(getDate("%I"), tostring(randomNr)));	
	local typIdTimeM = Utils.getNoNil(getDate("%M"), tostring(randomNr));
	if tonumber(typIdTimeM) <= 0 then typIdTimeM = "60";else typIdTimeM = string.format("%0.2i", typIdTimeM);end;
	local typIdTimeS = Utils.getNoNil(getDate("%S"), tostring(randomNr));
	if tonumber(typIdTimeS) <= 0 then typIdTimeS = "60";else typIdTimeS = string.format("%0.2i", typIdTimeS);end;
	local typIdString = typIdDate_D.. typIdDate_M.. typIdDate_Y.. typIdTimeH.. typIdTimeM.. typIdTimeS.. tostring(randomNr);		
	return tonumber(typIdString);
end;

function AdditionalContractUtils:isLanguageAvailable()	
	if AdditionalContractUtils.ACCEPTS_LANGUAGE[g_languageShort] ~= nil and AdditionalContractUtils.ACCEPTS_LANGUAGE[g_languageShort] == true then return true;else return false;end;	
end;

function AdditionalContractUtils:addModSupport(modName, variants, replaceModSupport)
	if modName == nil then return false, "not found modName (nil)";end;
	if modName ~= nil and self.modSupport[modName] ~= nil and replaceModSupport then self.modSupport[modName] = nil;end;	
	if modName ~= nil and self.modSupport[modName] == nil then
		self.modSupport[modName] = {state=variants == nil,variants={}};
		if variants ~= nil then 
			for variant, state in pairs(variants) do
				self.modSupport[modName].variants[variant] = state;
			end;
		end;
		return true;
	else
		return false, "already available modName (".. tostring(modName).. ")";
	end;
end;

function AdditionalContractUtils:addModSupportVariants(modName, variant, state)
	if modName == nil then return false, "not found modName (nil)";end;
	if self.modSupport[modName] ~= nil and variant ~= nil then		
		self.modSupport[modName].variants[variant] = state;		
	else
		return false, "unavailable modName (".. tostring(modName).. ") or variant (".. tostring(variant).. ")";
	end;
end;

function AdditionalContractUtils:removeModSupport(modName)
	if modName == nil then return false, "not found modName (nil)";end;
	if self.modSupport[modName] ~= nil then
		self.modSupport[modName] = nil;
	else
		return false, "unavailable modName (".. tostring(modName).. ")";
	end;
end;

function AdditionalContractUtils:hasModSupport(modName, variant)
	if modName == nil or self.modSupport[modName] == nil then return false;end;
	if variant == nil then return self.modSupport[modName].state;end;
	if self.modSupport[modName].variants[variant] == nil then return false;end;
	return self.modSupport[modName].variants[variant];
end;

function AdditionalContractUtils:getConvertFilename(filename, modDir)
	local filenameLower = string.lower(filename)
	if string.startsWith(filenameLower, "$moddir$") then
		local startIndex = 9;
		local f, l = string.find(filename, "/", 9);
		local replaceDir;
		if f == nil or (l == nil or startIndex >= f - 1) then
			replaceDir = filename;
		else			
			replaceDir = string.sub(filename, startIndex, f - 1);
		end;
		local modDir = g_modNameToDirectory[replaceDir] or modDir;
		if modDir == nil then
			return g_modsDirectory.. replaceDir;
		end;
		return modDir.. string.sub(filename, f + 1);
	end;
	return filename;
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:getRandomElement(objects, change, lastRandomTable, what)	
	local randomElement = nil;
	if change ~= nil and change > 0 and what ~= nil and lastRandomTable ~= nil and #lastRandomTable > 0 then
		for i = math.random(1, #objects), #objects do
			if change <= 0 then break;end;
			if objects[i][what] ~= nil and lastRandomTable[objects[i][what]] == nil then return objects[i];end;
			change = change - 1;
		end;		
	else
		randomElement = table.getRandomElement(objects);
	end;
	return randomElement;
end;

function AdditionalContractUtils:getRandomXmlMissionByVariant(xml, variant)
	if xml == nil or variant == nil then return nil;end;	
	local randomElements = {};	
	for m=1, #xml do
		local missionXml = xml[m];
		if missionXml ~= nil then
			if missionXml.variant ~= nil and missionXml.variant == variant then table.insert(randomElements, missionXml);end;
		end;			
	end;
	if #randomElements > 0 then	return table.getRandomElement(randomElements);end;
	return nil;
end;

function AdditionalContractUtils:getRandomXmlMissionByTyp(xml, typ)
	if xml == nil or typ == nil then return nil;end;	
	local randomElements = {};	
	for m=1, #xml do
		local missionXml = xml[m];
		if missionXml ~= nil then
			if missionXml.typ ~= nil and missionXml.typ == typ then table.insert(randomElements, missionXml);end;
		end;			
	end;
	if #randomElements > 0 then	return table.getRandomElement(randomElements);end;
	return nil;
end;

function AdditionalContractUtils:getRandomXmlMissionByTypAndVariant(xml, typ, variant)
	if xml == nil or typ == nil or variant == nil then return nil;end;	
	local randomElements = {};	
	for m=1, #xml do
		local missionXml = xml[m];
		if missionXml ~= nil then
			if missionXml.typ ~= nil and missionXml.typ == typ and missionXml.variant ~= nil and missionXml.variant == variant then table.insert(randomElements, missionXml);end;
		end;			
	end;
	if #randomElements > 0 then	return table.getRandomElement(randomElements);end;
	return nil;
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:shuffle(t)
	local n = #t;
	while n > 2 do
		local k = math.random(n)
		t[k] = t[n];
		t[n] = t[k];
		n = n - 1;
	end;
	return t;
end;

function AdditionalContractUtils:transform(x, y, z, dirX, dirY, dirZ, upX, upY, upZ, xOffset, yOffset, zOffset)
	local normX, normY, normZ = self:crossProduct(upX, upY, upZ, dirX, dirY, dirZ);
	return x + normX * xOffset + upX * yOffset + dirX * zOffset, y + normY * xOffset + upY * yOffset + dirY * zOffset, z + normZ * xOffset + upZ * yOffset + dirZ * zOffset;
end;

function AdditionalContractUtils:crossProduct(ax, ay, az, bx, by, bz)
	return ay * bz - az * by, az * bx - ax * bz, ax * by - ay * bx;
end;

function AdditionalContractUtils:canAddLimitedObjects(objectConfig, slotSystem, num)
	if num == nil or num <= 0 or (objectConfig == nil and slotSystem == nil) then return true;end;
	local slotSystem = slotSystem;
	if objectConfig ~= nil and objectConfig.category ~= nil then
		if string.find(objectConfig.category, "pallet") then slotSystem = "LIMITED_OBJECT_PALLET";
		elseif string.find(objectConfig.category, "bale") then slotSystem = "LIMITED_OBJECT_BALE";end;
	end;
	if slotSystem == nil then return true;end;
	return g_currentMission.slotSystem:getCanAddLimitedObjects(SlotSystem[slotSystem], num);
end;

function AdditionalContractUtils:getConvertFillTypeDataOnSavegame(fillTypeData)
	if fillTypeData == nil or #fillTypeData <= 0 then return nil;end;
	local convertData = nil;
	for f=1, #fillTypeData do
		if fillTypeData[f] ~= nil then
			local fillTypeName = fillTypeData[f].fillType:upper();
			local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName);
			if fillTypeIndex ~= nil then
				if convertData == nil then convertData = {acceptsFillType={}};end;
				convertData.acceptsFillType[fillTypeIndex] = {expectedLiters=fillTypeData[f].expectedLiters};
			end;
		end;
	end;
	return convertData;
end;

function AdditionalContractUtils:getConvertFillTypeData(numFillTypes, fillTypeData, typVariant)
	if fillTypeData == nil or #fillTypeData <= 0 then return nil;end;
	local convertData = nil;
	for f=1, numFillTypes do
		if fillTypeData[f] ~= nil then
			local fillTypeName = fillTypeData[f].fillType:upper();
			if fillTypeName ~= "RANDOM" then
				local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName);
				if fillTypeIndex ~= nil then
					local hasPrice, _ = g_additionalContractMapData:hasPricePerLiter(fillTypeIndex);
					if hasPrice then
						local loadingType = fillTypeData[f].loadingType;
						local fillVolume = g_additionalContractUtils:getIntegerToVector(fillTypeData[f].fillVolume)/1000;
						fillVolume = math.ceil(fillVolume) * 1000;
						local rewardScale = fillTypeData[f].rewardScale or 1;					
						if convertData == nil then 
							convertData = {numFillTypes=1, expectedLiters=fillVolume, acceptsFillType={}};
							convertData.acceptsFillType[fillTypeIndex] = {expectedLiters=fillVolume, rewardScale=rewardScale, loadingType=loadingType};				
						elseif convertData.acceptsFillType[fillTypeIndex] ~= nil then
							convertData.acceptsFillType[fillTypeIndex].expectedLiters = convertData.acceptsFillType[fillTypeIndex].expectedLiters + fillVolume;
							convertData.expectedLiters = convertData.expectedLiters + fillVolume;							
						else
							convertData.acceptsFillType[fillTypeIndex] = {expectedLiters=fillVolume, rewardScale=rewardScale, loadingType=loadingType};
							convertData.expectedLiters = convertData.expectedLiters + fillVolume;
							convertData.numFillTypes = convertData.numFillTypes + 1;
						end;
					end;				
				end;
			end;
		end;
	end;
	return convertData;
end;

function AdditionalContractUtils:getConvertAnimalDataOnSavegame(animalData)
	if animalData == nil then return nil;end;
	if animalData.animalTypeIndex ~= nil then return g_additionalContractUtils:getConvertAnimalDataOnSavegameOldKey(animalData);elseif animalData.animalTypeName ~= nil then return g_additionalContractUtils:getConvertAnimalDataOnSavegameNewKey(animalData);end;
	return nil;
end;

function AdditionalContractUtils:getConvertAnimalDataOnSavegameOldKey(animalData)
	if animalData == nil or animalData.animalTypeIndex == nil then return nil;end;
	if animalData.animalTypeIndex == 0 then 
		return {animalTypeTitle="ALL",animalTypeIndex=0,subTypeIndex=0,minHealth=animalData.minHealth,minAge=animalData.minAge,minFitness=animalData.minFitness};
	end;
	local animalTypeName = "";
	local subTypeIndex = nil;
	local subTypeName = nil;
	local animalType = g_currentMission.animalSystem:getTypeByIndex(animalData.animalTypeIndex);
	if animalType ~= nil then
		animalTypeName = animalType.name;
		animalTypeTitle = animalType.groupTitle;
		
		if animalData.subTypeIndex ~= nil and animalData.subTypeIndex ~= 0 and animalType.subTypes ~= nil and #animalType.subTypes > 0 then				
			for a=1, #animalType.subTypes do
				if animalType.subTypes[a] == animalData.subTypeIndex then 
					subTypeIndex = animalData.subTypeIndex;
					break;
				end;
			end;			
			if subTypeIndex ~= nil then
				local animalSubType = g_currentMission.animalSystem:getSubTypeByIndex(subTypeIndex);
				if animalSubType ~= nil then subTypeName = animalSubType.name;end;
			end;
		end;
		return {animalTypeTitle=animalTypeTitle,animalTypeIndex=animalData.animalTypeIndex,animalTypeName=animalTypeName,subTypeIndex=subTypeIndex or 0,subTypeName=subTypeName,minHealth=animalData.minHealth,minAge=animalData.minAge,minFitness=animalData.minFitness}; --age months
	end;
	return nil;
end;

function AdditionalContractUtils:getConvertAnimalDataOnSavegameNewKey(animalData)
	if animalData == nil or animalData.animalTypeName == nil then return nil;end;
	if animalData.animalTypeName:upper() == "ALL" then 
		return {animalTypeTitle="ALL",animalTypeIndex=0,subTypeIndex=0,minHealth=animalData.minHealth,minAge=animalData.minAge,minFitness=animalData.minFitness};
	end;
	local animalTypeIndex = g_currentMission.animalSystem:getTypeIndexByName(animalData.animalTypeName);
	if animalTypeIndex ~= nil then
		local animalType = g_currentMission.animalSystem:getTypeByIndex(animalTypeIndex);
		if animalType ~= nil then
			animalTypeName = animalType.name;
			animalTypeTitle = animalType.groupTitle;
			
			local subType = nil;
			local subTypeName = nil;
			if animalData.subTypeName:upper() ~= "ALL" then subType = g_currentMission.animalSystem:getSubTypeByName(animalData.subTypeName:upper());end;
			local subTypeIndex = nil;
			if subType ~= nil and subType.subTypeIndex ~= nil and animalType.subTypes ~= nil and #animalType.subTypes > 0 then	
				for a=1, #animalType.subTypes do
					if animalType.subTypes[a] == subType.subTypeIndex then 
						subTypeIndex = subType.subTypeIndex;
						break;
					end;
				end;
				if subTypeIndex ~= nil then
					local animalSubType = g_currentMission.animalSystem:getSubTypeByIndex(subTypeIndex);
					if animalSubType ~= nil then subTypeName = animalSubType.name;end;
				end;
			end;
			return {animalTypeTitle=animalTypeTitle,animalTypeIndex=animalTypeIndex,animalTypeName=animalTypeName,subTypeIndex=subTypeIndex or 0,subTypeName=subTypeName,minHealth=animalData.minHealth,minAge=animalData.minAge,minFitness=animalData.minFitness}; --age months
		end;
	end;	
	return nil;	
end;

function AdditionalContractUtils:getConvertAnimalData(object, animalData, typVariant)
	if object == nil or object.animalTypes == nil or #object.animalTypes <= 0 or animalData == nil then return nil;end;
	local animalTypeIndex = nil;
	local healthPercent = nil;
	local age = nil;
	local fitnessPercent = nil;	
	if animalData.typ:upper() == "RANDOM" then
		animalTypeIndex = self:getAcceptsAnimals(object.animalTypes, typVariant);
	elseif animalData.typ:upper() == "AVAILABLEPLACEABLE" then
		
	
	elseif animalData.typ:upper() ~= "ALL" then
		if not g_additionalContractMapData:isAcceptsAnimal(animalData.typ, typVariant) then return nil;end;
		local typIndex = g_currentMission.animalSystem:getTypeIndexByName(animalData.typ);
		if typIndex == nil then return nil;end;
		for a=1, #object.animalTypes do
			if object.animalTypes[a] == typIndex then animalTypeIndex = typIndex;break;end;
		end;		
	end;
	
	function setCondition()
		if animalData.healthPercent ~= nil then
			healthPercent = g_additionalContractUtils:getIntegerToVector(animalData.healthPercent); --/100;
		else
			healthPercent = 0;
		end;
		if animalData.age ~= nil then
			age = g_additionalContractUtils:getIntegerToVector(animalData.age);
		else
			age = 0;
		end;
		if animalData.fitnessPercent ~= nil and animalTypeIndex == AnimalType.HORSE then 
			fitnessPercent = g_additionalContractUtils:getIntegerToVector(animalData.fitnessPercent); --/100;
		else
			fitnessPercent = 0;
		end;
	end;
	
	if animalTypeIndex ~= nil then		
		local animalTypeName = "";
		local subTypeIndex = nil;
		local subTypeName = nil;
		local animalType = g_currentMission.animalSystem:getTypeByIndex(animalTypeIndex);
		if animalType ~= nil then
			animalTypeName = animalType.name;
			animalTypeTitle = animalType.groupTitle;
			
			if animalType.subTypes ~= nil and #animalType.subTypes > 0 and animalData.subType:upper() ~= "ALL" then
				if animalData.subType:upper() == "RANDOM" then
					subTypeIndex = table.getRandomElement(animalType.subTypes);
				else
					local subType = g_currentMission.animalSystem:getSubTypeByName(animalData.subType)
					if subType ~= nil and subType.subTypeIndex ~= nil then
						for a=1, #animalType.subTypes do
							if animalType.subTypes[a] == subType.subTypeIndex then 
								subTypeIndex = subType.subTypeIndex;
								break;
							end;
						end;
					end;
				end;
				if subTypeIndex ~= nil then
					local animalSubType = g_currentMission.animalSystem:getSubTypeByIndex(subTypeIndex);
					if animalSubType ~= nil then subTypeName = animalSubType.name;end;
				end;
			end;			
		end;
		setCondition();		
		return {animalTypeTitle=animalTypeTitle,animalTypeIndex=animalTypeIndex,animalTypeName=animalTypeName,subTypeIndex=subTypeIndex or 0,subTypeName=subTypeName,minHealth=healthPercent,minAge=age,minFitness=fitnessPercent}; --age months
	elseif animalData.typ:upper() == "ALL" then
		setCondition();
		return {animalTypeTitle="ALL",animalTypeIndex=0,subTypeIndex=0,minHealth=healthPercent,minAge=age,minFitness=fitnessPercent}; --age months
	end;
	return nil;	
end;

function AdditionalContractUtils:getAcceptsAnimals(animalTypes, typVariant)
	local acceptsAnimals = g_additionalContractMapData:getAcceptsAnimals(animalTypes, typVariant);
	if #acceptsAnimals > 0 then
		return g_currentMission.animalSystem:getTypeIndexByName(table.getRandomElement(acceptsAnimals));
	end;	
	return nil;
end;

function AdditionalContractUtils: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;

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

		return n and str:match(".*%S", n) or "";
	end;
		
	if input == nil then
		return nil;
	end;

	local strings = self: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
		Logging.xmlWarning(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");
			spec = getXMLString(xmlFile, key.. "#spec");
			description = getXMLString(xmlFile, key.. "#description");
			variant = getXMLInt(xmlFile, key.. "#variant");
			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
			Logging.xmlWarning("ERROR: universal mission definition requires name");			
		else
			mission.pickupTriggers = {};
			mission.dropoffTriggers = {};
			mission.objects = {};
			mission.tools = {};
			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
					Logging.xmlWarning(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
					Logging.xmlWarning(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 = getXMLString(xmlFile, subKey.. "#filename");
				if filename ~= nil and filename:len() == 0 then 
				
				else
					filename = Utils.getFilename(getXMLString(xmlFile, subKey.. "#filename"), AdditionalContracts.modDir);
				end;				
				
				if filename == nil then
					Logging.xmlWarning(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;
					function getAnimalData()
						local a = 0;
						local animalData = {};
						if hasXMLProperty(xmlFile, subKey .. ".animalData") then	
							while true do							
								local animalDataSubKey = string.format("%s.animalData(%d)", subKey, a);
								if not hasXMLProperty(xmlFile, animalDataSubKey) then return animalData;end;								
								local typ = Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#typ"), nil);
								local subType = Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#subType"), nil);								
								if typ ~= nil and subType ~= nil then									
									table.insert(animalData, {
										typ = typ:upper();
										subType = subType:upper();
										name = Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#name"), "RANDOM");
										prefix = Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#prefix"), nil);
										age = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#age"), nil), 2, true);
										dirtPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#dirtPercent"), nil), 2, true);
										fitnessPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#fitnessPercent"), nil), 2, true);
										healthPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#healthPercent"), nil), 2, true);
										ridingPercent = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#ridingPercent"), nil), 2, true);
										numAnimals = Utils.getNoNil(getXMLInt(xmlFile, animalDataSubKey.. "#num"), 1);
										canBeSold = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#canBeSold"), nil);
										price = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, animalDataSubKey.. "#price"), nil), 2, true);										
									});
								end;	
								a = a + 1;
							end;
						end;
						return animalData;
					end;
					function getFillTypeData()
						local f = 0;
						local fillTypeData = {};
						if hasXMLProperty(xmlFile, subKey .. ".fillTypeData") then	
							while true do							
								local fillTypeDataSubKey = string.format("%s.fillTypeData(%d)", subKey, f);
								if not hasXMLProperty(xmlFile, fillTypeDataSubKey) then return fillTypeData;end;														
								local fillType = Utils.getNoNil(getXMLString(xmlFile, fillTypeDataSubKey.. "#fillType"), nil); --self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, fillTypeDataSubKey.. "#fillType"), nil), nil, true, true);						
								local fillVolume = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, fillTypeDataSubKey.. "#fillVolume"), nil), 2, true);
								local rewardScale = Utils.getNoNil(getXMLFloat(xmlFile, fillTypeDataSubKey.. "#rewardScale"), 1);
								local loadingType = Utils.getNoNil(getXMLString(xmlFile, fillTypeDataSubKey.. "#loadingType"), "UNKNOWN");
								if fillType ~= nil and fillVolume ~= nil then
									table.insert(fillTypeData, {
										fillType = fillType;
										fillVolume = fillVolume;
										rewardScale = rewardScale;
										loadingType = loadingType;
									});								
								end;								
								f = f + 1;
							end;
						end;
						return fillTypeData;
					end;
					table.insert(mission.objects, {
						index = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#index"), j + 1);
						filename = filename;
						distanceTool = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#distanceTool"), false);
						rotation = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#rotation"), nil), 3);
						numBundle = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#numBundle"), 0);
						attacherInfo = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#attacherInfo"), false);
						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);	
						--ignoreFarmlandTyp = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#ignoreFarmlandTyp"), 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);
						mapHotspotAvailable = getXMLString(xmlFile, subKey.. "#mapHotspotAvailable", nil);
						mapHotspotType = getXMLString(xmlFile, subKey.. "#mapHotspotType", 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();
						animalData = getAnimalData();
						fillTypeData = getFillTypeData();
						canBeSold = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#canBeSold"), nil);
						aiAccess = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#aiAccess"), "3"); --ai active,0=no,1=ls,2=ad,3=ls+ad
					});
					
				end;

				j = j + 1;
			end;
			
			---tools---			
			
			if hasXMLProperty(xmlFile, key .. ".tools") then	
				
				local toolsSubKey = string.format("%s.tools(%d)", key, 0);
				
				j = 0;

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

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

					local filename = Utils.getFilename(getXMLString(xmlFile, subKey.. "#filename"), AdditionalContracts.modDir);
					
					if filename == nil then
						Logging.xmlWarning(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.tools, {
							index = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#index"), j + 1);
							filename = filename;							
							distanceTool = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#distanceTool"), false);
							rotation = self:getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, subKey.. "#rotation"), nil), 3);
							numBundle = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#numBundle"), 0);
							attacherInfo = Utils.getNoNil(getXMLBool(xmlFile, subKey.. "#attacherInfo"), false);
							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);	
							--ignoreFarmlandTyp = Utils.getNoNil(getXMLString(xmlFile, subKey.. "#ignoreFarmlandTyp"), 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);
							mapHotspotAvailable = getXMLString(xmlFile, subKey.. "#mapHotspotAvailable", nil);
							mapHotspotType = getXMLString(xmlFile, subKey.. "#mapHotspotType", 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();
							aiAccess = Utils.getNoNil(getXMLInt(xmlFile, subKey.. "#aiAccess"), "3"); --ai active,0=no,1=ls,2=ad,3=ls+ad
						});
						
					end;

					j = j + 1;
				end;
			end;
			---tools---
			
			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:hasOtherMods(modName, onlySearch)
	if modName ~= nil and (onlySearch == nil or false) and AdditionalContracts.otherMods ~= nil and AdditionalContracts.otherMods[modName] ~= nil then return AdditionalContracts.otherMods[modName];end;
	if onlySearch then return g_modIsLoaded[tostring(modName)] and modName ~= nil;end;	
	return false;
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();