SupplyDeliveryBulkMission = {};
SupplyDeliveryBulkMission.REGISTER_XML_PATHS = true;
SupplyDeliveryBulkMission.NAME = "supplyDeliveryBulkMission";
SupplyDeliveryBulkMission.MISSIONS_XML = {};
SupplyDeliveryBulkMission.MAX_NUM_INSTANCE = 1;
SupplyDeliveryBulkMission.NUM_INSTANCE = 0;
SupplyDeliveryBulkMission.MAX_NUM_DAY = {2,0,1}; --max,is,nextDay + ingame day

SupplyDeliveryBulkMission.data = {	
	ownTable = {VEHICLE_USE_COST=80, SUCCESS_FACTOR=0.95, classOverlay="supply", typOverlay="trailer"};
	reward = {dismiss=0, min=4000};
	jobTypName = g_i18n:getText("ai_jobTitleDeliver"); --g_i18n:getText("contract_typ_supply_delivery_bulk");	
};

SupplyDeliveryBulkMission.metadata = {
	interface = "FS25 ...", --new
	title = "Waren Anlieferung Contracts",
	notes = "Dieser Mod generiert Waren Anlieferungs Aufträge.",
	author = "(by HappyLooser)",		
	build = 1,
	datum = "15.09.2025",
	update = "19.09.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"
};

local SupplyDeliveryBulkMission_mt = Class(SupplyDeliveryBulkMission, AbstractMission);
InitObjectClass(SupplyDeliveryBulkMission, "SupplyDeliveryBulkMission");

function SupplyDeliveryBulkMission.new(isServer, isClient)	
	local title = g_i18n:getText("contract_universal_title");
	local description = SupplyDeliveryBulkMission.data.jobTypName; --g_i18n:getText("typDescription_supply_delivery_bulk");	
	local self = AbstractMission.new(isServer, isClient, title, description, SupplyDeliveryBulkMission_mt);
	
	--local uiScale = g_currentMission.hlUtils.getUiScale();
	--self.progressTitle = title.. " | ".. self.data.jobTypName;
	self.progressTitle = "*".. self.data.jobTypName;
	self.jobTypName = "*".. self.data.jobTypName;	
	
	self.pendingSellingStationId = nil;
	self.sellingStation = nil;	
	self.fillTypeIndex = nil;
	self.depositedLiters = 10; --push sellingStation fill event
	self.expectedLiters = 0;
	self.lastSellChange = -1;	
	self.mapHotspots = {};
	self.addSellingStationHotSpot = false;
	self.farmlandId = FarmlandManager.NO_OWNER_FARM_ID;	--GUI does not do a nil check so just use 0
	--self.field = {fieldId=0,getName=function()return ""end}; -- or ... GUI does not do a nil check so just use 0
	SupplyDeliveryBulkMission.NUM_INSTANCE = SupplyDeliveryBulkMission.NUM_INSTANCE + 1;
	return self;
end;

function SupplyDeliveryBulkMission.registerSavegameXMLPaths(schema, key)
	SupplyDeliveryBulkMission:superClass().registerSavegameXMLPaths(schema, key);
	local supplyDeliveryBulkKey = string.format("%s.supplyDeliveryBulk", key);	
	schema:register(XMLValueType.STRING, supplyDeliveryBulkKey .. "#fillType", "Name of the fill type");
	schema:register(XMLValueType.FLOAT, supplyDeliveryBulkKey .. "#expectedLiters", "Expected liters");
	schema:register(XMLValueType.FLOAT, supplyDeliveryBulkKey .. "#depositedLiters", "Deposited liters");
	schema:register(XMLValueType.STRING, supplyDeliveryBulkKey .. "#sellingStationPlaceableUniqueId", "Unique id of the selling point");
	schema:register(XMLValueType.INT, supplyDeliveryBulkKey .. "#unloadingStationIndex", "Index of the unloading station");
end;

function SupplyDeliveryBulkMission:getOwnData()
	return self.data;
end;

function SupplyDeliveryBulkMission:getMissionTypeName()
    return UniversalMission.NAME;
end;

