MulcherMission = {};
MulcherMission.NAME = "mulcherMission";
MulcherMission.UI_SETTINGS = true;
MulcherMission.MAX_NUM_INSTANCE = 3;
MulcherMission.DEFAULT_NUM_INSTANCE = 2;
MulcherMission.BALANCE = {false,0};
MulcherMission.RANDOM = false;
MulcherMission.AI_QUERY_FIX_VERSION = 4;
MulcherMission.AI_QUERY_FIX_INSTALLED = false;

source(AdditionalContracts.modDir.. "missions/mulcherMission/MulcherChangeSettingsEvent.lua");
source(AdditionalContracts.modDir.. "missions/mulcherMission/MulcherLoadSettingsEvent.lua");

MulcherMission.uiData = {
	settings = {
		mulcherMission_minMax = MulcherMission.DEFAULT_NUM_INSTANCE;
	};
	controls = {
		{ typName="mulcherMission", name="mulcherMission_minMax", autoBind=true, min=0, max=MulcherMission.MAX_NUM_INSTANCE, step=1, unit="/ ".. tostring(MulcherMission.MAX_NUM_INSTANCE), nillable=false };
	};
};

MulcherMission.data = {	
	ownTable = {FIELD_SIZE_BIGLARGE=30, classOverlay="tractor", typOverlay="weed"}; --big large multiple vehicles, 4x Mod Maps... / success factor for pf dlc	
	reward = {dismiss=0, min=2500, PER_HA=2000};
	jobTypName = g_i18n:getText("contract_field_mulcher_title");	
};

