BaleCollectMission = {};
BaleCollectMission.NAME = "baleCollectMission";
BaleCollectMission.INSERT_DELETE_BALES = false; --for debug testing mission
BaleCollectMission.UI_SETTINGS = true;
BaleCollectMission.MAX_NUM_INSTANCE = 2;
BaleCollectMission.DEFAULT_NUM_INSTANCE = 2;
BaleCollectMission.MAX_NUM_SEASON = {2,0,1}; --0=off or max,is,nextDay
BaleCollectMission.BALANCE = {false,0}; --on/off,is
BaleCollectMission.RANDOM = false; --at random pool
BaleCollectMission.ACCEPTS_FRUITTYPE = {GRASS=true}; --maizeplus ready
BaleCollectMission.ACCEPTS_FILLTYPE = {DRYGRASS_WINDROW=true,SILAGE=true,GRASS_WINDROW=true}; --maizeplus ready
BaleCollectMission.KEEP_LIMIT_VALUES = true; --at your own risk (field size max,bales limit)

source(AdditionalContracts.modDir.. "missions/baleCollectMission/BaleCollectChangeSettingsEvent.lua");
source(AdditionalContracts.modDir.. "missions/baleCollectMission/BaleCollectLoadSettingsEvent.lua");

BaleCollectMission.uiData = {
	settings = {		
		baleCollectMission_minMax = BaleCollectMission.DEFAULT_NUM_INSTANCE;		
	};
	controls = {
		{ typName="baleCollectMission", name="baleCollectMission_minMax", autoBind=true, min=0, max=BaleCollectMission.MAX_NUM_INSTANCE, step=1, unit="/ ".. tostring(BaleCollectMission.MAX_NUM_INSTANCE), nillable=false }; --min 0 = off
	};
};

BaleCollectMission.data = {	
	ownTable = {BALES_LIMIT=100, MANY_BALES=79, FIELD_SIZE_MAX=50, SUCCESS_FACTOR=0.95, classOverlay="tractor", typOverlay="unknownTrailer"}; --many bales more vehicles, 4x ? Mod Maps..., field size max for mission (better for performance) incl. check FS25 SlotSystem by Giants
	reward = {dismiss=0, min=0, PER_BALE=310}; 
	jobTypName = g_i18n:getText("contract_field_baleCollect_title");	
};

BaleCollectMission.metadata = {
	interface = "FS25 ...", --new
	title = "Bale Collect Contracts",
	notes = "Dieser Mod generiert Ballen sammeln Aufträge.",
	author = "(by HappyLooser)",		
	build = 4,
	datum = "10.08.2025",
	update = "01.11.2025",
	debugPrint = false, 
	discord = "HappyLooser Modding",
	info = " Link Freigabe,Änderungen,Kopien oder Code Benutzung ist ohne meine Zustimmung nicht erlaubt",
	"##Orginal Link Freigabe: https://www.farming-simulator.com/mods.php"
};

function debugPrint(text, ...)
	if BaleCollectMission.metadata.debugPrint then
		Logging.info(text,...);
	end;
end;

local BaleCollectMission_mt = Class(BaleCollectMission, AbstractFieldMission);
InitObjectClass(BaleCollectMission, "BaleCollectMission");

function BaleCollectMission.registerXMLPaths(schema, key)
	BaleCollectMission:superClass().registerXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#rewardPerBale", "Reward per bale");
	schema:register(XMLValueType.FLOAT, key .. "#failureCostFactor", "Failure cost factor");
	schema:register(XMLValueType.FLOAT, key .. "#failureCostOfTotal", "Failure cost of total");
end;

function BaleCollectMission.registerSavegameXMLPaths(schema, key)
	BaleCollectMission:superClass().registerSavegameXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#baleTypeIndex", "Bale type");
	schema:register(XMLValueType.INT, key .. "#numOfBales", "Bale count");
	schema:register(XMLValueType.STRING, key .. ".bale(?)#uniqueId", "Spawned bale");
	local baleCollectKey = string.format("%s.baleCollect", key);
	schema:register(XMLValueType.STRING, baleCollectKey .. "#fruitType", "Name of the fruit type");
	schema:register(XMLValueType.STRING, baleCollectKey .. "#fillType", "Name of the fill type");
	schema:register(XMLValueType.FLOAT, baleCollectKey .. "#expectedLiters", "Expected liters");
	schema:register(XMLValueType.FLOAT, baleCollectKey .. "#depositedLiters", "Deposited liters");
	schema:register(XMLValueType.STRING, baleCollectKey .. "#sellingStationPlaceableUniqueId", "Unique id of the selling point");
	schema:register(XMLValueType.INT, baleCollectKey .. "#unloadingStationIndex", "Index of the unloading station");
end;

function BaleCollectMission.registerMetaXMLPaths(schema, key)
	BaleCollectMission:superClass().registerMetaXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#nextDay", "Earliest day a new mission can spawn");	
end;

function BaleCollectMission.new(isServer, isClient, customMt)	
	local title = g_i18n:getText("contract_field_baleCollect_title");
	local description = g_i18n:getText("contract_field_baleCollect_description");
	local self = AbstractFieldMission.new(isServer, isClient, title, description, customMt or BaleCollectMission_mt);
	self.bales = {};
	self.balesToLoadByUniqueId = nil
	self.pendingSellingStationId = nil;
	self.sellingStation = nil;
	self.fruitTypeIndex = nil;
	self.fillTypeIndex = nil;
	self.depositedLiters = 10; --push sellingStation fill event
	self.expectedLiters = 0; 
	self.lastSellChange = -1;
	return self;
end;

function BaleCollectMission:init(field, baleTypeIndex, numOfBales, sellingStation, fruitTypeIndex, fillTypeIndex)
	self.baleTypeIndex = baleTypeIndex;
	self.numOfBales = numOfBales;
	self.fruitTypeIndex = fruitTypeIndex;
	self.fillTypeIndex = fillTypeIndex;
	local success = BaleCollectMission:superClass().init(self, field);
	self:setSellingStation(sellingStation);
	self:setMinReward();
	return success;	