function SupplyDeliveryBulkMission:finish(success)
	SupplyDeliveryBulkMission:superClass().finish(self, success);	
	if g_currentMission:getIsServer() then				
		if success then
			g_farmManager:getFarmById(self.farmId).stats:updateMissionDone();
		end;		
	end;	
	if g_currentMission:getFarmId() == self.farmId then
		if success == MissionFinishState.SUCCESS then
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, string.format(g_i18n:getText("contract_universal_finish"), self.jobTypName));
		elseif success == MissionFinishState.FAILED or success == MissionFinishState.CANCELED or success == MissionFinishState.TIMED_OUT then
			g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, string.format(g_i18n:getText("contract_universal_failed"), self.jobTypName));
		end;
	end;
end;

function SupplyDeliveryBulkMission: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;
	if SupplyDeliveryBulkMission.NUM_INSTANCE > 0 then SupplyDeliveryBulkMission.NUM_INSTANCE = SupplyDeliveryBulkMission.NUM_INSTANCE - 1;end;
	SupplyDeliveryBulkMission:superClass().delete(self);
end;

function SupplyDeliveryBulkMission:init()	
	local supplyDeliveryMission = table.getRandomElement(SupplyDeliveryBulkMission.MISSIONS_XML);
	if supplyDeliveryMission == nil then return false;end;
	local fillTypeConfig = table.getRandomElement(supplyDeliveryMission.fillTypes);
	if fillTypeConfig == nil then return false;end;
	local fillTypeName = g_additionalContractUtils:getStringToVector(fillTypeConfig.fillType):upper();
	local fillType = nil;
	if fillTypeName == "RANDOM" then 
		
	else 
		fillType = g_fillTypeManager:getFillTypeByName(fillTypeName);
	end;
	if fillType == nil then return false;end;
	self.fillTypeIndex = fillType.index;
	if self.fillTypeIndex == nil then return false;end;	
	self.expectedLiters = math.floor(g_additionalContractUtils:getIntegerToVector(fillTypeConfig.fillVolume)/100, 0.5)*100;
	local sellingStation, highestPrice = g_additionalContractMapData:getSellingStationWithHighestPrice(self.fillTypeIndex); 
	if not g_additionalContractMapData:isAvailableForSellingStation(sellingStation) then
		return false;
	end;
	self.missionConfig = supplyDeliveryMission;
	self.fillTypeConfig = fillTypeConfig;	
	self:setTypDescription();
	self:setSellingStation(sellingStation);	
	self.reward = self:calculateReward(highestPrice);
	self:setMinReward();	
	return SupplyDeliveryBulkMission:superClass().init(self);
end;

function SupplyDeliveryBulkMission:setTypDescription()
	local typDescription = self:getTypDesciption();
	--if object.typDescription ~= nil then		
	--	local typDescription = string.format(g_i18n:getText(object.typDescription), g_i18n:getText(object.typTitle));		
		self.description = typDescription;
	--end;
end;

function SupplyDeliveryBulkMission:onSavegameLoaded()	
	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 supply delivery product 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 supply delivery product mission", self.unloadingStationIndex, sellingStation.configFileName);
			g_missionManager:markMissionForDeletion(self);
			return;
		end;
		self:setSellingStation(unloadingStation);
		if self:getWasStarted() then
			unloadingStation.missions[self] = self;
		end;
	end;
	self:setTypDescription();
	SupplyDeliveryBulkMission:superClass().onSavegameLoaded(self);	
end;

function SupplyDeliveryBulkMission:writeStream(streamId, connection)
	SupplyDeliveryBulkMission:superClass().writeStream(self, streamId, connection);
	NetworkUtil.writeNodeObject(streamId, self.sellingStation);	
	streamWriteUIntN(streamId, self.fillTypeIndex, FillTypeManager.SEND_NUM_BITS);
	streamWriteFloat32(streamId, self.expectedLiters);
end;

function SupplyDeliveryBulkMission:readStream(streamId, connection)
	SupplyDeliveryBulkMission:superClass().readStream(self, streamId, connection);
	self.pendingSellingStationId = NetworkUtil.readNodeObjectId(streamId);	
	self.fillTypeIndex = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS);
	self.expectedLiters = streamReadFloat32(streamId);
	self:setTypDescription();
end;