MulcherMission.metadata = {
	interface = "FS25 ...", --new
	title = "Mulching Contracts",
	notes = "Dieser Mod generiert Mulcher Aufträge.",
	author = "(by HappyLooser)",
	build = 8,
	datum = "10.07.2025",
	update = "20.07.2026",
	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 function debugPrint(text, ...)
	if MulcherMission.metadata.debugPrint then
		Logging.info(text,...);
	end;
end;


--[[
    TFU multifruit compatibility fix

    The base-game Mulcher specialization registers every fruit type with
    isCultivationAllowed as an AI requirement. On multifruit maps several
    fruits can share the same density-map type index, while FieldCropsQuery
    accepts at most four requirements for one index. During an active
    mulcher contract we only need the crop that is actually present on the
    contract field, so the AI requirement list is narrowed to that crop.
]]
local function getMulcherAIStateRange(fruitDesc)
    if fruitDesc == nil or type(fruitDesc.cutState) ~= "number" or type(fruitDesc.mulchedState) ~= "number" then
        return nil, nil;
    end;

    if fruitDesc.mulchedState > fruitDesc.cutState then
        return 2, fruitDesc.mulchedState - 1;
    end;

    return 2, 15;
end;

local function getMissionFruitTypeIndex(mission)
    if mission == nil then
        return nil;
    end;

    local fruitTypeIndex = mission.fruitTypeIndex;
    if (fruitTypeIndex == nil or fruitTypeIndex == FruitType.UNKNOWN) and mission.field ~= nil and mission.field.getFieldState ~= nil then
        local fieldState = mission.field:getFieldState();
        if fieldState ~= nil then
            fruitTypeIndex = fieldState.fruitTypeIndex;
        end;
    end;

    if fruitTypeIndex == FruitType.UNKNOWN then
        return nil;
    end;

    return fruitTypeIndex;
end;

local function getVehicleLogName(vehicle)
    if vehicle == nil then
        return "unknown";
    end;

    if vehicle.getName ~= nil then
        local name = vehicle:getName();
        if name ~= nil and name ~= "" then
            return name;
        end;
    end;

    return vehicle.configFileName or "unknown";
end;

local function copyFruitRequirements(requirements)
    local copy = {};
    if requirements == nil then
        return copy;
    end;

    for _, requirement in ipairs(requirements) do
        table.insert(copy, {
            fruitType = requirement.fruitType,
            minGrowthState = requirement.minGrowthState,
            maxGrowthState = requirement.maxGrowthState,
            customMapId = requirement.customMapId,
            customMapStartChannel = requirement.customMapStartChannel,
            customMapNumChannels = requirement.customMapNumChannels
        });
    end;

    return copy;
end;

local function getRootVehicle(vehicle)
    if vehicle == nil then
        return nil;
    end;

    -- Use the public vehicle API first. Attached implements do not reliably
    -- expose a raw rootVehicle field during every load/attach transition.
    if vehicle.getRootVehicle ~= nil then
        local success, rootVehicle = pcall(vehicle.getRootVehicle, vehicle);
        if success and rootVehicle ~= nil then
            return rootVehicle;
        end;
    end;

    if vehicle.rootVehicle ~= nil then
        return vehicle.rootVehicle;
    end;

    -- Defensive fallback for non-standard attachable implementations.
    local currentVehicle = vehicle;
    local visited = {};
    while currentVehicle ~= nil and not visited[currentVehicle] do
        visited[currentVehicle] = true;
        local attachableSpec = currentVehicle.spec_attachable;
        local parentVehicle = attachableSpec ~= nil and attachableSpec.attacherVehicle or nil;
        if parentVehicle == nil then
            break;
        end;
        currentVehicle = parentVehicle;
    end;

    return currentVehicle or vehicle;
end;

local function isVehicleAIActive(vehicle)
    local rootVehicle = getRootVehicle(vehicle);
    if rootVehicle == nil then
        return true; -- conservative: never restore while ownership is unclear
    end;

    if rootVehicle.getIsAIActive ~= nil then
        local success, isActive = pcall(rootVehicle.getIsAIActive, rootVehicle);
        if not success then
            return true;
        end;
        if isActive == true then
            return true;
        end;
    end;

    -- getIsAIActive can briefly be false while a field job/course is still
    -- being initialized or torn down. These internal states keep restoration
    -- away from a live AI course.
    if rootVehicle.spec_aiJobVehicle ~= nil and rootVehicle.spec_aiJobVehicle.job ~= nil then
        return true;
    end;
    if rootVehicle.spec_aiDrivable ~= nil and rootVehicle.spec_aiDrivable.isRunning == true then
        return true;
    end;
    if rootVehicle.spec_aiFieldWorker ~= nil and rootVehicle.spec_aiFieldWorker.isActive == true then
        return true;
    end;

    return false;
end;

local MulcherMission_mt = Class(MulcherMission, AbstractFieldMission);
InitObjectClass(MulcherMission, "MulcherMission");

function MulcherMission.registerXMLPaths(schema, key)
	MulcherMission:superClass().registerXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#rewardPerHa", "Reward per ha");
end;

function MulcherMission.registerSavegameXMLPaths(schema, key)
	MulcherMission:superClass().registerSavegameXMLPaths(schema, key);
	schema:register(XMLValueType.INT, key .. "#targetMulcherLevel", "Target mulcher level");
	schema:register(XMLValueType.INT, key .. "#fruitTypeIndex", "Target fruit type index for the mulcher AI query");
end;

function MulcherMission:saveToXMLFile(xmlFile, key)
	MulcherMission:superClass().saveToXMLFile(self, xmlFile, key);
	xmlFile:setValue(key .. "#targetMulcherLevel", self.targetMulcherLevel);
	if self.fruitTypeIndex ~= nil and self.fruitTypeIndex ~= FruitType.UNKNOWN then
		xmlFile:setValue(key .. "#fruitTypeIndex", self.fruitTypeIndex);
	end;
end;

function MulcherMission:loadFromXMLFile(xmlFile, key)
	if not MulcherMission:superClass().loadFromXMLFile(self, xmlFile, key) then
		return false;
	end;
	self.targetMulcherLevel = xmlFile:getValue(key .. "#targetMulcherLevel");
	self.fruitTypeIndex = xmlFile:getValue(key .. "#fruitTypeIndex");
	if self.fruitTypeIndex == nil and self.field ~= nil and self.field.getFieldState ~= nil then
		local fieldState = self.field:getFieldState();
		if fieldState ~= nil then
			self.fruitTypeIndex = fieldState.fruitTypeIndex;
		end;
	end;
	return true;	
end;

function MulcherMission.new(isServer, isClient, customMt)
	local title = g_i18n:getText("contract_field_mulcher_title"); 
	local description = g_i18n:getText("contract_field_mulcher_description");
	local self = AbstractFieldMission.new(isServer, isClient, title, description, customMt or MulcherMission_mt);
	self.workAreaTypes = {
		[WorkAreaType.MULCHER] = true;
	};
	self.targetMulcherLevel = 0;
	self.fruitTypeIndex = nil;
	self.additionalContractsPatchedMulchers = {};
	return self;
end;

function MulcherMission:init(field, targetMulcherLevel)
	self.targetMulcherLevel = targetMulcherLevel;
	self.fruitTypeIndex = field:getFieldState().fruitTypeIndex;
	self:setMinReward();
	return MulcherMission:superClass().init(self, field);
end;

function MulcherMission:patchMulcherAIRequirements(vehicle)
    if not self.isServer or vehicle == nil or vehicle.spec_mulcher == nil or vehicle.spec_aiImplement == nil then
        return false;
    end;
    if vehicle.setAIFruitRequirements == nil then
        return false;
    end;

    local fruitTypeIndex = getMissionFruitTypeIndex(self);
    if fruitTypeIndex == nil then
        return false;
    end;

    local fruitDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex);
    local minGrowthState, maxGrowthState = getMulcherAIStateRange(fruitDesc);
    if fruitDesc == nil
        or fruitDesc.isCultivationAllowed ~= true
        or fruitDesc.terrainDataPlaneId == nil
        or type(fruitDesc.startStateChannel) ~= "number"
        or type(fruitDesc.numStateChannels) ~= "number"
        or fruitDesc.numStateChannels <= 0
        or minGrowthState == nil
        or maxGrowthState == nil
        or maxGrowthState < minGrowthState then
        return false;
    end;

    local missionMarker = string.format("%s:%s:%s", tostring(self.uniqueId or 0), tostring(self.activeMissionId or 0), tostring(fruitTypeIndex));
    if vehicle.additionalContractsMulcherAIFixMarker == missionMarker then
        return true;
    end;

    -- Preserve the pre-contract requirement list once. Player-owned implements
    -- are restored after the helper has stopped or are handed to the deferred
    -- restore queue if the mission is deleted immediately.
    if vehicle.additionalContractsMulcherOriginalAIFruitRequirements == nil then
        local requirements = vehicle.getAIFruitRequirements ~= nil and vehicle:getAIFruitRequirements() or vehicle.spec_aiImplement.requiredFruitTypes;
        vehicle.additionalContractsMulcherOriginalAIFruitRequirements = copyFruitRequirements(requirements);
    end;

    -- Public AIImplement API: replace the map-wide list with only the crop on
    -- this contract field. One requirement cannot exceed the engine's per-type
    -- limit and is sufficient for the active mulching job.
    local success, errorMessage = pcall(vehicle.setAIFruitRequirements, vehicle, fruitTypeIndex, minGrowthState, maxGrowthState);
    if not success then
        Logging.error("Additional Contracts | Mulcher AI compatibility fix failed for vehicle='%s': %s", getVehicleLogName(vehicle), tostring(errorMessage));
        return false;
    end;

    vehicle.additionalContractsMulcherAIFixMarker = missionMarker;
    vehicle.additionalContractsMulcherAIFixOwner = self;
    self.additionalContractsPatchedMulchers = self.additionalContractsPatchedMulchers or {};
    self.additionalContractsPatchedMulchers[vehicle] = true;
    self.fruitTypeIndex = fruitTypeIndex;

    local fieldId = "unknown";
    if self.field ~= nil and self.field.getId ~= nil then
        fieldId = self.field:getId();
    end;

    Logging.info("Additional Contracts | Mulcher AI compatibility fix: mission=%s field=%s vehicle='%s' fruit='%s' states=%d-%d",
        tostring(self.uniqueId or self.activeMissionId or "unknown"),
        tostring(fieldId),
        getVehicleLogName(vehicle),
        tostring(fruitDesc.name or fruitTypeIndex),
        minGrowthState,
        maxGrowthState);

    return true;
