--- Combine unloader job.
---@class CpAIJobCombineUnloader : CpAIJob
CpAIJobCombineUnloader = {
	name = "COMBINE_UNLOADER_CP",
	jobName = "CP_job_combineUnload",
	minStartDistanceToField = 20,
	minFieldUnloadDistanceToField = 20,
	maxHeapLength = 150
}

local AIJobCombineUnloaderCp_mt = Class(CpAIJobCombineUnloader, CpAIJob)

function CpAIJobCombineUnloader.new(isServer, customMt)
	local self = CpAIJob.new(isServer, customMt or AIJobCombineUnloaderCp_mt)

	self.lastPositionX, self.lastPositionZ = math.huge, math.huge

    self.selectedFieldPlot = FieldPlot(true)
    self.selectedFieldPlot:setVisible(false)
	self.selectedFieldPlot:setBrightColor(true)

	self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap)
    self.heapPlot:setVisible(false)
	self.heapNode = CpUtil.createNode("siloNode", 0, 0, 0, nil)

	--- Giants unload
	self.dischargeNodeInfos = {}
	
	return self
end

function CpAIJobCombineUnloader:delete()
	CpAICombineUnloader:superClass().delete(self)
	CpUtil.destroyNode(self.heapNode)
end

function CpAIJobCombineUnloader:setupTasks(isServer)
	CpAIJob.setupTasks(self, isServer)
	self.combineUnloaderTask = CpAITaskCombineUnloader(isServer, self)
	self:addTask(self.combineUnloaderTask)

	--- Giants unload
	self.waitForFillingTask = self.combineUnloaderTask
	self.driveToUnloadingTask = AITaskDriveTo.new(isServer, self)
	self.dischargeTask = AITaskDischarge.new(isServer, self)
	self:addTask(self.driveToUnloadingTask)
	self:addTask(self.dischargeTask)
	
end

function CpAIJobCombineUnloader:setupJobParameters()
	CpAIJob.setupJobParameters(self)
    self:setupCpJobParameters(CpCombineUnloaderJobParameters(self))
	self.cpJobParameters.fieldUnloadPosition:setSnappingAngle(math.pi/8) -- AI menu snapping angle of 22.5 degree.

	--- Giants unload
	self.unloadingStationParameter = self.cpJobParameters.unloadingStation
	self.waitForFillingTask = self.combineUnloaderTask
end

function CpAIJobCombineUnloader:getIsAvailableForVehicle(vehicle)
	return vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader()
end

function CpAIJobCombineUnloader:getCanStartJob()
	return self:getFieldPolygon()
end

---@param vehicle table
---@param mission Mission
---@param farmId number
---@param isDirectStart boolean disables the drive to by giants
---@param isStartPositionInvalid boolean resets the drive to target position by giants and the field position to the vehicle position.
function CpAIJobCombineUnloader:applyCurrentState(vehicle, mission, farmId, isDirectStart, isStartPositionInvalid)
	CpAIJob.applyCurrentState(self, vehicle, mission, farmId, isDirectStart)
	
	self.cpJobParameters:validateSettings()

	self:copyFrom(vehicle:getCpCombineUnloaderJob())

	local x, z = self.cpJobParameters.fieldPosition:getPosition()
	-- no field position from the previous job, use the vehicle's current position
	if x == nil or z == nil then
		x, _, z = getWorldTranslation(vehicle.rootNode)
		self.cpJobParameters.fieldPosition:setPosition(x, z)
	end
	local x, z = self.cpJobParameters.fieldUnloadPosition:getPosition()
	local angle = self.cpJobParameters.fieldUnloadPosition:getAngle()
	-- no field position from the previous job, use the vehicle's current position
	if x == nil or z == nil or angle == nil then
		x, _, z = getWorldTranslation(vehicle.rootNode)
		local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1)
		local angle = MathUtil.getYRotationFromDirection(dirX, dirZ)
		self.cpJobParameters.fieldUnloadPosition:setPosition(x, z)
		self.cpJobParameters.fieldUnloadPosition:setAngle(angle)
	end
end