end;

function BaleCollectMission:onSavegameLoaded()
	if self.field == nil then
		Logging.error("Field is not set for bale collect mission");
		g_missionManager:markMissionForDeletion(self);
		return;
	elseif self.field:getFieldState().fruitTypeIndex == self.fruitTypeIndex then
		if not self:getIsFinished() then
			local sellingStation = g_currentMission.placeableSystem:getPlaceableByUniqueId(self.sellingStationPlaceableUniqueId);
			if sellingStation == nil then
				Logging.error("Selling station placeable with uniqueId \'%s\' not available for bale collect mission", self.sellingStationPlaceableUniqueId);
				g_missionManager:markMissionForDeletion(self);
				return;
			end;
			local unloadingStation = g_currentMission.storageSystem:getPlaceableUnloadingStation(sellingStation, self.unloadingStationIndex);
			if unloadingStation == nil then
				Logging.error("Unable to retrieve unloadingStation %d for placeable %s for bale collect mission", self.unloadingStationIndex, sellingStation.configFileName);
				g_missionManager:markMissionForDeletion(self);
				return;
			end;
			self:setSellingStation(unloadingStation);
			if self:getWasStarted() then
				unloadingStation.missions[self] = self;
			end;
		end;
		BaleCollectMission:superClass().onSavegameLoaded(self);
	else
		local fruitTypeName = g_fruitTypeManager:getFruitTypeNameByIndex(self.fruitTypeIndex); --g_fruitTypeManager:getFruitTypeByIndex(self.fruitTypeIndex); --patch 1.14 ?
		Logging.error("FruitType \'%s\' is not present on field \'%s\' for bale collect mission", fruitTypeName, self.field:getName());
		g_missionManager:markMissionForDeletion(self);
	end;
end;

function BaleCollectMission:delete()
	if self.sellingStation ~= nil then
		self.sellingStation.missions[self] = nil;
		self.sellingStation = nil;
	end;
	if self.sellingStationMapHotspot ~= nil then
		table.removeElement(self.mapHotspots, self.sellingStationMapHotspot);
		g_currentMission:removeMapHotspot(self.sellingStationMapHotspot);
		self.sellingStationMapHotspot:delete();
		self.sellingStationMapHotspot = nil;
	end;
	BaleCollectMission:superClass().delete(self);
end;

function BaleCollectMission:writeStream(streamId, connection)
	BaleCollectMission:superClass().writeStream(self, streamId, connection);
	NetworkUtil.writeNodeObject(streamId, self.sellingStation);
	streamWriteUIntN(streamId, self.baleTypeIndex, BaleManager.SEND_NUM_BITS);
	streamWriteUInt16(streamId, self.numOfBales);
	streamWriteUIntN(streamId, self.fruitTypeIndex or 0, FruitTypeManager.SEND_NUM_BITS);
	streamWriteUIntN(streamId, self.fillTypeIndex, FillTypeManager.SEND_NUM_BITS);
end;

function BaleCollectMission:readStream(streamId, connection)
	BaleCollectMission:superClass().readStream(self, streamId, connection);
	self.pendingSellingStationId = NetworkUtil.readNodeObjectId(streamId);
	self.baleTypeIndex = streamReadUIntN(streamId, BaleManager.SEND_NUM_BITS);
	self.numOfBales = streamReadUInt16(streamId);
	self.fruitTypeIndex = streamReadUIntN(streamId, FruitTypeManager.SEND_NUM_BITS);
	self.fillTypeIndex = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS);
end;

function BaleCollectMission:saveToXMLFile(xmlFile, key)	
	--testing--
	if BaleCollectMission.INSERT_DELETE_BALES then
		for i, bale in ipairs(self.bales) do
			xmlFile:setValue(string.format("%s.bale(%d)", key, i - 1) .. "#uniqueId", bale:getUniqueId());
		end;
	end;
	--testing--
	xmlFile:setValue(key .. "#baleTypeIndex", self.baleTypeIndex);
	xmlFile:setValue(key .. "#numOfBales", self.numOfBales);
	local baleCollectKey = string.format("%s.baleCollect", key);
	--xmlFile:setValue(baleCollectKey .. "#fruitType", g_fruitTypeManager.fruitTypes[self.fruitTypeIndex].name);
	xmlFile:setValue(baleCollectKey .. "#fruitType", g_fruitTypeManager:getFruitTypeNameByIndex(self.fruitTypeIndex)); --patch 1.14 ?
	--xmlFile:setValue(baleCollectKey .. "#fillType",	g_fillTypeManager.fillTypes[self.fillTypeIndex].name);
	xmlFile:setValue(baleCollectKey .. "#fillType",	g_fillTypeManager:getFillTypeNameByIndex(self.fillTypeIndex)); --patch 1.14 ?
	xmlFile:setValue(baleCollectKey .. "#expectedLiters", self.expectedLiters);
	xmlFile:setValue(baleCollectKey .. "#depositedLiters", self.depositedLiters);	
	if self.sellingStation ~= nil then
		local sellingStationPlaceable = self.sellingStation.owningPlaceable;
		if sellingStationPlaceable == nil then
			local sellingStationName = self.sellingStation.getName and self.sellingStation:getName() or "unknown";
			Logging.xmlWarning(xmlFile, "Unable to retrieve placeable of sellingStation \'%s\' for saving bale collect mission \'%s\' ", sellingStationName, baleCollectKey);
			return;
		end;
		local unloadingStationIndex = g_currentMission.storageSystem:getPlaceableUnloadingStationIndex(sellingStationPlaceable, self.sellingStation);
		if unloadingStationIndex == nil then
			local sellingStationName = self.sellingStation.getName and self.sellingStation:getName() or (sellingStationPlaceable.getName and sellingStationPlaceable:getName() or "unknown");
			Logging.xmlWarning(xmlFile, "Unable to retrieve unloading station index of sellingStation \'%s\' for saving bale collect mission \'%s\' ", sellingStationName, baleCollectKey);
			return;
		end;
		xmlFile:setValue(baleCollectKey .. "#sellingStationPlaceableUniqueId", sellingStationPlaceable:getUniqueId());
		xmlFile:setValue(baleCollectKey .. "#unloadingStationIndex", unloadingStationIndex);
	end;
	BaleCollectMission:superClass().saveToXMLFile(self, xmlFile, key);