end;

function MulcherMission:patchMulcherAIRequirementsForVehicleTree(vehicle)
    local rootVehicle = getRootVehicle(vehicle);
    if rootVehicle == nil then
        return false;
    end;

    local patched = self:patchMulcherAIRequirements(rootVehicle);
    if rootVehicle.getChildVehicles ~= nil then
        local childVehicles = rootVehicle:getChildVehicles();
        if childVehicles ~= nil then
            for _, childVehicle in ipairs(childVehicles) do
                if childVehicle ~= rootVehicle and self:patchMulcherAIRequirements(childVehicle) then
                    patched = true;
                end;
            end;
        end;
    end;

    -- Defensive fallback for non-standard attachment trees.
    if vehicle ~= rootVehicle and self:patchMulcherAIRequirements(vehicle) then
        patched = true;
    end;

    return patched;
end;

local function restoreVehicleAIRequirements(vehicle, originalRequirements)
    if vehicle == nil or vehicle.isDeleted == true then
        return true;
    end;
    if vehicle.spec_aiImplement == nil or originalRequirements == nil then
        return true;
    end;
    if isVehicleAIActive(vehicle) then
        return false;
    end;

    local spec = vehicle.spec_aiImplement;
    spec.requiredFruitTypes = copyFruitRequirements(originalRequirements);
    spec.fieldCropyQuery = nil;
    spec.fieldCropyQueryValid = false;

    vehicle.additionalContractsMulcherAIFixMarker = nil;
    vehicle.additionalContractsMulcherAIFixOwner = nil;
    vehicle.additionalContractsMulcherOriginalAIFruitRequirements = nil;
    return true;