function SupplyDeliveryBulkMission:saveToXMLFile(xmlFile, key)	
	xmlFile:setValue(key .. "#typName", SupplyDeliveryBulkMission.NAME);
	local supplyDeliveryBulkKey = string.format("%s.supplyDeliveryBulk", key);	
	--xmlFile:setValue(supplyDeliveryBulkKey .. "#fillType",	g_fillTypeManager.fillTypes[self.fillTypeIndex].name);
	xmlFile:setValue(supplyDeliveryBulkKey .. "#fillType",	g_fillTypeManager:getFillTypeNameByIndex(self.fillTypeIndex)); --patch 1.14 ?
	xmlFile:setValue(supplyDeliveryBulkKey .. "#expectedLiters", self.expectedLiters);
	xmlFile:setValue(supplyDeliveryBulkKey .. "#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, supplyDeliveryBulkKey);
			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, supplyDeliveryBulkKey);
			return;
		end;
		xmlFile:setValue(supplyDeliveryBulkKey .. "#sellingStationPlaceableUniqueId", sellingStationPlaceable:getUniqueId());
		xmlFile:setValue(supplyDeliveryBulkKey .. "#unloadingStationIndex", unloadingStationIndex);
	end;
	SupplyDeliveryBulkMission:superClass().saveToXMLFile(self, xmlFile, key);
end;

function SupplyDeliveryBulkMission:loadFromXMLFile(xmlFile, key)		
	local supplyDeliveryBulkKey = string.format("%s.supplyDeliveryBulk", key);
	local fillTypeName = xmlFile:getValue(supplyDeliveryBulkKey.."#fillType");
	self.fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName);
	self.expectedLiters = xmlFile:getValue(supplyDeliveryBulkKey .. "#expectedLiters", self.expectedLiters);
	self.depositedLiters = xmlFile:getValue(supplyDeliveryBulkKey .. "#depositedLiters", self.depositedLiters);
	if not self:getIsFinished() then
		local sellingStationPlaceableUniqueId = xmlFile:getValue(supplyDeliveryBulkKey .. "#sellingStationPlaceableUniqueId");
		if sellingStationPlaceableUniqueId == nil then
			Logging.xmlError(xmlFile, "No sellingStationPlaceable uniqueId given for bale collect mission at \'%s\'", supplyDeliveryBulkKey);
			return false;
		end;
		local unloadingStationIndex = xmlFile:getValue(supplyDeliveryBulkKey .. "#unloadingStationIndex");
		if unloadingStationIndex == nil then
			Logging.xmlError(xmlFile, "No unloadting station index given for bale collect mission at \'%s\'", supplyDeliveryBulkKey);
			return false;
		end;
		self.sellingStationPlaceableUniqueId = sellingStationPlaceableUniqueId;
		self.unloadingStationIndex = unloadingStationIndex;
	end;
	if not SupplyDeliveryBulkMission:superClass().loadFromXMLFile(self, xmlFile, key) then
		return false;
	end;
	return true;	
end;

function SupplyDeliveryBulkMission:update(dt)
	SupplyDeliveryBulkMission:superClass().update(self, dt);
	if self.pendingSellingStationId ~= nil then
		self:tryToResolveSellingStation();
	end;
	if self.status == MissionStatus.RUNNING and (g_localPlayer ~= nil and (g_localPlayer.farmId == self.farmId and not self.addSellingStationHotSpot)) then
		self:addHotspot();
	end;
end;

function SupplyDeliveryBulkMission: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 SupplyDeliveryBulkMission: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 SupplyDeliveryBulkMission:validate()
	local isValidate = SupplyDeliveryBulkMission:superClass().validate(self)
	if isValidate then
		if self.sellingStation == nil or self.sellingStation.isRegistered then
			return isValidate;
		else
			return false;
		end;
	else
		return false;
	end;
end;

function SupplyDeliveryBulkMission:getFarmlandId()
    return self.farmlandId or FarmlandManager.NO_OWNER_FARM_ID; --GUI does not do a nil check so just use 0 / Error: Running LUA method 'update'. dataS/scripts/gui/InGameMenuContractsFrame.lua:572: attempt to compare nil < nil
end;

function SupplyDeliveryBulkMission:getMapHotspots()
	return self.mapHotspots;
end;

function SupplyDeliveryBulkMission:addHotspot()	
	if self.sellingStationMapHotspot ~= nil then
		g_currentMission:addMapHotspot(self.sellingStationMapHotspot);
	end;
	self.addSellingStationHotSpot = true;
end;

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

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

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

function SupplyDeliveryBulkMission: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 SupplyDeliveryBulkMission:getReward()	
	return self.reward;
end;

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

function SupplyDeliveryBulkMission:calculateReward(highestPrice)	
	return (self.expectedLiters * highestPrice) * self.fillTypeConfig.rewardScale;
end;