end;

function BaleCollectMission:loadFromXMLFile(xmlFile, key)	
	--testing--
	if BaleCollectMission.INSERT_DELETE_BALES then
		for _, baleKey in xmlFile:iterator(key .. ".bale") do
			local baleUniqueId = xmlFile:getValue(baleKey .. "#uniqueId");
			if self.balesToLoadByUniqueId == nil then
				self.balesToLoadByUniqueId = {};
			end;
			table.insert(self.balesToLoadByUniqueId, baleUniqueId);
		end;
	end;
	--testing--
	self.baleTypeIndex = xmlFile:getValue(key .. "#baleTypeIndex");
	if self.baleTypeIndex == nil then
		return false;
	end;
	self.numOfBales = xmlFile:getValue(key .. "#numOfBales");
	if self.numOfBales == nil then
		return false;
	end;
	self.finishedBaleSpawning = true;
	local baleCollectKey = string.format("%s.baleCollect", key);
	local fruitTypeName = xmlFile:getValue(baleCollectKey.."#fruitType");	
	self.fruitTypeIndex = g_fruitTypeManager:getFruitTypeIndexByName(fruitTypeName);
	local fillTypeName = xmlFile:getValue(baleCollectKey.."#fillType");
	self.fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName);
	self.expectedLiters = xmlFile:getValue(baleCollectKey .. "#expectedLiters", self.expectedLiters);
	self.depositedLiters = xmlFile:getValue(baleCollectKey .. "#depositedLiters", self.depositedLiters);
	if not BaleCollectMission:superClass().loadFromXMLFile(self, xmlFile, key) then
		return false;
	end;
	if not self:getIsFinished() then
		local sellingStationPlaceableUniqueId = xmlFile:getValue(baleCollectKey .. "#sellingStationPlaceableUniqueId");
		if sellingStationPlaceableUniqueId == nil then
			Logging.xmlError(xmlFile, "No sellingStationPlaceable uniqueId given for bale collect mission at \'%s\'", baleCollectKey);
			return false;
		end;
		local unloadingStationIndex = xmlFile:getValue(baleCollectKey .. "#unloadingStationIndex");
		if unloadingStationIndex == nil then
			Logging.xmlError(xmlFile, "No unloadting station index given for bale collect mission at \'%s\'", baleCollectKey);
			return false;
		end;
		self.sellingStationPlaceableUniqueId = sellingStationPlaceableUniqueId;
		self.unloadingStationIndex = unloadingStationIndex;
	end;
	if BaleCollectMission:canMaxNumSeason() then self:setMaxNumSeason();end;
	return true;	
end;

-- Local values: fruitType, fruitTypeDesc, litersPerSqm, fieldSizeSqm, litersToDrop, x, z, fieldCourseSettings, baleFillTypeIndex, baleXMLFilename, baleCapacity, baleDesc, isRoundBale, nextBaleLiters, spawnBale, segmentLiters, lastSx, lastSz, lastEx, lastEz, lastLength, segmentFunc, finishFunc
function BaleCollectMission:prepareField()
	BaleCollectMission:superClass().prepareField(self)
	if self.isServer then
		local fruitType = self.fruitTypeIndex
		local fruitTypeDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitType)
		local litersPerSqm = fruitTypeDesc.windrowLiterPerSqm or fruitTypeDesc.literPerSqm
		local litersToDrop = MathUtil.haToSqm(self.field:getAreaHa()) * litersPerSqm
		local x, z = self.field:getCenterOfFieldWorldPosition()
		local fieldCourseSettings = FieldCourseSettings.new()
		fieldCourseSettings.implementWidth = 8
		fieldCourseSettings.numHeadlands = 2
		
		local baleFillTypeIndex = self.fillTypeIndex; --fruitTypeDesc.windrowFillType.index		
		local fillType = g_fillTypeManager:getFillTypeByIndex(baleFillTypeIndex)		
		
		local baleXMLFilename = g_baleManager:getBaleXMLFilenameByIndex(self.baleTypeIndex)
		local baleCapacity = g_baleManager:getBaleCapacityByBaleIndex(self.baleTypeIndex, baleFillTypeIndex)
		local baleDesc = g_baleManager:getBaleDescByIndex(self.baleTypeIndex)
		local isRoundBale = g_baleManager:getIsRoundBale(self.baleTypeIndex)
		local nextBaleLiters = baleCapacity
		local function spawnBale(p47_, p48_, p49_, p50_, p51_, p52_)
			-- upvalues: (copy) baleDesc, (copy) isRoundBale, (copy) self, (copy) baleXMLFilename, (copy) baleFillTypeIndex, (copy) baleCapacity
			local v53_ = p51_ * p52_
			local v54_, v55_ = MathUtil.vector2Normalize(p49_ - p47_, p50_ - p48_)
			local v56_ = p47_ + v54_ * v53_
			local v57_ = p48_ + v55_ * v53_
			local v58_ = MathUtil.getYRotationFromDirection(v54_, v55_) + 1.5707963267948966
			local v59_ = baleDesc.diameter
			if not isRoundBale then
				v58_ = v58_ + 1.5707963267948966
				v59_ = baleDesc.height
			end
			local v60_ = getTerrainHeightAtWorldPos(g_terrainNode, v56_, 0, v57_) + v59_ * 0.5
			local bale = Bale.new(self.isServer, self.isClient)
			if bale:loadFromConfigXML(baleXMLFilename, v56_, v60_, v57_, 0, v58_, 0) then
				bale:setFillType(baleFillTypeIndex)
				bale:setFillLevel(baleCapacity)
				if baleFillTypeIndex == FillType.SILAGE then
					bale:setWrapTextures();
					bale:setWrappingState(1, false);					
				end;
				--bale:setOwnerFarmId(self.field:getOwner(), true) --old
				bale:setOwnerFarmId(self.farmId, true)
				bale:setIsMissionBale(true)
				bale:register()								
				--testing--
				if BaleCollectMission.INSERT_DELETE_BALES then table.insert(self.bales, bale);end;
				--testing--
			end
		end
		local v_u_64_ = 0
		local v_u_65_ = nil
		local v_u_66_ = nil
		local v_u_67_ = nil
		local v_u_68_ = nil
		local v_u_69_ = nil
		local function segmentFunc(p70_, p71_, p72_, p73_, _, _, _, p74_)
			-- upvalues: (copy) litersToDrop, (ref) v_u_64_, (ref) v_u_65_, (ref) v_u_66_, (ref) v_u_67_, (ref) v_u_68_, (ref) v_u_69_, (ref) nextBaleLiters, (copy) baleCapacity, (copy) spawnBale
			local v75_ = MathUtil.vector2Length(p72_ - p70_, p73_ - p71_)
			local v76_ = v75_ * (litersToDrop / p74_)
			v_u_64_ = v76_
			v_u_65_ = p70_
			v_u_66_ = p71_
			v_u_67_ = p72_
			v_u_68_ = p73_
			v_u_69_ = v75_
			while v_u_64_ > 0 do
				if v_u_64_ <= nextBaleLiters then
					nextBaleLiters = nextBaleLiters - v_u_64_
					v_u_64_ = 0
				else
					local v77_ = nextBaleLiters - v_u_64_
					v_u_64_ = math.abs(v77_)
					local v78_ = 1 - v_u_64_ / v76_
					nextBaleLiters = baleCapacity
					spawnBale(p70_, p71_, p72_, p73_, v75_, v78_)
				end
			end
		end
		local function finishFunc()
			-- upvalues: (ref) v_u_64_, (copy) spawnBale, (ref) v_u_65_, (ref) v_u_66_, (ref) v_u_67_, (ref) v_u_68_, (ref) v_u_69_, (copy) self
			if v_u_64_ > 0 then
				spawnBale(v_u_65_, v_u_66_, v_u_67_, v_u_68_, v_u_69_, 1)
			end
			self.finishedBaleSpawning = true
		end
		FieldCourseIterator.new(x, z, fieldCourseSettings, segmentFunc, finishFunc)
	end