end;

function MulcherMission.queuePendingRestore(vehicle, originalRequirements)
    if vehicle == nil or originalRequirements == nil then
        return;
    end;
    MulcherMission.PENDING_RESTORES = MulcherMission.PENDING_RESTORES or setmetatable({}, {__mode="k"});
    MulcherMission.PENDING_RESTORES[vehicle] = copyFruitRequirements(originalRequirements);
end;

function MulcherMission.processPendingRestores()
    local pendingRestores = MulcherMission.PENDING_RESTORES;
    if pendingRestores == nil then
        return;
    end;

    for vehicle, originalRequirements in pairs(pendingRestores) do
        local success, restored = pcall(restoreVehicleAIRequirements, vehicle, originalRequirements);
        if not success then
            Logging.error("Additional Contracts | Deferred Mulcher AI restore failed for vehicle='%s': %s", getVehicleLogName(vehicle), tostring(restored));
            pendingRestores[vehicle] = nil;
        elseif restored then
            pendingRestores[vehicle] = nil;
            Logging.info("Additional Contracts | Deferred Mulcher AI requirements restored for vehicle='%s'", getVehicleLogName(vehicle));
        end;
    end;
end;

function MulcherMission:restoreMulcherAIRequirements(vehicle)
    if vehicle == nil then
        return true;
    end;
    if vehicle.additionalContractsMulcherAIFixOwner ~= self then
        return true;
    end;

    local originalRequirements = vehicle.additionalContractsMulcherOriginalAIFruitRequirements;
    local restored = restoreVehicleAIRequirements(vehicle, originalRequirements);
    if restored then
        Logging.info("Additional Contracts | Restored Mulcher AI requirements for vehicle='%s'", getVehicleLogName(vehicle));
    end;
    return restored;
end;

function MulcherMission:restoreAllMulcherAIRequirements()
    if self.additionalContractsPatchedMulchers == nil then
        return false;
    end;

    local hasPending = false;
    for vehicle, _ in pairs(self.additionalContractsPatchedMulchers) do
        local success, restored = pcall(self.restoreMulcherAIRequirements, self, vehicle);
        if not success then
            if not self.additionalContractsRestoreErrorLogged then
                self.additionalContractsRestoreErrorLogged = true;
                Logging.error("Additional Contracts | Mulcher AI restore failed: %s", tostring(restored));
            end;
            hasPending = true;
        elseif restored then
            self.additionalContractsPatchedMulchers[vehicle] = nil;
        else
            hasPending = true;
        end;
    end;

    return hasPending;
end;

function MulcherMission:onSpawnedVehicle(vehicles, vehicleLoadState, loadingInfo)
    MulcherMission:superClass().onSpawnedVehicle(self, vehicles, vehicleLoadState, loadingInfo);
    if self.isServer and vehicleLoadState == VehicleLoadingState.OK and vehicles ~= nil then
        for _, vehicle in ipairs(vehicles) do
            self:patchMulcherAIRequirementsForVehicleTree(vehicle);
        end;
    end;
end;

function MulcherMission:update(dt)
    MulcherMission:superClass().update(self, dt);
    if not self.isServer then
        return;
    end;

    -- Covers mission vehicles restored from an existing save after their unique
    -- IDs have been resolved by AbstractMission:update.
    if self.getIsRunning ~= nil and self:getIsRunning() and self.vehicles ~= nil then
        for _, vehicle in ipairs(self.vehicles) do
            self:patchMulcherAIRequirementsForVehicleTree(vehicle);
        end;
    end;

    if self.additionalContractsRestorePending then
        self.additionalContractsRestorePending = self:restoreAllMulcherAIRequirements();
    end;