function SupplyDeliveryBulkMission.canRun()	
	if not g_additionalContractMapData:hasMapTrigger("unloadingStation") or #SupplyDeliveryBulkMission.MISSIONS_XML == 0 then 
		g_additionalContractTypes:removeBalanceTyp(SupplyDeliveryBulkMission.NAME);
		g_additionalContractTypes:removeRandomTyp(SupplyDeliveryBulkMission.NAME);
		g_additionalContractTypes:removeNextDayTyp(SupplyDeliveryBulkMission.NAME);
		return false;
	end;
	return not g_additionalContractTypes:isMaxNextDayTyp(SupplyDeliveryBulkMission.NAME) and SupplyDeliveryBulkMission.NUM_INSTANCE < SupplyDeliveryBulkMission.MAX_NUM_INSTANCE;
end;

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

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

function SupplyDeliveryBulkMission:getVehicleSize()	
	if self.expectedLiters > 3500 then return "medium_supplyDeliveryBulkMission";else return "small_supplyDeliveryBulkMission";end;	
end;

function SupplyDeliveryBulkMission:getVehicleVariant()	
	return "DEFAULT_SUPPLYDELIVERYBULK";	
end;

function SupplyDeliveryBulkMission:getVehicleCosts()
	if self.vehiclesToLoad == nil then
		return 0;
	end;
	local numVehicles = #self.vehiclesToLoad;
	local difficultyMultiplier = 0.7 + 0.3 * g_currentMission.missionInfo.economicDifficulty;
	return numVehicles * SupplyDeliveryBulkMission.data.ownTable.VEHICLE_USE_COST * difficultyMultiplier;
end;

function SupplyDeliveryBulkMission:getDetails()
	local details = SupplyDeliveryBulkMission: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 expectedLiters = {
		["title"] = g_i18n:getText("animals_foodMixQuantity");
		["value"] = tostring(g_i18n:formatNumber(self.expectedLiters));
	};	
	table.insert(details, expectedLiters);
	local fillType = {
		["title"] = g_i18n:getText("contract_details_harvesting_crop");
		["value"] = g_fillTypeManager:getFillTypeTitleByIndex(self.fillTypeIndex);
	};
	table.insert(details, fillType);	
	return details;
end;

function SupplyDeliveryBulkMission:getNPC()	
	local npc = nil; --g_npcManager:getNPCByIndex(self.missionConfig.npcIndex);
	if npc == nil then npc = g_npcManager:getNPCByIndex(1);end;
	return npc;
end;

function SupplyDeliveryBulkMission:loadInit()
	g_additionalContractTypes:insertRandomTyp(SupplyDeliveryBulkMission.NAME);
	g_additionalContractTypes:insertBalanceTyp(SupplyDeliveryBulkMission.NAME);
	g_additionalContractTypes:insertNextDayTyp(SupplyDeliveryBulkMission.NAME, SupplyDeliveryBulkMission.MAX_NUM_DAY);
	local xmlFilename = Utils.getFilename("missions/universalMission/supplyDeliveryBulkMission/missions.xml", AdditionalContracts.modDir);
	if xmlFilename ~= nil and xmlFilename ~= "" then SupplyDeliveryBulkMission.MISSIONS_XML = g_additionalContractUtils:loadMissionsXml(xmlFilename);end;
	xmlFilename = Utils.getFilename("missionVehicles/supplyDeliveryBulkMissionVehicles.xml", AdditionalContracts.modDir)
	if xmlFilename ~= nil and xmlFilename ~= "" then g_missionManager:addPendingMissionVehiclesFile(xmlFilename, AdditionalContracts.modDir);end;
end;

function SupplyDeliveryBulkMission:getTypDesciption()
	if g_additionalContractUtils:isLanguageAvailable() then
		return g_i18n:getText("typDescription_supply_delivery_bulk"); 
	else
		return g_i18n:getText("ai_jobTitleDeliver").. ": ".. g_i18n:getText("contract_details_harvesting_crop").. " --> ".. g_i18n:getText("contract_details_harvesting_sellingStation");
	end;
end;

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

function SupplyDeliveryBulkMission:getClassTypOverlay(args)	
	return SupplyDeliveryBulkMission.data.classOverlay, SupplyDeliveryBulkMission.data.typOverlay;
end;

---balance system---

---balance system---

g_additionalContractTypes:registerTyp(SupplyDeliveryBulkMission, SupplyDeliveryBulkMission.NAME);