end

function BaleCollectMission:getFieldPreparingTask()
	local fruitType = self.fruitTypeIndex;
	local fruitTypeDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitType);
	local fieldPreparingTask = FieldUpdateTask.new();
	fieldPreparingTask:setArea(self.field:getDensityMapPolygon());
	fieldPreparingTask:setField(self.field);
	fieldPreparingTask:setFruit(fruitType, fruitTypeDesc.cutState);
	if fruitTypeDesc.harvestGroundType ~= nil then
		fieldPreparingTask:setGroundType(fruitTypeDesc.harvestGroundType);
	end;
	return fieldPreparingTask;
end;

function BaleCollectMission:getIsPrepared()
	if BaleCollectMission:superClass().getIsPrepared(self) then
		return self.finishedBaleSpawning;
	else
		return false;
	end;
end;

function BaleCollectMission:finishedPreparing()
	BaleCollectMission:superClass().finishedPreparing(self);		
	self.expectedLiters = self:getMaxCutLiters();
	if self.expectedLiters <= 0 then
		self:finish(MissionFinishState.FAILED);
	end;
end;

function BaleCollectMission:finishField()
	if self.isServer and #self.bales > 0 then
		for _, bale in ipairs(self.bales) do
			self:deleteBale(bale);
		end;
		self.bales = {};
	end
	BaleCollectMission:superClass().finishField(self);
end;

function BaleCollectMission:deleteBale(bale)
	if bale ~= nil and bale.nodeId ~= nil and entityExists(bale.nodeId) then
		bale:delete();
	else
		--self.penalty.actual = self.penalty.actual + self.penalty.missingBale;
	end;
end;

function BaleCollectMission:update(dt)
	BaleCollectMission:superClass().update(self, dt);
	if self.pendingSellingStationId ~= nil then
		self:tryToResolveSellingStation();
	end;
	--testing--
	if BaleCollectMission.INSERT_DELETE_BALES then
		if self.isServer and self.balesToLoadByUniqueId ~= nil then
			for _, baleUniqueId in ipairs(self.balesToLoadByUniqueId) do
				local bale = g_currentMission.itemSystem:getItemByUniqueId(baleUniqueId);
				if bale ~= nil then				
					table.insert(self.bales, bale);
				end;
			end;
			self.balesToLoadByUniqueId = nil;
			local numLoadedBales = #self.bales;
			if numLoadedBales ~= self.numOfBales then
				Logging.error("Could not load all bales from savegame");
				self.numOfBales = numLoadedBales;
			end;
		end;
	end;
	--testing--
	if self.lastSellChange > 0 then
		self.lastSellChange = self.lastSellChange - 1;
		if self.lastSellChange == 0 then
			local expected = self.expectedLiters * AbstractMission.SUCCESS_FACTOR;
			local depositedLiters = self.depositedLiters / expected * 100;
			local percentage = math.floor(depositedLiters);
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, string.format(g_i18n:getText("contract_field_harvest_progress_transporting_forField"), percentage, self.field.farmland:getId()));
		end;
	end;	
end;

function BaleCollectMission:tryToResolveSellingStation()
	if self.pendingSellingStationId ~= nil and self.sellingStation == nil then
		local sellingStation = NetworkUtil.getObject(self.pendingSellingStationId);
		if sellingStation ~= nil then
			self:setSellingStation(sellingStation);
		end;
	end;
end;