--- Gets the giants unload station.
function CpAIJobCombineUnloader:getUnloadingStations()
	local unloadingStations = {}
	for _, unloadingStation in pairs(g_currentMission.storageSystem:getUnloadingStations()) do
		if g_currentMission.accessHandler:canPlayerAccess(unloadingStation) and unloadingStation:isa(UnloadingStation) then
			local fillTypes = unloadingStation:getAISupportedFillTypes()

			if next(fillTypes) ~= nil then
				table.insert(unloadingStations, unloadingStation)
			end
		end
	end
	return unloadingStations
end

function CpAIJobCombineUnloader:setValues()
	CpAIJob.setValues(self)
	local vehicle = self.vehicleParameter:getVehicle()
	self.combineUnloaderTask:setVehicle(vehicle)
	self:setupGiantsUnloaderData(vehicle)
end

function CpAIJobCombineUnloader:validateFieldPosition(isValid, errorMessage)
	local tx, tz = self.cpJobParameters.fieldPosition:getPosition()
	if tx == nil or tz == nil then 
		return false, g_i18n:getText("CP_error_not_on_field")
	end
	local _
	local fieldPolygon, _ = CpFieldUtil.getFieldPolygonAtWorldPosition(tx, tz)
	self:setFieldPolygon(fieldPolygon)
	if fieldPolygon then 
		self.selectedFieldPlot:setWaypoints(fieldPolygon)
        self.selectedFieldPlot:setVisible(true)
	else
		return false, g_i18n:getText("CP_error_not_on_field")
	end
	return isValid, errorMessage
end

--- Called when parameters change, scan field
function CpAIJobCombineUnloader:validate(farmId)
	self.selectedFieldPlot:setVisible(false)
	self.heapPlot:setVisible(false)
	local isValid, errorMessage = CpAIJob.validate(self, farmId)
	if not isValid then
		return isValid, errorMessage
	end
	local vehicle = self.vehicleParameter:getVehicle()
	if vehicle then 
		vehicle:applyCpCombineUnloaderJobParameters(self)
	end
	------------------------------------
	--- Validate selected field
	-------------------------------------
	local isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage)

	if not isValid then
		return isValid, errorMessage
	end
	local fieldPolygon = self:getFieldPolygon()
	------------------------------------
	--- Validate start distance to field
	-------------------------------------
	local useGiantsUnload = false
	if not self.cpJobParameters.useGiantsUnload:getIsDisabled() then 
		useGiantsUnload =  self.cpJobParameters.useGiantsUnload:getValue() 
	end
	if isValid and self.isDirectStart then 
		--- Checks the distance for starting with the hud, as a safety check.
		--- Firstly check, if the vehicle is near the field.
		local x, _, z = getWorldTranslation(vehicle.rootNode)
		isValid = CpMathUtil.isPointInPolygon(fieldPolygon, x, z) or 
				  CpMathUtil.getClosestDistanceToPolygonEdge(fieldPolygon, x, z) < self.minStartDistanceToField
		if not isValid and useGiantsUnload then 
			--- Alternatively check, if the start marker is close to the field and giants unload is active.
			local x, z = self.cpJobParameters.startPosition:getPosition()
			isValid = CpMathUtil.isPointInPolygon(fieldPolygon, x, z) or 
				  CpMathUtil.getClosestDistanceToPolygonEdge(fieldPolygon, x, z) < self.minStartDistanceToField
			if not isValid then
				return false, g_i18n:getText("CP_error_start_position_to_far_away_from_field")
			end 
		end
		if not isValid then
			return false, g_i18n:getText("CP_error_unloader_to_far_away_from_field")
		end
	end
	------------------------------------
	--- Validate giants unload if needed
	-------------------------------------
	if useGiantsUnload then 
		isValid, errorMessage = self.cpJobParameters.unloadingStation:validateUnloadingStation()
		
		if not isValid then
			return false, errorMessage
		end

		if not AIJobDeliver.getIsAvailableForVehicle(self, vehicle) then 
			return false, g_i18n:getText("CP_error_giants_unloader_not_available")
		end
		isValid, message = self.cpJobParameters.startPosition:validate()
	end
	if not isValid then
		return isValid, errorMessage
	end
	------------------------------------
	--- Validate field unload if needed
	-------------------------------------
	local useFieldUnload = false
	if not self.cpJobParameters.useFieldUnload:getIsDisabled() then 
		useFieldUnload =  self.cpJobParameters.useFieldUnload:getValue() 
	end
	if useFieldUnload then 
		
		local x, z = self.cpJobParameters.fieldUnloadPosition:getPosition()
		isValid = CpMathUtil.isPointInPolygon(fieldPolygon, x, z) or 
				  CpMathUtil.getClosestDistanceToPolygonEdge(fieldPolygon, x, z) < self.minFieldUnloadDistanceToField
		if not isValid then
			return false, g_i18n:getText("CP_error_fieldUnloadPosition_too_far_away_from_field")
		end
		--- Draws the silo
		local angle = self.cpJobParameters.fieldUnloadPosition:getAngle()
		setTranslation(self.heapNode, x, 0, z)
		setRotation(self.heapNode, 0, angle, 0)
		local found, heapSilo = BunkerSiloManagerUtil.createHeapBunkerSilo(vehicle, self.heapNode, 0, self.maxHeapLength, -10)
		if found then	
			self.heapPlot:setArea(heapSilo:getArea())
			self.heapPlot:setVisible(true)
		end
	end

	return isValid, errorMessage