end;

function MulcherMission:finish(finishState)
    MulcherMission:superClass().finish(self, finishState);
    self.additionalContractsRestorePending = self:restoreAllMulcherAIRequirements();
end;

function MulcherMission:delete()
    local hasPending = self:restoreAllMulcherAIRequirements();
    if hasPending and self.additionalContractsPatchedMulchers ~= nil then
        for vehicle, _ in pairs(self.additionalContractsPatchedMulchers) do
            if vehicle ~= nil and vehicle.additionalContractsMulcherAIFixOwner == self then
                MulcherMission.queuePendingRestore(vehicle, vehicle.additionalContractsMulcherOriginalAIFruitRequirements);
                vehicle.additionalContractsMulcherAIFixOwner = nil;
                vehicle.additionalContractsMulcherAIFixMarker = nil;
                vehicle.additionalContractsMulcherOriginalAIFruitRequirements = nil;
            end;
        end;
    end;
    self.additionalContractsPatchedMulchers = {};
    MulcherMission:superClass().delete(self);
end;

-- MissionManager calls this for actual work processing on the contract field.
-- It also covers player-owned mulchers once work begins.
function MulcherMission:getIsWorkAllowed(farmId, x, z, workAreaType, vehicle)
    local isAllowed = MulcherMission:superClass().getIsWorkAllowed(self, farmId, x, z, workAreaType, vehicle);
    if isAllowed and vehicle ~= nil and (workAreaType == nil or workAreaType == WorkAreaType.MULCHER) then
        self:patchMulcherAIRequirementsForVehicleTree(vehicle);
    end;
    return isAllowed;
end;

-- Final guard immediately before AIVehicleUtil asks the implement to create its
-- FieldCropsQuery. This is needed for player-owned mulchers because AI course
-- validation happens before the first physical work-area callback.
function MulcherMission.onBeforeGetAIAreaOfVehicle(vehicle, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ)
    if g_missionManager == nil or vehicle == nil or startWorldX == nil or startWorldZ == nil then
        return;
    end;

    local samplePositions = {
        {(startWorldX + (widthWorldX or startWorldX)) * 0.5, (startWorldZ + (widthWorldZ or startWorldZ)) * 0.5},
        {(startWorldX + (heightWorldX or startWorldX)) * 0.5, (startWorldZ + (heightWorldZ or startWorldZ)) * 0.5},
        {(startWorldX + (widthWorldX or startWorldX) + (heightWorldX or startWorldX)) / 3, (startWorldZ + (widthWorldZ or startWorldZ) + (heightWorldZ or startWorldZ)) / 3},
        {startWorldX, startWorldZ}
    };

    for _, position in ipairs(samplePositions) do
        local mission = g_missionManager:getMissionAtWorldPosition(position[1], position[2]);
        if mission ~= nil
            and mission.isServer
            and mission.getMissionTypeName ~= nil
            and mission:getMissionTypeName() == MulcherMission.NAME
            and mission.getIsRunning ~= nil
            and mission:getIsRunning() then
            local success, errorMessage = pcall(mission.patchMulcherAIRequirementsForVehicleTree, mission, vehicle);
            if not success and not MulcherMission.AI_QUERY_FIX_ERROR_LOGGED then
                MulcherMission.AI_QUERY_FIX_ERROR_LOGGED = true;
                Logging.error("Additional Contracts | Mulcher AI compatibility guard failed: %s", tostring(errorMessage));
            end;
            return;
        end;
    end;
end;

-- AIVehicleUtil.getAIAreaOfVehicle is a return-valued function. A prepended
-- hook is not safe here because GIANTS' prepend/append helpers do not preserve
-- the wrapped function's return values. The overwritten hook below explicitly
-- forwards and returns both area values required by AIDriveStrategyFieldCourse.
function MulcherMission.getAIAreaOfVehicle(vehicle, superFunc, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ)
    MulcherMission.onBeforeGetAIAreaOfVehicle(vehicle, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ);

    local area, totalArea = superFunc(vehicle, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ);
    if type(area) ~= "number" or type(totalArea) ~= "number" then
        if not MulcherMission.AI_AREA_RETURN_GUARD_LOGGED then
            MulcherMission.AI_AREA_RETURN_GUARD_LOGGED = true;
            Logging.warning("Additional Contracts | Mulcher AI area guard received invalid return values area=%s totalArea=%s; using 0,0", tostring(area), tostring(totalArea));
        end;
        return 0, 0;
    end;

    return area, totalArea;