function BaleCollectMission:setSellingStation(sellingStation)
	if sellingStation ~= nil then
		self.pendingSellingStationId = nil;
		self.sellingStation = sellingStation;
		local placeable = sellingStation.owningPlaceable;
		if placeable ~= nil and placeable.getHotspot ~= nil then
			local mapHotspot = placeable:getHotspot();
			if mapHotspot ~= nil then
				self.sellingStationMapHotspot = HarvestMissionHotspot.new();
				self.sellingStationMapHotspot:setWorldPosition(mapHotspot:getWorldPosition());
				table.addElement(self.mapHotspots, self.sellingStationMapHotspot);
				if self.addSellingStationHotSpot then
					g_currentMission:addMapHotspot(self.sellingStationMapHotspot);
				end;
			end;
		end;
	end;
end;

function BaleCollectMission:addHotspots()
	BaleCollectMission:superClass().addHotspots(self);
	self.addSellingStationHotSpot = true;
	if self.sellingStationMapHotspot ~= nil then
		g_currentMission:addMapHotspot(self.sellingStationMapHotspot);
	end;
end;

function BaleCollectMission:removeHotspot()
	BaleCollectMission:superClass().removeHotspot(self);
	if self.sellingStationMapHotspot ~= nil then
		g_currentMission:removeMapHotspot(self.sellingStationMapHotspot);
	end;
	self.addSellingStationHotSpot = false;
end;

function BaleCollectMission:start(spawnVehicles)
	if self.pendingSellingStationId ~= nil then
		self:tryToResolveSellingStation();
	end;
	if self.sellingStation == nil then
		return false;
	end;
	self.sellingStation.missions[self] = self;
	return BaleCollectMission:superClass().start(self, spawnVehicles);
end;

function BaleCollectMission:getCompletion()	
	local completion;
	if self.expectedLiters > 0 then
		local sellCompletion = self.depositedLiters / self.expectedLiters / BaleCollectMission.data.ownTable.SUCCESS_FACTOR;
		completion = math.min(sellCompletion, 1);
	else
		completion = 1;
	end;	
	return completion;
end;

function BaleCollectMission:getRewardPerHa()
	return 1;
end;

function BaleCollectMission:getReward()
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME)
	local difficultyMultiplier = 1.3 - 0.1 * g_currentMission.missionInfo.economicDifficulty;
	return BaleCollectMission:superClass().getReward(self) + data.rewardPerBale * self.numOfBales * difficultyMultiplier;
end;

function BaleCollectMission:setMinReward() 
	--if BaleCollectMission.data.reward.min == nil or BaleCollectMission.data.reward.min <= 0 then return;end;	
	--if self.reward < BaleCollectMission.data.reward.min then self.reward = BaleCollectMission.data.reward.min;end;	
end;

function BaleCollectMission:getStealingCosts()
	if self.finishState ~= MissionFinishState.SUCCESS and self.isServer then
		local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
		local fillType = self.fillTypeIndex;
		local litersHarvested = self.expectedLiters;		
		local diff = litersHarvested - self.depositedLiters;
		if litersHarvested * data.failureCostFactor < diff then
			local _, pricePerLiter = g_additionalContractMapData:getSellingStationWithHighestPrice(fillType);
			local dismiss = Utils.getNoNil(self.data.reward.dismiss, 0); --penalty			
			return (diff * data.failureCostOfTotal * pricePerLiter) + dismiss;
		end;
	end;
	return 0;
end;

function BaleCollectMission:getDetails()
	local details = BaleCollectMission:superClass().getDetails(self);
	local stationName = nil;
	if self.pendingSellingStationId ~= nil then
		self:tryToResolveSellingStation();
	end;
	if self.sellingStation ~= nil then
		stationName = self.sellingStation:getName();
	end;
	if stationName ~= nil then
		local sellPoint = {
			["title"] = g_i18n:getText("contract_details_harvesting_sellingStation");
			["value"] = stationName;
		};
		table.insert(details, sellPoint);
	end;	
	local numOfBalesText = " (".. tostring(self.numOfBales).. ")";
	local isRoundBale;	
	if g_baleManager:getIsRoundBale(self.baleTypeIndex) then
		isRoundBale = g_i18n:getText("contract_details_bale_type_round").. numOfBalesText;
	else
		isRoundBale = g_i18n:getText("contract_details_bale_type_square").. numOfBalesText;
	end;
	local baleTyp = {
		["title"] = g_i18n:getText("contract_details_bale_type");
		["value"] = isRoundBale;
	};	
	table.insert(details, baleTyp);
	local fillType = {
		["title"] = g_i18n:getText("contract_details_harvesting_crop");
		["value"] = g_fillTypeManager:getFillTypeTitleByIndex(self.fillTypeIndex);
	};
	table.insert(details, fillType);
	if BaleCollectMission.data.reward.dismiss ~= nil and BaleCollectMission.data.reward.dismiss > 0 then
		local penalty = {
			["title"] = g_i18n:getText("contract_details_penalty");
			["value"] = g_i18n:formatMoney(BaleCollectMission.data.reward.dismiss, 0, true, true);
		};
		table.insert(details, penalty);
	end;
	return details;
end;

function BaleCollectMission:getFinishedDetails()
	local finishedDetails = BaleCollectMission:superClass().getFinishedDetails(self);
	if not g_currentMission.missionDynamicInfo.isMultiplayer and BaleCollectMission.data.reward.dismiss ~= nil and BaleCollectMission.data.reward.dismiss > 0 then
		if self.finishState ~= MissionFinishState.SUCCESS then
			local replaceValue = false;
			for _, v in pairs(finishedDetails) do
				if v ~= nil and v.title ~= nil and v.title == g_i18n:getText("contract_stealing") then
					v.title = v.title.. " + ".. g_i18n:getText("contract_details_penalty");					
				end;
			end;			
		end;
	end;
	return finishedDetails;
end;

function BaleCollectMission:getExtraProgressText()
	local stationName = "Unknown";
	if self.pendingSellingStationId ~= nil then
		self:tryToResolveSellingStation();
	end;
	if self.sellingStation ~= nil then
		stationName = self.sellingStation:getName();
	end;
	return string.format(g_i18n:getText("contract_field_harvest_nextUnloadDesc"), g_fillTypeManager:getFillTypeTitleByIndex(self.fillTypeIndex), stationName);