end

function CpAIJobCombineUnloader:draw(map, isOverviewMap)
	CpAIJob.draw(self, map, isOverviewMap)
	if not isOverviewMap then
		self.selectedFieldPlot:draw(map)
		self.heapPlot:draw(map)
	end
end

------------------------------------
--- Giants unload 
------------------------------------

--- Sets static data for the giants unload. 
function CpAIJobCombineUnloader:setupGiantsUnloaderData(vehicle)
	self.dischargeNodeInfos = {}
	if vehicle == nil then 
		return
	end
	if vehicle.getAIDischargeNodes ~= nil then
		for _, dischargeNode in ipairs(vehicle:getAIDischargeNodes()) do
			local _, _, z = vehicle:getAIDischargeNodeZAlignedOffset(dischargeNode, vehicle)

			table.insert(self.dischargeNodeInfos, {
				dirty = true,
				vehicle = vehicle,
				dischargeNode = dischargeNode,
				offsetZ = z
			})
		end
	end

	local childVehicles = vehicle:getChildVehicles()

	for _, childVehicle in ipairs(childVehicles) do
		if childVehicle.getAIDischargeNodes ~= nil then
			for _, dischargeNode in ipairs(childVehicle:getAIDischargeNodes()) do
				local _, _, z = childVehicle:getAIDischargeNodeZAlignedOffset(dischargeNode, vehicle)

				table.insert(self.dischargeNodeInfos, {
					dirty = true,
					vehicle = childVehicle,
					dischargeNode = dischargeNode,
					offsetZ = z
				})
			end
		end
	end
	self.driveToUnloadingTask:setVehicle(vehicle)
	self.dischargeTask:setVehicle(vehicle)
	if #self.dischargeNodeInfos > 0 then 
		table.sort(self.dischargeNodeInfos, function (a, b)
			return b.offsetZ < a.offsetZ
		end)
		local maxOffset = self.dischargeNodeInfos[#self.dischargeNodeInfos].offsetZ

		self.driveToUnloadingTask:setTargetOffset(-maxOffset)
	end
	local unloadingStation = self.unloadingStationParameter:getUnloadingStation()

	if unloadingStation == nil then
		return
	end
	self.supportedFillTypes = unloadingStation:getAISupportedFillTypes()
end

function CpAIJobCombineUnloader:getNextTaskIndex(isSkipTask)
	--- Giants unload, sets the correct dischargeNode and vehicle and unload target information.
	local index = AIJobDeliver.getNextTaskIndex(self, isSkipTask)
	return index
end

function CpAIJobCombineUnloader:canContinueWork()
	local canContinueWork, errorMessage = CpAIJobCombineUnloader:superClass().canContinueWork(self)
	if not canContinueWork then 
		return canContinueWork, errorMessage
	end
	--- Giants unload, checks if the unloading station is still available and not full.
	if self.cpJobParameters.useGiantsUnload:getValue() then 
		
		local canContinue, errorMessage = AIJobDeliver.canContinueWork(self)
		if not canContinue then 
			return canContinue, errorMessage
		end
		if self.currentTaskIndex == self.driveToUnloadingTask.taskIndex then
			local hasSupportedFillTypeLoaded = false
			for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do
				local fillType = dischargeNodeInfo.vehicle:getFillUnitFillType(dischargeNodeInfo.dischargeNode.fillUnitIndex)
				if self.supportedFillTypes[fillType] then 
					hasSupportedFillTypeLoaded = true
				end
			end
			if not hasSupportedFillTypeLoaded then
				return false, AIMessageErrorNoValidFillTypeLoaded.new()
			end
			if self.driveToUnloadingTask.x == nil then 
				CpUtil.errorVehicle(self.vehicle, "No valid drive to unload task position set!")
				return false, AIMessageCpError.new()
			end
		end
	end

	return true, nil
end

function CpAIJobCombineUnloader:startTask(task)
	--- Giants unload, reset the discharge nodes before unloading.
	if task == self.driveToUnloadingTask then
		for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do
			dischargeNodeInfo.dirty = true
		end
	end
	CpAIJobCombineUnloader:superClass().startTask(self, task)
end

--- Starting index for giants unload: 
---  - Close or on the field, we make sure cp pathfinder is always involved.
---  - Else if the trailer is full and we are far away from the field, then let giants drive to unload directly.
---@return number
function CpAIJobCombineUnloader:getStartTaskIndex()
	local startTask = CpAIJobCombineUnloader:superClass().getStartTaskIndex(self)
	if not self.cpJobParameters.useGiantsUnload:getValue() then 
		return startTask
	end
	local vehicle = self:getVehicle()
	local fieldPolygon = self:getFieldPolygon()
	local x, _, z = getWorldTranslation(vehicle.rootNode)
	local fillLevelPercentage = FillLevelManager.getTotalTrailerFillLevelPercentage(vehicle)
	local readyToDriveUnloading = vehicle:getCpSettings().fullThreshold:getValue() <= fillLevelPercentage
	if readyToDriveUnloading then 
		CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, vehicle, "Not close to the field and vehicle is full, so start driving to unload.")
		--- Small hack, so we can use the giants function and don't need to do copy & paste.
		local oldTaskIx = self.currentTaskIndex
		self.currentTaskIndex = self.combineUnloaderTask.taskIndex
		--- Needs to be used to set the unload target coordinates.
		self:getNextTaskIndex()
		self.currentTaskIndex = oldTaskIx
		if self.driveToUnloadingTask.x == nil then 
			CpUtil.errorVehicle(self.vehicle, "No valid drive to unload task position set!")
			return startTask
		end
		return self.driveToUnloadingTask.taskIndex
	end
	if CpMathUtil.isPointInPolygon(fieldPolygon, x, z) or 
		CpMathUtil.getClosestDistanceToPolygonEdge(fieldPolygon, x, z) < 2*self.minStartDistanceToField then
		CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, vehicle, "Close to the field, start cp drive strategy.")
		return startTask
	end
	return startTask
end

function CpAIJobCombineUnloader:getIsLooping()
	return true
end

--- Gets the additional task description shown.
--- TODO: Add the missing description once the task system is better implemented.
---@return unknown
function CpAIJobCombineUnloader:getDescription()
	local desc = CpAIJob:superClass().getDescription(self)
	local currentTask = self:getTaskByIndex(self.currentTaskIndex)
    if currentTask == self.driveToTask then
		desc = desc .. " - " .. g_i18n:getText("ai_taskDescriptionDriveToField")
	elseif currentTask == self.combineUnloaderTask then
		if self.cpJobParameters.unloadTarget:getValue() == CpCombineUnloaderJobParameters.UNLOAD_COMBINE then
			desc = desc .. " - " .. g_i18n:getText("CP_ai_taskDescriptionUnloadCombine")
		else 
			desc = desc .. " - " .. g_i18n:getText("CP_ai_taskDescriptionUnloadSiloLoader")
		end
	elseif currentTask == self.driveToUnloadingTask or currentTask == self.dischargeTask then 
		desc = desc .. " - " .. g_i18n:getText("CP_ai_taskDescriptionUnloadingWithGiants")
	end
	return desc
end