end;

function MulcherMission.installAIQueryFix()
    if MulcherMission.AI_QUERY_FIX_INSTALLED then
        return;
    end;
    if AIVehicleUtil == nil or AIVehicleUtil.getAIAreaOfVehicle == nil or Utils == nil or Utils.overwrittenFunction == nil then
        Logging.warning("Additional Contracts | Mulcher AI compatibility fix could not be installed");
        return;
    end;

    AIVehicleUtil.getAIAreaOfVehicle = Utils.overwrittenFunction(AIVehicleUtil.getAIAreaOfVehicle, MulcherMission.getAIAreaOfVehicle);
    MulcherMission.AI_QUERY_FIX_INSTALLED = true;
    Logging.info("Additional Contracts | Installed Mulcher AI multifruit compatibility fix v%s (return-safe)", tostring(MulcherMission.AI_QUERY_FIX_VERSION));
end;

function MulcherMission:createModifier()	
	local mapId, firstChannel, numChannels = g_currentMission.fieldGroundSystem:getDensityMapData(FieldDensityMap.STUBBLE_SHRED_LEVEL);
	local levelState = FieldGroundType.getValueByType(FieldGroundType.STUBBLE_TILLAGE);	
	self.completionModifier = DensityMapModifier.new(mapId, firstChannel, numChannels, g_terrainNode);
	self.completionFilter = DensityMapFilter.new(self.completionModifier);	
	self.completionFilter:setValueCompareParams(DensityValueCompareType.EQUAL, levelState);	
end;

function MulcherMission:getFieldFinishTask()
	local fieldState = self.field:getFieldState();
	fieldState.stubbleShredLevel = self.targetMulcherLevel;	
	fieldState.fruitTypeIndex = FruitType.UNKNOWN;	
	fieldState.groundType = FieldGroundType.STUBBLE_TILLAGE;	
	debugPrint("f%s, growth %d, spray %s, sprayType %s, plow %s, lime %s, weed %s, roller %s, stubbleShredLevel %s, fruitTypeIndex %s, /getFieldFinishTask",
			self.field.farmland.name, fieldState.growthState, fieldState.sprayLevel, fieldState.sprayType, fieldState.plowLevel, fieldState.limeLevel, fieldState.weedState, fieldState.rollerLevel, fieldState.stubbleShredLevel, fieldState.fruitTypeIndex)
		
	return MulcherMission:superClass().getFieldFinishTask(self);
end;

function MulcherMission:getCompletion()
	return self:getFieldCompletion() / g_additionalContractTypes:getSuccessFactor();
	--if g_modIsLoaded.FS25_precisionFarming then
	--	return self:getFieldCompletion() / MulcherMission.data.ownTable.SUCCESS_FACTOR;
	--end;
	--return MulcherMission:superClass().getCompletion(self);	
end;

function MulcherMission:getVehicleVariant()	
	if self.field:getAreaHa() >= MulcherMission.data.ownTable.FIELD_SIZE_BIGLARGE then return "BIGLARGE";end; --big large multiple vehicles, 4x Mod Maps
	return "GRAIN";	
end;

function MulcherMission:getRewardPerHa()
	return g_missionManager:getMissionTypeDataByName(MulcherMission.NAME).rewardPerHa;
end;

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

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

function MulcherMission:validate(event)
	if MulcherMission:superClass().validate(self, event) then
		return (self:getIsFinished() or MulcherMission.isAvailableForField(self.field, self)) and true or false;
	else
		return false;
	end;
end;

function MulcherMission.loadMapData(xmlFile, key, baseDirectory)	
	local data = g_missionManager:getMissionTypeDataByName(MulcherMission.NAME);
	data.rewardPerHa = xmlFile:getFloat(key .. "#rewardPerHa", MulcherMission.data.reward.PER_HA);
	return true;
end;