end;

function BaleCollectMission:fillSold(fillDelta)
	local depositedLiters = self.depositedLiters + fillDelta;
	local expectedLiters = self.expectedLiters;
	self.depositedLiters = math.min(depositedLiters, expectedLiters);
	local expected = self.expectedLiters * AbstractMission.SUCCESS_FACTOR;
	if self.sellingStation ~= nil and expected <= self.depositedLiters then
		self.sellingStation.missions[self] = nil;
	end;
	self.lastSellChange = 30;
end;

function BaleCollectMission:getVehicleVariant()
	local baleTyp = "SQUAREBALES";
	if g_baleManager:getIsRoundBale(self.baleTypeIndex) then baleTyp = "ROUNDBALES";end;	
	if self.numOfBales > BaleCollectMission.data.ownTable.MANY_BALES then --many bales more vehicles, 4x ? Mod Maps
		return "MANY_".. baleTyp;
	end;
	return baleTyp;
end;

function BaleCollectMission:getMaxCutLiters()
	local baleCapacity = g_baleManager:getBaleCapacityByBaleIndex(self.baleTypeIndex, self.fillTypeIndex);	
	return (baleCapacity * self.numOfBales) * 0.89;
end;

function BaleCollectMission:getMissionTypeName()
	return BaleCollectMission.NAME;
end;

function BaleCollectMission:validate(event)
	if not BaleCollectMission:superClass().validate(self, event) then
		return false;
	end;
	if not self:getIsFinished() then
		if not BaleCollectMission.isAvailableForField(self.field, self) then
			return false;
		end;
		if self.sellingStation ~= nil and not self.sellingStation.isRegistered then
			return false;
		end;
	end;
	return true;
end;

function BaleCollectMission:onDeleteSellingStation(sellingStation)
	if sellingStation == self.sellingStation and (self.isServer and (self.status == MissionStatus.RUNNING and not g_currentMission.isExitingGame)) then
		Logging.warning("Finish bale collect mission because selling station was removed");
		self:finish(MissionFinishState.FAILED);
	end;
end;

function BaleCollectMission.loadMapData(xmlFile, key, baseDirectory)
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
	data.rewardPerBale = xmlFile:getFloat(key .. "#rewardPerBale", BaleCollectMission.data.reward.PER_BALE);
	data.failureCostFactor = xmlFile:getFloat(key .. "#failureCostFactor", 0.1);
	data.failureCostOfTotal = xmlFile:getFloat(key .. "#failureCostOfTotal", 0.95);	
	return true;
end;

function BaleCollectMission.loadMetaDataFromXMLFile(xmlFile, key)
	if BaleCollectMission:canMaxNumSeason() then
		g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME).nextMissionDay = xmlFile:getValue(key .. "#nextDay");		
	end;
end;

function BaleCollectMission.saveMetaDataToXMLFile(xmlFile, key)
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);	
	if data.nextMissionDay ~= nil then
		xmlFile:setValue(key .. "#nextDay", data.nextMissionDay);
	end;	
end;

function BaleCollectMission.tryGenerateMission(addConsole, setMaxNumSeason)
	if BaleCollectMission.canRun(addConsole) then
		local field = g_fieldManager:getFieldForMission();
		if field == nil then
			if addConsole then printError("Error: No found mission field");end;
			return;
		end;
		if field.currentMission ~= nil then
			if addConsole then printError("Error: Field currentMission already");end;
			return;
		end;		
		local limitActive = BaleCollectMission.KEEP_LIMIT_VALUES;
		if limitActive and field:getAreaHa() > BaleCollectMission.data.ownTable.FIELD_SIZE_MAX then if addConsole then printError("Error: Area Hectar is large, limit is active");end;return;end;
		local isAvailableForField, fruitTypeIndex, fillTypeIndex = BaleCollectMission.isAvailableForField(field, nil);
		if not isAvailableForField or fruitTypeIndex == nil or fillTypeIndex == nil then			
			if addConsole then printError("Error: No found isAvailableForField or fruitTypeIndex or fillTypeIndex");end;
			return;
		end;
		local baleTypeIndex;
		if math.random() > 0.5 then
			baleTypeIndex = g_baleManager:getBaleIndex(fillTypeIndex, true, 1.2, 0, 0, 1.5, ""); --roundbale
		else
			baleTypeIndex = g_baleManager:getBaleIndex(fillTypeIndex, false, 1.2, 0.9, 1.8, 0, ""); --squarebale
		end
		if baleTypeIndex == nil then			
			if addConsole then printError("Error: No found baleTypeIndex");end;
			return;
		end;		
		local fruitType = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex);
		local litersPerSqm = fruitType.windrowLiterPerSqm or fruitType.literPerSqm;
		local litersToDrop = MathUtil.haToSqm(field:getAreaHa()) * litersPerSqm;
		local baleCapacity = g_baleManager:getBaleCapacityByBaleIndex(baleTypeIndex, fillTypeIndex);
		local numOfBales = math.floor(litersToDrop/baleCapacity);
		
		if numOfBales == 0 or (limitActive and (numOfBales > BaleCollectMission.data.ownTable.BALES_LIMIT or not g_currentMission.slotSystem:getCanAddLimitedObjects(SlotSystem.LIMITED_OBJECT_BALE, numOfBales))) then			
			if addConsole then printError("Error: No found numOfBales or limit is active by Mod/SlotSystem and is limit");end;
			return;
		end;		
		local sellingStation, highestPrice = g_additionalContractMapData:getSellingStationWithHighestPrice(fillTypeIndex); 
		if not g_additionalContractMapData:isAvailableForSellingStation(sellingStation) then
			if addConsole then printError("Error: No found selling station");end;
			return;
		end;
		local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
		local mission = BaleCollectMission.new(true, g_client ~= nil)
		if mission:init(field, baleTypeIndex, numOfBales, sellingStation, fruitTypeIndex, fillTypeIndex) then 
			mission:setDefaultEndDate();
			if (addConsole == nil or addConsole and setMaxNumSeason ~= nil) and limitActive and mission:canMaxNumSeason() then 
				mission:setMaxNumSeason();
				if mission:isMaxNumSeason() then
					data.nextMissionDay = g_currentMission.environment.currentMonotonicDay + mission:getNextMissionDay();
					mission:setMaxNumSeason(0);
				end;
			end;			
			return mission;
		end;
		if addConsole then printError("Error: Mission init not true");end;
		mission:delete();
	end;
	if addConsole then printError("Error: Mission type not active or disabled");end;
	return nil;
end;

function BaleCollectMission.isAvailableForField(field, mission)
	if mission == nil then
		local fieldState = field:getFieldState();
		if not fieldState.isValid then
			return false;
		end;		
		local fruitTypeIndex = fieldState.fruitTypeIndex;		
		if fruitTypeIndex == FruitType.UNKNOWN then 
			return false;
		end;
		local fruitType = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex);
		if fruitType == nil or fruitType:getIsCatchCrop() then			
			return false;
		end;		
		if not fruitType:getIsHarvestable(fieldState.growthState) then
			return false;
		end;
		local fillTypeIndex = BaleCollectMission:getFillTypeIndexVariant(fruitType, fruitTypeIndex);		
		if fillTypeIndex == nil then
			return false;
		end;
		return true, fruitTypeIndex, fillTypeIndex;
	end;
	return true;
end;

function BaleCollectMission:getFillTypeIndexVariant(fruitType, fruitTypeIndex)	
	if BaleCollectMission.ACCEPTS_FRUITTYPE[fruitType.name] ~= nil then
		if BaleCollectMission.ACCEPTS_FRUITTYPE[fruitType.name] then
			local fillTypes = {};
			for fillTypName, available in pairs(BaleCollectMission.ACCEPTS_FILLTYPE) do				
				if available then 
					local fillType = g_fillTypeManager:getFillTypeByName(fillTypName);					
					table.insert(fillTypes, fillType.index);
				end;
			end;
			if fillTypes ~= nil and #fillTypes > 0 then				
				return table.getRandomElement(fillTypes);
			end;
		end;
	elseif g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex).hasWindrow then
		local fillTypeIndex = g_fruitTypeManager:getWindrowFillTypeIndexByFruitTypeIndex(fruitTypeIndex);			
		if fillTypeIndex == FillType.STRAW then return fillTypeIndex;end;		
	end;
	return nil;
end;

function BaleCollectMission.canRun(addConsole)	
	if not BaleCollectMission:getOnOff() then return false;end;
	if addConsole then return true;end;
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
	if data.numInstances >= data.maxNumInstances or data.numInstances >= g_additionalContractTypes.settings[BaleCollectMission.NAME.. "_minMax"] then
		return false;
	else
		return not BaleCollectMission:canMaxNumSeason() or data.nextMissionDay == nil or g_currentMission.environment.currentMonotonicDay >= data.nextMissionDay;		
	end;
end;

function BaleCollectMission:setMaxNumInstance(maxNumInstance)
	self.uiData.settings[BaleCollectMission.NAME.. "_minMax"] = maxNumInstance;
	g_additionalContractTypes:replaceUISettings(BaleCollectMission.NAME.. "_minMax", maxNumInstance);
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
	if data ~= nil then data.maxNumInstances = maxNumInstance;end;	
end;

function BaleCollectMission:getMaxNumInstance()
	local data = g_missionManager:getMissionTypeDataByName(BaleCollectMission.NAME);
	if data == nil then 
		return g_additionalContractTypes.settings[BaleCollectMission.NAME.. "_minMax"];
	end;
	return data.maxNumInstances;
end;

function BaleCollectMission:getOnOff()	
	return g_additionalContractTypes.settings[BaleCollectMission.NAME.. "_minMax"] > 0;
end;

function BaleCollectMission:setOnOff(state)	
	
end;

function BaleCollectMission:canMaxNumSeason()
	return BaleCollectMission.MAX_NUM_SEASON[1] > 0;
end;

function BaleCollectMission:isMaxNumSeason(canMaxNumSeason)
	if canMaxNumSeason ~= nil and canMaxNumSeason then return BaleCollectMission:canMaxNumSeason() and BaleCollectMission.MAX_NUM_SEASON[2] >= BaleCollectMission.MAX_NUM_SEASON[1];end;
	return BaleCollectMission.MAX_NUM_SEASON[2] >= BaleCollectMission.MAX_NUM_SEASON[1];
end;

function BaleCollectMission:setMaxNumSeason(state)
	BaleCollectMission.MAX_NUM_SEASON[2] = state or BaleCollectMission.MAX_NUM_SEASON[2] + 1;	
end;

function BaleCollectMission:getNextMissionDay()
	return BaleCollectMission.MAX_NUM_SEASON[3];
end;

function BaleCollectMission:onStartMap(args)
	--if g_modIsLoaded["FS25_MaizePlus"] then BaleCollectMission.ACCEPTS_FILLTYPE.SILAGE = false;end;	
end;