function MulcherMission.tryGenerateMission(addConsole)
	if MulcherMission.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;
		if not MulcherMission.isAvailableForField(field, nil) then
			if addConsole then printError("Error: No found isAvailableForField");end;
			return;
		end;
		local mulcherLevel = field:getFieldState().stubbleShredLevel + 1
		local mission = MulcherMission.new(true, g_client ~= nil);
		if mission:init(field, mulcherLevel) then
			mission:setDefaultEndDate();
			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 MulcherMission.isAvailableForField(field, mission)
	if mission == nil then
		if field == nil or field.getFieldState == nil then return false;end;
		local fieldState = field:getFieldState();
		if not fieldState.isValid then
			return false;
		end;
		if field.grassMissionOnly then
			return false;
		end;				
		debugPrint("f%s, growth %d, spray %s, sprayType %s, plow %s, lime %s, weed %s, roller %s, stubbleShredLevel %s, fruitTypeIndex %s, /isAvailableForField",
			field.farmland.name, fieldState.growthState, fieldState.sprayLevel, fieldState.sprayType, fieldState.plowLevel, fieldState.limeLevel, fieldState.weedState, fieldState.rollerLevel, fieldState.stubbleShredLevel, fieldState.fruitTypeIndex)
		if fieldState.stubbleShredLevel == nil or fieldState.stubbleShredLevel > 0 then 
			return false;
		end;
		local fruitIndex = fieldState.fruitTypeIndex;
		if fruitIndex == FruitType.UNKNOWN then
			return false;
		end;
		local growthState = fieldState.growthState;
		local fruitDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitIndex);		
		if fruitDesc == nil or (fruitDesc:getIsCatchCrop() and growthState <= 1) then return false;end;
		if fruitDesc.isCultivationAllowed ~= true or fruitDesc.terrainDataPlaneId == nil then return false;end;
		if type(fruitDesc.startStateChannel) ~= "number" or type(fruitDesc.numStateChannels) ~= "number" or fruitDesc.numStateChannels <= 0 then return false;end;
		local minGrowthState, maxGrowthState = getMulcherAIStateRange(fruitDesc);
		if minGrowthState == nil or maxGrowthState == nil or maxGrowthState < minGrowthState then return false;end;
		if not fruitDesc:getIsCut(growthState) or fruitDesc:getIsWithered(growthState) then return false;end;		
	else
		if mission:getIsReadyToStart() and not mission:getIsInProgress() then --check and cancel
			if field:getFieldState().growthState <=1 then return false;end;	
		end;
	end;
	return true;
end;

function MulcherMission.canRun(addConsole)
	if not MulcherMission:getOnOff() then return false;end;
	if addConsole then return true;end;
	local data = g_missionManager:getMissionTypeDataByName(MulcherMission.NAME);	
	if data.numInstances >= data.maxNumInstances or data.numInstances >= g_additionalContractTypes.settings[MulcherMission.NAME.. "_minMax"] then
		return false;
	else
		return true; --not g_currentMission.growthSystem:getIsGrowingInProgress();
	end;
end;

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

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

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

function MulcherMission:setOnOff(state)
	
end;

function MulcherMission:canBalance()
	return MulcherMission.BALANCE[1];
end;

function MulcherMission:getBalance()
	return MulcherMission.BALANCE[2];
end;

function MulcherMission:setBalance(state)
	MulcherMission.BALANCE[2] = state or MulcherMission.BALANCE[2] + 1;
end;

function MulcherMission:canRandom()
	return MulcherMission.RANDOM;
end;

function MulcherMission:setRandom(state)
	MulcherMission.RANDOM = state or not MulcherMission.RANDOM;
end;

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

function MulcherMission:onStartMap(args)
	if not args.isDetiServer and FS25_MissionsExpanded ~= nil and FS25_MissionsExpanded.MissionsExpanded ~= nil and FS25_MissionsExpanded.MissionsExpanded.missionsMultiplier["mulcherMission"] ~= nil then --replace MissionsExpanded value
		FS25_MissionsExpanded.MissionsExpanded.missionsMultiplier["mulcherMission"][1] = g_additionalContractTypes.settings[MulcherMission.NAME.. "_minMax"];
		local data = g_missionManager:getMissionTypeDataByName(MulcherMission.NAME);
		if data ~= nil and data.maxNumInstances ~= g_additionalContractTypes.settings[MulcherMission.NAME.. "_minMax"] then data.maxNumInstances = g_additionalContractTypes.settings[MulcherMission.NAME.. "_minMax"];end; 
	end;
end;

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

function MulcherMission: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(MulcherLoadSettingsEvent.new());
	end;
end;

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

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

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

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

function MulcherMission:getClassTypOverlay(args)
	local classOverlay = "tractor";
	local typOverlay = "weed";
	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 MulcherMission:getFillTypeIndexOverlay(args)
	return nil, "fillType_field";
end;

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