function BaleCollectMission:isOnStartMap(args)
	if not args.isDetiServer and g_currentMission.hlUtils.modLoaded["FS25_MissionsDisplay"] ~= nil then
		if g_currentMission.hlUtils.globalFunction["FS25_MissionsDisplay"].getBuildVersion == nil or g_currentMission.hlUtils.globalFunction["FS25_MissionsDisplay"].getBuildVersion() <= 28 then
			local isAlReadyType = false;
			local isUnknownAlReadyType = false;
			local mdMod = getfenv(0)["FS25_MissionsDisplay"];
			if mdMod ~= nil and mdMod.Missions_Display ~= nil and mdMod.Missions_Display.values ~= nil and mdMod.Missions_Display.values.missionsTypes ~= nil then
				local missionData = g_missionManager:getMissionType(BaleCollectMission.NAME);
				for missionId, missionsType in pairs(mdMod.Missions_Display.values.missionsTypes) do
					if missionsType.name == "baleCollectMission" and missionsType.modName == "FS25_AdditionalContracts" then isAlReadyType = true;break;end;
					if missionData ~= nil and missionData.typeId ~= nil and missionData.typeId == missionsType.typId then isUnknownAlReadyType = true;break;end;					
				end;				
				if isUnknownAlReadyType or not isAlReadyType then								
					local isActive = function(args)return missionData.classObject:getOnOff(args);end;
					local changeMaxNumInstance = function(args)return missionData.classObject:getMaxNumInstance(args);end;
					local baleCollectMissionData = {name="baleCollectMission",jobTypName=BaleCollectMission:getJobTypName(),typId=missionData.typeId,isActive=isActive,changeMaxNumInstance=changeMaxNumInstance, overlay="unknownTrailer", modName="FS25_AdditionalContracts", view=true, total=0};
					if isUnknownAlReadyType then
						baleCollectMissionData.view = mdMod.Missions_Display.values.missionsTypes[missionData.typeId].view;				
					end;
					mdMod.Missions_Display.values.missionsTypes[missionData.typeId] = baleCollectMissionData;
				end;
			end;			
		end;
	end;
end;

function BaleCollectMission:loadInit()	
	if BaleCollectMission.UI_SETTINGS then		
		local isReady = false;
		for key, value in pairs(BaleCollectMission.uiData.settings) do			
			isReady = g_additionalContractTypes:setUISettings(key, value);
			if not isReady then break;end; 
		end;
		if isReady then
			for _, control in ipairs(BaleCollectMission.uiData.controls) do
				g_additionalContractTypes:setUIControls(control);			
			end;
		else
			for key, value in pairs(BaleCollectMission.uiData.settings) do
				g_additionalContractTypes:delUISettings(key);				 
			end;
		end;		
	end;
	local xmlFile = Utils.getFilename("missionVehicles/baleCollectMissionVehicles.xml", AdditionalContracts.modDir)
	g_missionManager:addPendingMissionVehiclesFile(xmlFile, AdditionalContracts.modDir)
end;

function BaleCollectMission:loadSettingsEvent(mission, connection, x, y, z, viewDistanceCoeff)
	if g_currentMission ~= nil and g_currentMission.missionDynamicInfo ~= nil and g_currentMission.missionDynamicInfo.isMultiplayer then
		g_client:getServerConnection():sendEvent(BaleCollectLoadSettingsEvent.new());
	end;
end;

function BaleCollectMission:changeSettingsEvent(settingsId, state)
	if g_currentMission ~= nil and g_currentMission.missionDynamicInfo ~= nil and g_currentMission.missionDynamicInfo.isMultiplayer then
		g_client:getServerConnection():sendEvent(BaleCollectChangeSettingsEvent.new(settingsId, state));
	else
		self:onChangeSettings(settingsId, state);
	end;
end;

function BaleCollectMission:onChangeSettings(settingsId, state)		
	if settingsId == "baleCollectMission_minMax" then		
		self:setMaxNumInstance(state);		
	end;	
end;

function BaleCollectMission:loadSettingsXML(xmlFile, prefix)
	if not xmlFile:hasProperty(prefix.. ".".. BaleCollectMission.NAME.. ".minMax") then return;end;	--first start
	local minMax = xmlFile:getInt(prefix.. ".".. BaleCollectMission.NAME.. ".minMax");
	local control = g_additionalContractTypes:getUIControls(BaleCollectMission.NAME.. "_minMax");
	if minMax > control.max then minMax = control.max;elseif minMax < control.min then minMax = control.min;end;	
	self:setMaxNumInstance(minMax);	
end;

function BaleCollectMission:saveSettingsXML(xmlFile, prefix)
	xmlFile:setInt(prefix.. ".".. BaleCollectMission.NAME.. ".minMax", g_additionalContractTypes.settings[BaleCollectMission.NAME.. "_minMax"]);	
end;

function BaleCollectMission:getJobTypName()
	return self.data.jobTypName;
end;

function BaleCollectMission:getMissionTypes()
	local missionData = g_missionManager:getMissionType(BaleCollectMission.NAME);
	local isActive = function(args)return missionData.classObject:getOnOff(args);end;
	local changeMaxNumInstance = function(args)return missionData.classObject:getMaxNumInstance(args);end;
	local baleCollectMissionData = {name="baleCollectMission",jobTypName=BaleCollectMission:getJobTypName(),typId=missionData.typeId,isActive=isActive,changeMaxNumInstance=changeMaxNumInstance, overlay="unknownTrailer", modName="FS25_AdditionalContracts", view=true};
	return baleCollectMissionData;
end;

function BaleCollectMission:getClassTypOverlay(args)
	local classOverlay = "tractor";
	local typOverlay = "unknownTrailer";
	if self.data.ownTable.classOverlay ~= nil then classOverlay = self.data.ownTable.classOverlay;end;
	if self.data.ownTable.typOverlay ~= nil then typOverlay = self.data.ownTable.typOverlay;end;	
	return classOverlay, typOverlay;
end;

function BaleCollectMission:getFillTypeIndexOverlay(args)
	local isRoundBale = g_baleManager:getIsRoundBale(self.baleTypeIndex);
	if isRoundBale then 
		if self.fillTypeIndex == FillType.STRAW then return FillType.ROUNDBALE;elseif self.fillTypeIndex == FillType.SILAGE then return FillType.ROUNDBALE_COTTON;elseif self.fillTypeIndex == FillType.GRASS then return FillType.ROUNDBALE_GRASS;end;
		return FillType.ROUNDBALE_DRYGRASS;	
	end;
	if self.fillTypeIndex == FillType.STRAW then return FillType.SQUAREBALE;elseif self.fillTypeIndex == FillType.SILAGE then return FillType.SQUAREBALE_COTTON;elseif self.fillTypeIndex == FillType.GRASS then return FillType.SQUAREBALE_GRASS;end;
	return FillType.SQUAREBALE_DRYGRASS;
end;

g_additionalContractTypes:registerTyp(BaleCollectMission, BaleCollectMission.NAME, true);
