local abs, ceil, max, min = math.abs, math.ceil, math.max, math.min;

function courseplay:openCloseHud(vehicle, open)
	courseplay:setMouseCursor(vehicle, open);
	vehicle.cp.hud.show = open;
	--print(string.format("courseplay:openCloseHud set to %s",tostring(vehicle.cp.hud.show)))
	if open then
		--courseplay.buttons:setActiveEnabled(vehicle, 'all');
	else
		courseplay.buttons:setHoveredButton(vehicle, nil);
	end;
end;

function courseplay:setAIDriver(vehicle, mode)

	local errorHandler = function(err)
		printCallstack()
		courseplay.infoVehicle(vehicle, "Exception, can't init mode %d: %s", mode, tostring(err))
		return err
	end

	if vehicle.cp.driver then
		vehicle.cp.driver:delete()
	end

	local status, result
	if mode == courseplay.MODE_TRANSPORT then
		status, result = xpcall(AIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_GRAIN_TRANSPORT then
		status, result = xpcall(GrainTransportAIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_COMBI then
		status, result = xpcall(CombineUnloadAIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_OVERLOADER then
		status, result = xpcall(OverloaderAIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_SHOVEL_FILL_AND_EMPTY then
		status, result = xpcall(ShovelAIDriver.create, errorHandler, vehicle)
	elseif mode == courseplay.MODE_SEED_FERTILIZE then
		status, result = xpcall(FillableFieldworkAIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_FIELDWORK then
		status, result = xpcall(UnloadableFieldworkAIDriver.create, errorHandler, vehicle)
	elseif mode == courseplay.MODE_BALE_COLLECTOR then
		status, result = xpcall(BaleCollectorAIDriver, errorHandler, vehicle)
	elseif mode == courseplay.MODE_BUNKERSILO_COMPACTER then
		status, result = xpcall(BunkerSiloAIDriver.create, errorHandler, vehicle)
	elseif mode == courseplay.MODE_FIELD_SUPPLY then
		status, result = xpcall(FieldSupplyAIDriver, errorHandler, vehicle)
	end
	if status then
		vehicle.cp.driver = result
	else
		-- give it another try as a fallback level
		courseplay.infoVehicle(vehicle, 'Retrying initialization in mode 5')
		self.cp.settings.driverMode:resetToDefault(true)
	end
end

function courseplay:setDriveNow(vehicle)
	vehicle.cp.driver:setDriveNow()
end

---This function gets only called locally like an actionEvent,
---For reference: giants AIVehicle.actionEventToggleAIState() 
function courseplay:startStop(vehicle)
	if vehicle.cp.canDrive then
		---These to function also handle the sync for multiplayer
		if not vehicle:getIsCourseplayDriving() then
		
			courseplay.onStartCpAIDriver(vehicle,nil,false, g_currentMission.player.farmId)
		else
			courseplay.onStopCpAIDriver(vehicle,AIVehicle.STOP_REASON_USER)
		end
	else
		---Not sure why this is hear might be related to an key actionEvent,
		---Should currently be broken in multiplayer now...
		courseplay:start_record(vehicle);
	end
	courseplay.hud:setReloadPageOrder(vehicle, vehicle.cp.hud.currentPage, true);
end;

function courseplay:startStopUnloader(combine)
	local tractor = g_combineUnloadManager:getUnloaderByNumber(1, combine)
	if tractor then	
		tractor.cp.settings.forcedToStop:toggle()
	end
end;

function courseplay:sendUnloaderHome(combine)
	local unloader = g_combineUnloadManager:getUnloaderByNumber(1, combine)
	if unloader then 
		unloader.cp.driver:setDriveUnloadNow(true)
	end
end
function courseplay:setHudPage(vehicle, pageNum)
	vehicle.cp.hud.hudPageButtons[vehicle.cp.hud.currentPage]:setActive(false)
	vehicle.cp.hud.currentPage = pageNum;
	vehicle.cp.hud.hudPageButtons[pageNum]:setActive(true)
	courseplay.hud:setReloadPageOrder(vehicle, vehicle.cp.hud.currentPage, true);
end;

--- These three tool offset function should be handled by the setting.
function courseplay:changeToolOffsetX(vehicle, changeBy)
	vehicle.cp.settings.toolOffsetX:changeBy(changeBy * 0.1,true)
	vehicle.cp.totalOffsetX = vehicle.cp.settings.toolOffsetX:get();
	-- show new setting for a few seconds on the screen
	courseplay:setCustomTimer(vehicle, 'showWorkWidth', 2);
end

function courseplay:setAutoToolOffsetX(vehicle)
	vehicle.cp.settings.toolOffsetX:setToConfiguredValue(vehicle)
end

function courseplay:changeToolOffsetZ(vehicle, changeBy, force, noDraw)
	vehicle.cp.settings.toolOffsetZ:changeBy(changeBy * 0.1,true)
	-- show new setting for a few seconds on the screen
	courseplay:setCustomTimer(vehicle, 'showWorkWidth', 2);
end;

--legancy Code in toolManager still using it!
function courseplay:changeReverseSpeed(vehicle, changeBy, force, forceReloadPage)
	local speed = force or (vehicle.cp.speeds.reverse + changeBy);
	if not force then
		speed = MathUtil.clamp(speed, vehicle.cp.speeds.minReverse, vehicle.cp.speeds.max);
	end;
	vehicle.cp.speeds.reverse = speed;

	if forceReloadPage then
		courseplay.hud:setReloadPageOrder(vehicle, 5, true);
	end;
end


function courseplay:selectAssignedCombine(vehicle, changeBy)
	vehicle.cp.settings.selectedCombineToUnload:refresh()
	if changeBy > 0 then
		vehicle.cp.settings.selectedCombineToUnload:setNext()
	else
		vehicle.cp.settings.selectedCombineToUnload:setPrevious()
	end
	courseplay:removeActiveCombineFromTractor(vehicle)
	courseplay.hud:setReloadPageOrder(vehicle, vehicle.cp.hud.currentPage, true);
end;

function courseplay:removeActiveCombineFromTractor(vehicle)
	if vehicle.cp.driver and vehicle.cp.driver.onUserUnassignedActiveCombine then
		vehicle.cp.driver:onUserUnassignedActiveCombine()
	end
	courseplay.hud:setReloadPageOrder(vehicle, 4, true);
end;

function courseplay:switchDriverCopy(vehicle, changeBy)
	local drivers = courseplay:findDrivers(vehicle);
	
	if drivers ~= nil then
		vehicle.cp.selectedDriverNumber = MathUtil.clamp(vehicle.cp.selectedDriverNumber + changeBy, 0, #(drivers));

		if vehicle.cp.selectedDriverNumber == 0 then
			vehicle.cp.copyCourseFromDriver = nil;
			vehicle.cp.hasFoundCopyDriver = false;
		else
			vehicle.cp.copyCourseFromDriver = drivers[vehicle.cp.selectedDriverNumber];
			vehicle.cp.hasFoundCopyDriver = true;
		end;
	else
		vehicle.cp.copyCourseFromDriver = nil;
		vehicle.cp.selectedDriverNumber = 0;
		vehicle.cp.hasFoundCopyDriver = false;
	end;
end;

function courseplay:findDrivers(vehicle)
	local foundDrivers = {}; -- resetting all drivers
	for _,otherVehicle in pairs(g_currentMission.enterables) do
		if otherVehicle.Waypoints ~= nil and otherVehicle.hasCourseplaySpec  then
			if otherVehicle.rootNode ~= vehicle.rootNode and #(otherVehicle.Waypoints) > 0 then
				table.insert(foundDrivers, otherVehicle);
			end;
		end;
	end;

	return foundDrivers;
end;



function courseplay.settings.add_folder_settings(folder)
	folder.showChildren = false
	folder.skipMe = false
end

function courseplay.settings.add_folder(input1, input2)
-- function might be called like add_folder(vehicle, id) or like add_folder(id)
	local vehicle, id
	
	if input2 ~= nil then
		vehicle = input1
		id = input2
	else
		vehicle = false
		id = input1
	end
	
	if vehicle == false then
	-- no vehicle given -> add folder to all vehicles
		for k,v in pairs(g_currentMission.enterables) do
			if v.hasCourseplaySpec then -- alternative way to check if SpecializationUtil.hasSpecialization(courseplay, v.specializations)
				v.cp.folder_settings[id] = {}
				courseplay.settings.add_folder_settings(v.cp.folder_settings[id])
			end	
		end
	else
	-- vehicle given -> add folder to that vehicle
		vehicle.cp.folder_settings[id] = {}
		courseplay.settings.add_folder_settings(vehicle.cp.folder_settings[id])
	end
end

function courseplay.settings.update_folders(vehicle)
	local old_settings
	
	if vehicle == nil then
	-- no vehicle given -> update all folders in all vehicles
		for k,v in pairs(g_currentMission.enterables) do
			if v.hasCourseplaySpec then -- alternative way to check if SpecializationUtil.hasSpecialization(courseplay, v.specializations)
				old_settings = v.cp.folder_settings
				v.cp.folder_settings = {}
				for _,f in pairs(g_currentMission.cp_folders) do
					if old_settings[f.id] ~= nil then
						v.cp.folder_settings[f.id] = old_settings[f.id]
					else
						v.cp.folder_settings[f.id] = {}
						courseplay.settings.add_folder_settings(v.cp.folder_settings[f.id])
					end
				end
				old_settings = nil
			end	
		end
	else
	-- vehicle given -> update all folders in that vehicle
		old_settings = vehicle.cp.folder_settings
		vehicle.cp.folder_settings = {}
		for _,f in pairs(g_currentMission.cp_folders) do
			if old_settings[f.id] ~= nil then
				vehicle.cp.folder_settings[f.id] = old_settings[f.id]
			else
				vehicle.cp.folder_settings[f.id] = {}
				courseplay.settings.add_folder_settings(vehicle.cp.folder_settings[f.id])
			end
		end
	end
	old_settings = nil
end

function courseplay.settings.setReloadCourseItems(vehicle)
	if vehicle ~= nil then
		vehicle.cp.reloadCourseItems = true
		courseplay.hud:setReloadPageOrder(vehicle, 2, true);
	else
		-- make sure the course list is reloaded when tabbed to all vehicles.
		for k,v in pairs(g_currentMission.enterables) do
			if v.hasCourseplaySpec then -- alternative way to check if SpecializationUtil.hasSpecialization(courseplay, v.specializations)
				v.cp.reloadCourseItems = true
				courseplay.debugVehicle(courseplay.DBG_COURSES, v,"courseplay.hud:setReloadPageOrder(%s, 2, true) TypeName: %s ;",tostring(v.name), v.typeName)
				courseplay.hud:setReloadPageOrder(v, 2, true);
			end
		end
	end
end

function courseplay.settings.toggleFilter(vehicle, enable)
	if enable and not vehicle.cp.hud.filterEnabled then
		vehicle.cp.sorted = vehicle.cp.filtered;
		vehicle.cp.hud.filterEnabled = true;
	elseif not enable and vehicle.cp.hud.filterEnabled then
		vehicle.cp.filtered = vehicle.cp.sorted;
		vehicle.cp.sorted = g_currentMission.cp_sorted;
		vehicle.cp.hud.filterEnabled = false;
	end;
end;

function courseplay.hud.setCourses(self, start_index)
	start_index = start_index or 1
	if start_index < 1 then 
		start_index = 1
	elseif start_index > #self.cp.sorted.item then
		start_index = #self.cp.sorted.item
	end
	
	-- delete content of hud.courses
	self.cp.hud.courses = {}
	
	local index = start_index
	local hudLines = courseplay.hud.numLines
	local i = 1
	
	if index == 1 and self.cp.hud.showZeroLevelFolder then
		table.insert(self.cp.hud.courses, { id=0, uid=0, name='Level 0', displayname='Level 0', parent=0, type='folder', level=0})
		i = 2	-- = i+1
	end
	
	-- is start_index even showed?
	index = courseplay.courses:getMeOrBestFit(self, index)
	
	if index ~= 0 then
		-- insert first entry
		table.insert(self.cp.hud.courses, self.cp.sorted.item[index])
		i = i+1
		
		-- now search for the next entries
		while i <= hudLines do
			index = courseplay.courses:getNextCourse(self,index)
			if index == 0 then
				-- no next item found: fill table with previous items and abort the loop
				if start_index > 1 then
					-- shift up
					courseplay:shiftHudCourses(self, -(hudLines - i + 1))
				end
				i = hudLines+1 -- abort the loop
			else
				table.insert(self.cp.hud.courses, self.cp.sorted.item[index])
				i = i + 1
			end
		end --while
	end -- i<3

	courseplay.hud:setReloadPageOrder(self, 2, true);
end

function courseplay.hud.reloadCourses(vehicle)
	local index = 1
	local i = 1
	if vehicle ~= nil then
		while i <= #vehicle.cp.hud.courses and vehicle.cp.sorted.info[ vehicle.cp.hud.courses[i].uid ] == nil do
			i = i + 1
		end		
		if i <= #vehicle.cp.hud.courses then 
			index = vehicle.cp.sorted.info[ vehicle.cp.hud.courses[i].uid ].sorted_index
		end
		courseplay.hud.setCourses(vehicle, index)
	else
		for k,v in pairs(g_currentMission.enterables) do
			if v.hasCourseplaySpec then -- alternative way to check if SpecializationUtil.hasSpecialization(courseplay, v.specializations)
				i = 1
				-- course/folder in the hud might have been deleted -> info no longer available				
				while i <= #v.cp.hud.courses and v.cp.sorted.info[ v.cp.hud.courses[i].uid ] == nil do
					i = i + 1
				end
				if i > #v.cp.hud.courses then
					index = 1
				else
					index = v.cp.sorted.info[ v.cp.hud.courses[i].uid ].sorted_index
				end
				courseplay.hud.setCourses(v,index)
			end
		end
	end
end

function courseplay:shiftHudCourses(vehicle, change_by)	
	local hudLines = courseplay.hud.numLines
	local index = hudLines
	
	while change_by > 0 do
		-- get the index of the last showed item
		index = vehicle.cp.sorted.info[vehicle.cp.hud.courses[#(vehicle.cp.hud.courses)].uid].sorted_index
		
		-- search for the next item
		index = courseplay.courses:getNextCourse(vehicle,index)
		if index == 0 then
			-- there is no next item: abort
			change_by = 0
		else
			if #(vehicle.cp.hud.courses) == hudLines then
				-- remove first entry...
				table.remove(vehicle.cp.hud.courses, 1)
			end
			-- ... and add one at the end
			table.insert(vehicle.cp.hud.courses, vehicle.cp.sorted.item[index])
			change_by = change_by - 1
		end		
	end

	while change_by < 0 do
		-- get the index of the first showed item
		index = vehicle.cp.sorted.info[vehicle.cp.hud.courses[1].uid].sorted_index
		
		-- search reverse for the next item
		index = courseplay.courses:getNextCourse(vehicle, index, true)
		if index == 0 then
			-- there is no next item: abort
			change_by = 0
			
			-- show LevelZeroFolder?
			if vehicle.cp.hud.showZeroLevelFolder then
				if #(vehicle.cp.hud.courses) >= hudLines then
					-- remove last entry...
					table.remove(vehicle.cp.hud.courses)
				end
				table.insert(vehicle.cp.hud.courses, 1, { id=0, uid=0, name='Level 0', displayname='Level 0', parent=0, type='folder', level=0})
			end
			
		else
			if #(vehicle.cp.hud.courses) >= hudLines then
				-- remove last entry...
				table.remove(vehicle.cp.hud.courses)
			end
			-- ... and add one at the beginning:	
			table.insert(vehicle.cp.hud.courses, 1, vehicle.cp.sorted.item[index])
			change_by = change_by + 1
		end		
	end
	
	courseplay.hud:setReloadPageOrder(vehicle, 2, true);
end

--Update all vehicles' course list arrow displays
function courseplay.settings.validateCourseListArrows(vehicle)
	local n_courses = #(vehicle.cp.sorted.item)
	local n_hudcourses, prev, next
	
	if vehicle then
		-- update vehicle only
		prev = true
		next = true
		n_hudcourses = #(vehicle.cp.hud.courses)
		if not (n_hudcourses > 0) then
			prev = false
			next = false
		else
			-- update prev
			if vehicle.cp.hud.showZeroLevelFolder then
				if vehicle.cp.hud.courses[1].uid == 0 then
					prev = false
				end
			elseif vehicle.cp.sorted.info[ vehicle.cp.hud.courses[1].uid ].sorted_index == 1 then
				prev = false
			end
			-- update next
			if n_hudcourses < courseplay.hud.numLines then
				next = false
			elseif vehicle.cp.hud.showZeroLevelFolder and vehicle.cp.hud.courses[n_hudcourses].uid == 0 then
				next = false
			elseif 0 == courseplay.courses:getNextCourse(vehicle, vehicle.cp.sorted.info[ vehicle.cp.hud.courses[n_hudcourses].uid ].sorted_index) then
				next = false
			end
		end
		return prev, next;
		--vehicle.cp.hud.courseListPrev = prev
		--vehicle.cp.hud.courseListNext = next
	--[[else
		-- update all vehicles
			for k,v in pairs(g_currentMission.enterables) do
			if v.hasCourseplaySpec then -- alternative way to check if SpecializationUtil.hasSpecialization(courseplay, v.specializations)
				prev = true
				next = true
				n_hudcourses = #(v.cp.hud.courses)
				if not (n_hudcourses > 0) then
					prev = false
					next = false
				else
					-- update prev
					if v.cp.hud.showZeroLevelFolder then
						if v.cp.hud.courses[1].uid == 0 then
							prev = false
						end
					elseif v.cp.sorted.info[v.cp.hud.courses[1].uid].sorted_index == 1 then
						prev = false
					end
					-- update next
					if n_hudcourses < coursplay.hud.numLines then
						next = false
					elseif 0 == courseplay.courses:getNextCourse(v, v.cp.sorted.info[v.cp.hud.courses[n_hudcourses].uid].sorted_index) then
						next = false
					end
				end
				v.cp.hud.courseListPrev = prev
				v.cp.hud.courseListNext = next
			end -- if hasSpecialization
		end]] -- in pairs(enterables)
	end -- if vehicle
end;

function courseplay:expandFolder(vehicle, index)
-- expand/reduce a folder in the hud
	if vehicle.cp.hud.courses[index].type == 'folder' then
		local f = vehicle.cp.folder_settings[ vehicle.cp.hud.courses[index].id ]
		f.showChildren = not f.showChildren
		if f.showChildren then
		-- from not showing to showing -> put it on top to see as much of the content as possible
			courseplay.hud.setCourses(vehicle, vehicle.cp.sorted.info[vehicle.cp.hud.courses[index].uid].sorted_index)
		else
		-- from showing to not showing -> stay where it was
			courseplay.hud.reloadCourses(vehicle)
		end
	end
end

function courseplay:validateCanSwitchMode(vehicle)
	vehicle:setCpVar('canSwitchMode', not vehicle:getIsCourseplayDriving() and not vehicle.cp.isRecording and
		not vehicle.cp.recordingIsPaused ,courseplay.isClient);
	courseplay:debug(('%s: validateCanSwitchMode(): isDriving=%s, isRecording=%s, recordingIsPaused=%s ==> canSwitchMode=%s'):format(
		nameNum(vehicle), tostring(vehicle:getIsCourseplayDriving()), tostring(vehicle.cp.isRecording),
		tostring(vehicle.cp.recordingIsPaused), tostring(vehicle.cp.canSwitchMode)), courseplay.DBG_UNCATEGORIZED);
end;

function courseplay:reloadCoursesFromXML(vehicle)
	courseplay:debug("reloadCoursesFromXML()", courseplay.DBG_COURSES);
	if g_server ~= nil then
		courseplay.courses:loadCoursesAndFoldersFromXml();

		--courseplay:debug(tableShow(g_currentMission.cp_courses, "g_cM cp_courses", 8), courseplay.DBG_COURSES);
		courseplay:debug("g_currentMission.cp_courses = courseplay.courses:loadCoursesAndFoldersFromXml()", courseplay.DBG_COURSES);
		if not vehicle:getIsCourseplayDriving() then
			local loadedCoursesBackup = vehicle.cp.loadedCourses;
			courseplay:clearCurrentLoadedCourse(vehicle);
			vehicle.cp.loadedCourses = loadedCoursesBackup;
			courseplay:reloadCourses(vehicle, true);
		end;
		courseplay.settings.update_folders()
		courseplay.settings.setReloadCourseItems()
		--courseplay.hud.reloadCourses()
	end
end;

function courseplay:setMouseCursor(self, show)
	self.cp.mouseCursorActive = show;
	g_inputBinding:setShowMouseCursor(show);

	--Cameras: deactivate/reactivate zoom function in order to allow CP mouse wheel
	for camIndex,_ in pairs(self.cp.camerasBackup) do
		self.spec_enterable.cameras[camIndex].allowTranslation = not show;
		--print(string.format("%s: right mouse key (mouse cursor=%s): camera %d allowTranslation=%s", nameNum(self), tostring(self.cp.mouseCursorActive), camIndex, tostring(self.cameras[camIndex].allowTranslation)));
	end;

	if not show then
		for i,button in pairs(self.cp.buttons.global) do
			button:setHovered(false);
		end;
		for i,button in pairs(self.cp.buttons[self.cp.hud.currentPage]) do
			button:setHovered(false);
		end;
		if self.cp.hud.currentPage == 2 then
			for i,button in pairs(self.cp.buttons[-2]) do
				button:setHovered(false);
			end;
		end;

		for line=1,courseplay.hud.numLines do
			self.cp.hud.content.pages[self.cp.hud.currentPage][line][1].isHovered = false;
		end;

		courseplay.buttons:setHoveredButton(self, nil);

		self.cp.hud.mouseWheel.render = false;
	end;
end;

--- Changes the line of debug channels visible in the hud.
--- For example changes dbg channels 1-12 to 13-24 in the hud.
function courseplay:changeDebugChannelSection(vehicle, changeBy)
	local debugChannelData = courseplay.hud.debugChannelData
	--- Changes the visible debug channels in the hud.
	debugChannelData.lineSelected = MathUtil.clamp(debugChannelData.lineSelected + changeBy, 1, debugChannelData.lines)
	debugChannelData.lineLastChannel = debugChannelData.buttonsPerLine * debugChannelData.lineSelected
	debugChannelData.lineFirstChannel = debugChannelData.lineLastChannel - debugChannelData.buttonsPerLine + 1
	
	--- Sets the buttons to the correct debug channel after a line change.
	for channel = debugChannelData.lineFirstChannel, debugChannelData.lineLastChannel do
		local col = ((channel-1) % debugChannelData.buttonsPerLine) + 1
		local button = vehicle.cp.hud.debugChannelButtons[col]
		button:setParameter(channel)
		button:setToolTip(debugChannelData.toolTips[channel])
	end
end

function courseplay:setEngineState(vehicle, on)
	if vehicle == nil or on == nil or vehicle.spec_motorized.isMotorStarted == on then
		return;
	end;

	-- default
	if vehicle.startMotor and vehicle.stopMotor then
		if on then
			vehicle:startMotor();
		else
			vehicle.lastAcceleration = 0;
			vehicle:stopMotor();
		end;
	end;
end;

function courseplay:toggleFindFirstWaypoint(vehicle)
	vehicle:setCpVar('distanceCheck',not vehicle.cp.distanceCheck,courseplay.isClient);
	if not courseplay.isClient and not vehicle.cp.distanceCheck then
		courseplay:setInfoText(vehicle, nil);
	end;
	--courseplay.buttons:setActiveEnabled(vehicle, 'findFirstWaypoint');
end;

function courseplay:setSlippingStage(vehicle, stage)
	if vehicle.cp.slippingStage ~= stage then
		courseplay:debug(('%s: setSlippingStage(..., %d)'):format(nameNum(vehicle), stage), courseplay.DBG_AI_DRIVER);
		vehicle.cp.slippingStage = stage;
	end;
end;

----------------------------------------------------------------------------------------------------

function courseplay:setCpVar(varName, value, noEventSend)
	local split = StringUtil.splitString(".", varName);
	if #split ==1 then
		if self.cp[varName] ~= value then
			local oldValue = self.cp[varName]; --TODO check whether needed or not
			self.cp[varName] = value;		
			if CpManager.isMP and not noEventSend then
				--print(courseplay.utils:getFnCallPath(2))
				courseplay:debug(string.format("setCpVar: %s: %s -> send Event",varName,tostring(value)), courseplay.DBG_MULTIPLAYER);
				CourseplayEvent.sendEvent(self, "self.cp."..varName, value)
			end
			if varName == "isDriving" then
				courseplay:debug("reload page 1", courseplay.DBG_MULTIPLAYER);
				courseplay.hud:setReloadPageOrder(self, 1, true);
			elseif varName:sub(1, 3) == 'HUD' then
				-- TODO: using the variable name to trigger a HUD refresh is not a good idea.
				--print('broken settings 1860')
				if StringUtil.startsWith(varName, 'HUD0') then
					courseplay:debug("reload page 0", courseplay.DBG_MULTIPLAYER);
					courseplay.hud:setReloadPageOrder(self, 0, true);
				elseif StringUtil.startsWith(varName, 'HUD1') then
					courseplay:debug("reload page 1", courseplay.DBG_MULTIPLAYER);
					courseplay.hud:setReloadPageOrder(self, 1, true);
				elseif StringUtil.startsWith(varName, 'HUD4') then
					courseplay:debug("reload page 4", courseplay.DBG_MULTIPLAYER);
					courseplay.hud:setReloadPageOrder(self, 4, true);
				end;
			elseif varName == 'waypointIndex' and self.cp.hud.currentPage == courseplay.hud.PAGE_CP_CONTROL and (self.cp.isRecording or self.cp.recordingIsPaused) and value and value == 4 then -- record pause action becomes available
				--courseplay.buttons:setActiveEnabled(self, 'recording');
			end;
		end;
	elseif #split == 2 then
		if self.cp[split[1]][split[2]] ~= value then
			self.cp[split[1]][split[2]] = value
		end
		-- TODO: this is unclear, shouldn't this be only called when the value changed?
		if CpManager.isMP and not noEventSend then
			--print(courseplay.utils:getFnCallPath(2))
			courseplay:debug(string.format("setCpVar: %s: %s -> send Event",varName,tostring(value)), courseplay.DBG_MULTIPLAYER);
			CourseplayEvent.sendEvent(self, "self.cp."..varName, value)
		end
	end
end;

---@class Setting
Setting = CpObject()

--- Interface for settings
--- @param name string name of this settings, will be used as an identifier in containers and XML
--- @param label string text ID in translations used as a label for this setting on the GUI
--- @param toolTip string text ID in translations used as a tooltip for this setting on the GUI
--- @param vehicle table vehicle, needed for vehicle specific settings for multiplayer syncs
function Setting:init(name, label, toolTip, vehicle, value)
	self.name = name
	self.label = label
	self.toolTip = toolTip
	self.value = value
	-- Required to send sync events for settings changes
	self.vehicle = vehicle
	self.syncValue = false
	-- override
	self.xmlKey = name
	self.xmlAttribute = '#value'
	self.events={}
	self.debugMpChannel = courseplay.DBG_MULTIPLAYER
end

-- Get the current value
function Setting:get()
	return self.value
end

-- Is the current value same as the param?
function Setting:is(value)
	return self.value == value
end

function Setting:equals(value)
	return self.value == value
end

-- Get the current text to be shown on the UI
function Setting:getText()
	return tostring(self.value)
end

function Setting:getLabel()
	return courseplay:loc(self.label)
end

function Setting:getName()
	return self.name
end

function Setting:getParentName()
	return self.parentName
end

function Setting:getToolTip()
	return courseplay:loc(self.toolTip)
end

-- function only called from network to set synced setting
function Setting:setFromNetwork(value)
	self:set(value,true)
end

function Setting:getDebugString()
	return string.format('%s: %s', self.name, tostring(self:get()))
end

--- Set to a specific value
function Setting:set(value)
	self.value = value
	self:onChange()
end

function Setting:onChange()
	-- setting specific implementation in the derived classes
end

function Setting:getKey(parentKey)
	return parentKey .. '.' .. self.xmlKey .. self.xmlAttribute
end

function Setting:loadFromXml(xml, parentKey)
	-- override
end

function Setting:saveToXml(xml, parentKey)
	-- override
end

-- For settings where the valid values depend on other conditions, re-evaluate the validity of the
-- current setting (when for example changed the mode of the vehicle, is the current setting still valid for the new mode)
function Setting:validateCurrentValue()
	-- override
end

function Setting:setParent(name)
	self.parentName = name
end

-- remember the associated GUI element
function Setting:setGuiElement(element)
	self.guiElement = element
end

function Setting:getGuiElement()
	return self.guiElement
end

function Setting:hasGuiElement()
	return self.guiElement~=nil
end

--- Should this setting be disabled on the GUI?
function Setting:isDisabled()
	return false
end

--- Action event for Keys?
function Setting:actionEvent(actionName, inputValue, callbackState, isAnalog)
	---override
end

--- Registers an event that synchronizes a value.
---@param eventFunc function Callback function run on the receiving end of the event.
---@param getValueFunc function Callback function to get the value, which needs synchronizing.
---@param readFunc function Reads the value data stream on the receiving end with this function.
---@param writeFunc function Writes the value data stream from the sender with this function.
function Setting:registerEvent(eventFunc,getValueFunc,readFunc,writeFunc)
	local ix = #self.events+1
	local event = {
		eventFunc = eventFunc,
		getValueFunc = getValueFunc,
		readFunc = readFunc,
		writeFunc = writeFunc,
		ix = ix
	}
	table.insert(self.events,event)
	return ix
end

--- Registers an event that synchronizes an Int value.
---@param eventFunc function
---@param getValueFunc function
function Setting:registerIntEvent(eventFunc,getValueFunc)
	return self:registerEvent(eventFunc,getValueFunc,streamReadInt32,streamWriteInt32)
end

--- Registers an event that synchronizes an Float value.
---@param eventFunc function
---@param getValueFunc function
function Setting:registerFloatEvent(eventFunc,getValueFunc)
	return self:registerEvent(eventFunc,getValueFunc,streamReadFloat32,streamWriteFloat32)
end

--- Registers an event that synchronizes an Boolean value.
---@param eventFunc function
---@param getValueFunc function
function Setting:registerBoolEvent(eventFunc,getValueFunc)
	return self:registerEvent(eventFunc,getValueFunc,streamReadBool,streamWriteBool)
end
--- Registers an event that requests a function call on the receiving end.
---@param eventFunc function
function Setting:registerFunctionEvent(eventFunc)
	return self:registerEvent(eventFunc)
end

--- Gets an event by it's id.
function Setting:getEvent(ix)
	return self.events[ix]
end

--- Raises an event by it's id to synchronize a value,
--- which is defined by a callback function.
function Setting:raiseEvent(eventIx,value)
	local event = self:getEvent(eventIx)
	value = event.getValueFunc and event.getValueFunc(self) or value
	if self.vehicle ~= nil then
		VehicleSettingEvent.sendEvent(self.vehicle,self,event,value)
	else
		GlobalSettingEvent.sendEvent(self,event,value)
	end
end

--- Setting debug.
---@param channel number debug channel
function Setting:debug(channel,...)
	courseplay.debugFormat(channel,...)
end

function Setting:debugMp(...)
	self:debug(self.debugMpChannel,...)
end

function Setting:isMpDebugActive()
	return courseplay.debugChannels[courseplay.DBG_MULTIPLAYER]
end

--- Debug for synchronizing on joining a game.
---@param value any the value that gets synchronized.
---@param valueName string the value name
function Setting:debugWriteStream(value,valueName)
	if self:isMpDebugActive() then
		self:debugMp("Write, container: %s, setting: %s, %s: %s",self.parentName,self.name,valueName or "value",tostring(value))
	end
end

--- Debug for synchronizing on joining a game.
---@param value any the value that gets synchronized.
---@param valueName string the value name
function Setting:debugReadStream(value,valueName)
	if self:isMpDebugActive() then
		self:debugMp("Read, container: %s, setting: %s, %s: %s",self.parentName,self.name,valueName or "value",tostring(value))
	end
end

--- Is synchronizing of this setting allowed.
function Setting:isSyncAllowed()
	return self.syncValue
end

---@class FloatSetting :Setting
FloatSetting = CpObject(Setting)
--- @param name string name of this settings, will be used as an identifier in containers and XML
--- @param label string text ID in translations used as a label for this setting on the GUI
--- @param toolTip string text ID in translations used as a tooltip for this setting on the GUI
--- @param vehicle table vehicle, needed for vehicle specific settings for multiplayer syncs
--- @param value number default value
function FloatSetting:init(name, label, toolTip, vehicle, value)
	Setting.init(self, name, label, toolTip, vehicle, value)
	self.DEFAULT_EVENT = self:registerFloatEvent(self.setFromNetwork,self.get)
end

function FloatSetting:loadFromXml(xml, parentKey)
	local value = getXMLFloat(xml, self:getKey(parentKey))
	if value then
		self:set(value,true)
	end
end

function FloatSetting:saveToXml(xml, parentKey)
	setXMLFloat(xml, self:getKey(parentKey), self:get())
end

function FloatSetting:onWriteStream(stream)
	local value = self:get()
	self:debugWriteStream(value)
	streamWriteFloat32(stream, value)
end

function FloatSetting:onReadStream(stream)
	local value = streamReadFloat32(stream)
	self:debugReadStream(value)
	if value then 
		self:setFromNetwork(value)
	end
end

function FloatSetting:changeByX(x)
	self:set(self:get()+x)
end

function FloatSetting:set(value,noEventSend)
	self.value = value
	if noEventSend == nil or noEventSend == false then
		if self:isSyncAllowed() then
			self:sendEvent(value)
		end
	end
	self:onChange()
end

function FloatSetting:sendEvent(value)
	self:raiseEvent(self.DEFAULT_EVENT)
end

---@class IntSetting : Setting
IntSetting = CpObject(Setting)
--- @param name string name of this settings, will be used as an identifier in containers and XML
--- @param label string text ID in translations used as a label for this setting on the GUI
--- @param toolTip string text ID in translations used as a tooltip for this setting on the GUI
--- @param vehicle table vehicle, needed for vehicle specific settings for multiplayer syncs
--- @param MIN number min allowed value
--- @param MAX number max allowed value
--- @param value number default value
function IntSetting:init(name, label, toolTip, vehicle,MIN,MAX,value)
	Setting.init(self, name, label, toolTip, vehicle,value)
	self.MAX = MAX
	self.MIN = MIN
	self.DEFAULT_EVENT = self:registerIntEvent(self.setFromNetwork,self.get)
end

function IntSetting:loadFromXml(xml, parentKey)
	local value = getXMLInt(xml, self:getKey(parentKey))
	if value then
		self:set(value,true)
	end
end

function IntSetting:saveToXml(xml, parentKey)
	setXMLInt(xml, self:getKey(parentKey), self:get())
end

function IntSetting:onWriteStream(stream)
	local value = self:get()
	self:debugWriteStream(value)
	streamWriteInt32(stream, value)
end

function IntSetting:onReadStream(stream)
	local value = streamReadInt32(stream)
	self:debugReadStream(value)
	if value then 
		self:setFromNetwork(value)
	end
end

function IntSetting:changeByX(x)
	self:set(self:get()+x)
end

function IntSetting:set(value,noEventSend)
	local minOk = self.MIN and value>=self.MIN or self.MIN == nil
	local maxOk = self.MAX and value<=self.MAX or self.MAX == nil
	if minOk and maxOk and value then 
		self.value = value
		if noEventSend == nil or noEventSend == false then
			if self:isSyncAllowed() then
				self:sendEvent(value)
			end
		end
		self:onChange()
	end
end

function IntSetting:sendEvent(value)
	self:raiseEvent(self.DEFAULT_EVENT)
end

---@class SettingList : Setting
SettingList = CpObject(Setting)

--- A setting that can have a predefined set of values
--- @param name string name of this settings, will be used as an identifier in containers and XML
--- @param label string text ID in translations used as a label for this setting on the GUI
--- @param toolTip string text ID in translations used as a tooltip for this setting on the GUI
--- @param vehicle table vehicle, needed for vehicle specific settings for multiplayer syncs
--- @param values table with the valid values
--- @param texts string[] name in the translation XML files describing the corresponding value
function SettingList:init(name, label, toolTip, vehicle, values, texts)
	Setting.init(self, name, label, toolTip, vehicle)
	self.values = values
	self.texts = texts
	-- index of the current value/text
	self.current = 1
	-- index of the previous value/text
	self.previous = 1

	self.DEFAULT_EVENT = self:registerIntEvent(self.setFromNetwork,self.getNetworkCurrentValue)
end

-- Get the current value
function SettingList:get()
	return self.values[self.current]
end

---@param seconds number if value changed within seconds than it should be considered invalid
---@return nil if value changed in the last seconds seconds, otherwise the current value
function SettingList:getIfNotChangedFor(seconds)
	if self:getSecondsSinceLastChange() > seconds then
		return self:get()
	else
		return nil
	end
end

-- Is the current value same as the param?
function SettingList:is(value)
	return self.values[self.current] == value
end

-- Get the current text key (for the logs, for example)
function SettingList:__tostring()
	return self.texts[self.current]
end

-- Get the current text
function SettingList:getText()
	return courseplay:loc(self.texts[self.current])
end

--- Set the next value
function SettingList:setNext()
	local new = self:checkAndSetValidValue(self.current + 1)
	self:setToIx(new)
end

--- Set the previous value
function SettingList:setPrevious()
	local new = self:checkAndSetValidValue(self.current - 1)
	self:setToIx(new)
end

function SettingList:changeByX(x)
	local ix = 1
	if x<0 then
		ix = -1
	end
	local new = self:checkAndSetValidValue(self.current + ix)
	self:setToIx(new)
end

-- TODO: consolidate this with setNext()
function SettingList:next()
	self:setNext()
end

-- private function to set to the value at ix
function SettingList:setToIx(ix, noEventSend)
	if ix ~= self.current then
		self.previous = self.current
		self.current = ix
		self:onChange()
		self.lastChangeTimeMilliseconds = g_time
		if noEventSend == nil or noEventSend == false then
			if self:isSyncAllowed() then
				self:sendEvent()
			end
		end
	end
end

-- function only called from network to set synced setting
function SettingList:setFromNetwork(ix)
	if ix ~= self.current then
		self.previous = self.current
		self.current = ix
		self:onChange()
	end
end

--- Set to a specific value
function SettingList:set(value,noEventSend)
	local new
	-- find the value requested
	for i = 1, #self.values do
		if self.values[i] == value then
			new = self:checkAndSetValidValue(i)
			self:setToIx(new,noEventSend)
			return
		end
	end
end

function SettingList:checkAndSetValidValue(new)
	if new > #self.values then
		return 1
	elseif new < 1 then
		return #self.values
	else
		return new
	end
end

function SettingList:onChange()
	-- setting specific implementation in the derived classes
end

--- Helper functions for the case when used with a GUI multi text option element
function SettingList:getGuiElementTexts()
	local texts = {}
	for _, text in ipairs(self.texts) do
		table.insert(texts, courseplay:loc(text))
	end
	return texts
end

function SettingList:getValueFromGuiElementState(state)
	return self.values[state]
end

function SettingList:getGuiElementState()
	return self:getGuiElementStateFromValue(self.values[self.current])
end

function SettingList:getGuiElementStateFromValue(value)
	for i = 1, #self.values do
		if self.values[i] == value then
			return i
		end
	end
	return nil
end

function SettingList:setFromGuiElement()
	if self.guiElement then
		self:setToIx(self.guiElement:getState())
	end
end

function SettingList:updateGuiElement()
	if self.guiElement then
		self.guiElement:setTexts(self:getGuiElementTexts())
		self.guiElement:setState(self:getGuiElementState())
	end
end

function SettingList:getKey(parentKey)
	return  self:getElementKey(parentKey) .. self.xmlAttribute
end

function SettingList:getElementKey(parentKey)
	return parentKey .. '.' .. self.xmlKey
end

function SettingList:loadFromXml(xml, parentKey)
	-- remember the value loaded from XML for those settings which aren't up to date when loading,
	-- for example the field numbers
	self.valueFromXml = getXMLInt(xml, self:getKey(parentKey))
	if self.valueFromXml then 
		self:set(self.valueFromXml,true)
	end
end

function SettingList:saveToXml(xml, parentKey)
	setXMLInt(xml, self:getKey(parentKey), Utils.getNoNil(self:get(),0))
end

---@return number seconds since last change
function SettingList:getSecondsSinceLastChange()
	return self:getMilliSecondsSinceLastChange() / 1000
end

---@return number milliseconds since last change
function SettingList:getMilliSecondsSinceLastChange()
	return (g_time - self.lastChangeTimeMilliseconds)
end

function SettingList:validateCurrentValue()
	local new = self:checkAndSetValidValue(self.current)
	self:setToIx(new,true)
end

function SettingList:getDebugString()
	-- replace % as this string goes through multiple formats (%% does not seem to work and I have no time to figure it out
	return string.format('%s: %s', self.name, string.gsub(self.texts[self.current], '%%', 'percent'))
end

function SettingList:onWriteStream(stream)
	local value =  self:getNetworkCurrentValue()
	self:debugWriteStream(value)
	streamWriteInt32(stream, value)
end

function SettingList:onReadStream(stream)
	local value = streamReadInt32(stream)
	self:debugReadStream(value)
	if value ~= nil then 
		self:setFromNetwork(value)
	end
end

function SettingList:getNetworkCurrentValue()
	return self.current
end

function SettingList:sendEvent()
	self:raiseEvent(self.DEFAULT_EVENT)
end

function SettingList:actionEvent(actionName, inputValue, callbackState, isAnalog)
	self:changeByX(1)

	---TODO: figure out how to refresh the hud after an setting action event correctly.  
	self.vehicle.cp.driver:refreshHUD()
end

---WIP
---Generic LinkedList setting and Interface for LinkedList.lua
---@class LinkedList : Setting
LinkedListSetting = CpObject(Setting)
function LinkedListSetting:init(name, label, toolTip, vehicle)
	Setting.init(self, name, label, toolTip, vehicle)
	self.List = LinkedList({value=nil,text="Dummy"})
end

function LinkedListSetting:moveUpByIndex(index)
	self.List:swapUpX(index)
end

function LinkedListSetting:moveDownByIndex(index)
	self.List:swapDownX(index)
end

function LinkedListSetting:addLast(data)
	self.List:addLast(data)
end

function LinkedListSetting:deleteByIndex(index)
	self.List:removeX(index)
end

function LinkedListSetting:getText(index)
	data = self:getDataByIndex(index)
	if data and data.text then 
		return data.text
	else
		return ""
	end
end

function LinkedListSetting:getDataByIndex(index)
	local element = self.List:getElementByIndex(index)
	if element and element.data then 
		return element.data
	end
end

function LinkedListSetting:getSize()
	return self.List:getSize()
end

function LinkedListSetting:getData()
	return self.List:getData()
end

function LinkedListSetting:getDataXtoY(x,y)	
	return self.List:getDataXtoY(x,y)
end

function LinkedListSetting:isEmpty()	
	return self:getSize()<=0
end

function LinkedListSetting:onWriteStream(stream)
	--override code
end

function LinkedListSetting:onReadStream(stream)
	--override code
end
--- Generic boolean setting
---@class BooleanSetting : SettingList
BooleanSetting = CpObject(SettingList)

function BooleanSetting:init(name, label, toolTip, vehicle, texts)
	if not texts then
		texts = {
			'COURSEPLAY_DEACTIVATED',
			'COURSEPLAY_ACTIVATED'
		}
	end
	SettingList.init(self, name, label, toolTip, vehicle,
		{
			false,
			true
		}, texts)
	self.xmlAttribute = '#active'
end

function BooleanSetting:toggle()
	self:set(not self:get())
end

function BooleanSetting:changeByX(x)
	self:toggle()
end


function BooleanSetting:loadFromXml(xml, parentKey)
	local value = getXMLBool(xml, self:getKey(parentKey))
	if value ~= nil then
		self:set(value,true)
	end
end

function BooleanSetting:saveToXml(xml, parentKey)
	setXMLBool(xml, self:getKey(parentKey), self:get())
end

--- Generic Percentage setting from 1% to 100%
---@class PercentageSettingList : SettingList
PercentageSettingList = CpObject(SettingList)
function PercentageSettingList:init(name, label, toolTip, vehicle)
	local values = {}
	local texts = {}
	for i=1,100 do 
		values[i] = i
		texts[i] = i.."%"
	end
	SettingList.init(self, name, label, toolTip, vehicle,values, texts)
end

function PercentageSettingList:checkAndSetValidValue(new)
	if new <= #self.values and new > 0 then
		return new
	else
		return self.current
	end
end

--- Generic Speed setting from x to y 
---@class SpeedSetting : SettingList
SpeedSetting = CpObject(SettingList)
function SpeedSetting:init(name, label, toolTip, vehicle,startValue,stopValue)
	local values = {}
	local texts = {}
	for i=1,stopValue-startValue do 
		local x = startValue+i-1
		values[i] = x
		texts[i] = x
	end
	SettingList.init(self, name, label, toolTip, vehicle,values, texts)
end

function SpeedSetting:getText()
	local speed = self.texts[self.current] ---in km/h
	
	return string.format("%i %s",g_i18n:getSpeed(speed),g_i18n:getSpeedMeasuringUnit())
end

-- Generic IntSetting with power of x (for example 0.1) 
---@class IntSettingScientific 
IntSettingScientific = CpObject(IntSetting)
--- @param name string name of this settings, will be used as an identifier in containers and XML
--- @param label string text ID in translations used as a label for this setting on the GUI
--- @param toolTip string text ID in translations used as a tooltip for this setting on the GUI
--- @param vehicle table vehicle, needed for vehicle specific settings for multiplayer syncs
--- @param MIN int min allowed value
--- @param MAX int max allowed value
--- @param POWER float changes int value by const POWER 
function IntSettingScientific:init(name, label, toolTip, vehicle,MIN,MAX,POWER)
	IntSetting.init(self, name, label, toolTip, vehicle,MIN,MAX)
	self.MAX = MAX
	self.MIN = MIN
	self.POWER = POWER
end

function IntSettingScientific:getText()
	return tostring(self:getFixedValue())
end

-- get the fixed value with self.POWER
function IntSettingScientific:getFixedValue()
	return self:get()*self.POWER
end

---@class DriverModeSetting : SettingList
DriverModeSetting = CpObject(SettingList)
DriverModeSetting.GRAIN_TRANSPORT = 1
DriverModeSetting.COMBINE_UNLOAD = 2
DriverModeSetting.OVERLOAD = 3
DriverModeSetting.FILLABLE_FIELDWORK = 4
DriverModeSetting.TRANSPORT = 5
DriverModeSetting.FIELDWORK = 6
DriverModeSetting.BALE_COLLECTOR = 7
DriverModeSetting.FIELD_SUPPLY = 8
DriverModeSetting.SHOVEL = 9
DriverModeSetting.BUNKER_SILO = 10
DriverModeSetting.NUM_MODES = 10

DriverModeSetting.defaultMode = DriverModeSetting.TRANSPORT

function DriverModeSetting:init(vehicle)
	local values = {}
	local texts = {}
	for i=1,self.NUM_MODES do 
		values[i] = i
		texts[i] = i
	end
	SettingList.init(self,"driverMode","driverMode","driverMode",vehicle,values,texts)
	self.current = self.defaultMode
end

function DriverModeSetting:loadFromXml(xml, parentKey)
	-- remember the value loaded from XML for those settings which aren't up to date when loading,
	-- for example the field numbers
	self.valueFromXml = Utils.getNoNil(getXMLInt(xml, self:getKey(parentKey)),self.defaultMode)
	self:setFromNetwork(self.valueFromXml)
end

--- Only allow courseplay:getIsToolCombiValidForCpMode() after all implements are attached 
--- and make sure to update the hud.
function DriverModeSetting:postInit()
	self.valueFromXml = nil
	self:validateCurrentValue()
	self.vehicle.cp.driver:refreshHUD()
end

function DriverModeSetting:validateCurrentValue()
	--- When the AIDriver gets initialized the first time not all 
	--- implements are attached, so ignore the valid check.
	
	self.validModes = {}
	for i=1,self.NUM_MODES do 
		local valid = courseplay:getIsToolCombiValidForCpMode(self.vehicle,i)
		self.validModes[i] = valid or false
		--- Only reset the driver mode from the server
		if g_server and self.valueFromXml == nil then
			if self:get() == i and not valid then 
				self:resetToDefault()
			end
		end
	end
end

function DriverModeSetting:onChange()
	self:setAIDriver()
	self.vehicle.cp.driver:refreshHUD()
end

function DriverModeSetting:setAIDriver()
	courseplay.infoVehicle(self.vehicle, 'Setting mode %d', self:get())
	courseplay:setAIDriver(self.vehicle, self:get())
end

function DriverModeSetting:resetToDefault(noEventSend)
	self:set(self.defaultMode,noEventSend)
end

function DriverModeSetting:getDefaultMode()
	return self.defaultMode
end

function DriverModeSetting:getValidModes()
	return self.validModes
end

function DriverModeSetting:checkAndSetValidValue(new)
	if courseplay:getIsToolCombiValidForCpMode(self.vehicle,new) then 
		return SettingList.checkAndSetValidValue(self,new)
	else 
		return self.defaultMode
	end
end

function DriverModeSetting:isDisabled()
	return not self.vehicle.cp.canSwitchMode or self.vehicle:getIsCourseplayDriving() 
end

--- Set the next value
function DriverModeSetting:setNext()
	if not self:isDisabled() then
		local targetMode = self.current
		repeat
			targetMode = SettingList.checkAndSetValidValue(self, targetMode + 1)
		until courseplay:getIsToolCombiValidForCpMode(self.vehicle, self.values[targetMode])
		self:setToIx(targetMode)
	end
end

--- Set the previous value
function DriverModeSetting:setPrevious()
	if not self:isDisabled() then
		local targetMode = self.current
		repeat
			targetMode = SettingList.checkAndSetValidValue(self, targetMode - 1)
		until courseplay:getIsToolCombiValidForCpMode(self.vehicle, self.values[targetMode])
		self:setToIx(targetMode)
	end
end

--- AutoDrive mode setting
---@class AutoDriveModeSetting : SettingList
AutoDriveModeSetting = CpObject(SettingList)

-- How to use AutoDrive
AutoDriveModeSetting.NO_AUTODRIVE			= 0  -- AutoDrive not found
AutoDriveModeSetting.DONT_USE				= 1  -- Don't use AutoDrive
AutoDriveModeSetting.UNLOAD_OR_REFILL 		= 2  -- Use AutoDrive for unload and refill
AutoDriveModeSetting.PARK 					= 3  -- Use AutoDrive to park vehicle after work is done
AutoDriveModeSetting.UNLOAD_OR_REFILL_PARK 	= 4  -- Use AutoDrive for unload and refill and park after work is done

function AutoDriveModeSetting:init(vehicle)
	SettingList.init(self, 'autoDriveMode', 'COURSEPLAY_AUTODRIVE_MODE', '', vehicle,
		{
			AutoDriveModeSetting.DONT_USE,
			AutoDriveModeSetting.UNLOAD_OR_REFILL,
			AutoDriveModeSetting.PARK,
			AutoDriveModeSetting.UNLOAD_OR_REFILL_PARK,
		},
		{
			'COURSEPLAY_AUTODRIVE_DONT_USE',
			'COURSEPLAY_AUTODRIVE_UNLOAD_OR_REFILL',
			'COURSEPLAY_AUTODRIVE_PARK',
			'COURSEPLAY_AUTODRIVE_UNLOAD_OR_REFILL_PARK',
		})
	self:update()
end

function AutoDriveModeSetting:next()
	courseplay.debugVehicle(courseplay.DBG_UNCATEGORIZED, self.vehicle, 'AutoDrive mode: %d', self.vehicle.cp.settings.autoDriveMode:get())
	SettingList.next(self)
end

function AutoDriveModeSetting:isAutoDriveAvailable()
	return self.vehicle.spec_autodrive and self.vehicle.spec_autodrive.StartDriving
end

function AutoDriveModeSetting:update()
	if self.vehicle.spec_autodrive and self.vehicle.spec_autodrive.GetParkDestination then
		local parkDestination = self.vehicle.spec_autodrive:GetParkDestination(self.vehicle)
		if parkDestination and #self.values == 2 then
			-- add park options when the available
			table.insert(self.values, AutoDriveModeSetting.PARK)
			table.insert(self.values, AutoDriveModeSetting.UNLOAD_OR_REFILL_PARK)
			table.insert(self.texts, 'COURSEPLAY_AUTODRIVE_PARK')
			table.insert(self.texts, 'COURSEPLAY_AUTODRIVE_UNLOAD_OR_REFILL_PARK')
		elseif not parkDestination and #self.values == 4 then
			-- remove park options if they are on our list but are not available
			table.remove(self.values, 3)
			table.remove(self.values, 3)
			table.remove(self.texts, 3)
			table.remove(self.texts, 3)
		end
		if self.valueFromXml then
			self:set(self.valueFromXml)
			self.valueFromXml = nil
		end
	end
end

function AutoDriveModeSetting:useForUnloadOrRefill()
	return self:is(AutoDriveModeSetting.UNLOAD_OR_REFILL) or self:is(AutoDriveModeSetting.UNLOAD_OR_REFILL_PARK)
end

function AutoDriveModeSetting:useForParkVehicle()
	return self:is(AutoDriveModeSetting.PARK) or self:is(AutoDriveModeSetting.UNLOAD_OR_REFILL_PARK)
end

function AutoDriveModeSetting:isDisabled()
	return not self.vehicle.cp.canDrive or not self.vehicle.spec_autodrive
end

--- Starting point setting (at which waypoint should the vehicle start the course)
---@class StartingPointSetting : SettingList
StartingPointSetting = CpObject(SettingList)

StartingPointSetting.START_AT_NEAREST_POINT = 1 -- nearest waypoint regardless of direction
StartingPointSetting.START_AT_FIRST_POINT   = 2 -- first waypoint
StartingPointSetting.START_AT_CURRENT_POINT = 3 -- current waypoint
StartingPointSetting.START_AT_NEXT_POINT    = 4 -- nearest waypoint with approximately same direction as vehicle
StartingPointSetting.START_WITH_UNLOAD      = 5 -- start with unloading the combine (only for CombineUnloadAIDriver)
StartingPointSetting.START_COLLECTING_BALES = 6 -- start with unloading the combine (only for CombineUnloadAIDriver)
StartingPointSetting.START_AT_LAST_POINT	= 7 -- last waypoint for all BunkerSiloAIDrivers (is visible as first wp in the hud)

function StartingPointSetting:init(vehicle, driverModeSetting)
	self.driverModeSetting = driverModeSetting
	local values, texts = self:getValuesForMode(driverModeSetting:get())
	SettingList.init(self, 'startingPoint',
		'COURSEPLAY_START_AT_POINT', 'COURSEPLAY_START_AT_POINT', vehicle, values, texts)
end

-- TODO: this should probably part of the specific driver mode?
function StartingPointSetting:getValuesForMode(mode)
	if mode == courseplay.MODE_COMBI or mode == courseplay.MODE_OVERLOADER then
		return {
			StartingPointSetting.START_AT_NEAREST_POINT,
			StartingPointSetting.START_AT_FIRST_POINT  ,
			StartingPointSetting.START_AT_CURRENT_POINT,
			StartingPointSetting.START_AT_NEXT_POINT,
			StartingPointSetting.START_WITH_UNLOAD
		},
		{
			"COURSEPLAY_NEAREST_POINT",
			"COURSEPLAY_FIRST_POINT"  ,
			"COURSEPLAY_CURRENT_POINT",
			"COURSEPLAY_NEXT_POINT",
			"COURSEPLAY_UNLOAD",
		}
	elseif mode == courseplay.MODE_BALE_COLLECTOR then
		return {
			StartingPointSetting.START_AT_NEAREST_POINT,
			StartingPointSetting.START_AT_FIRST_POINT  ,
			StartingPointSetting.START_AT_NEXT_POINT,
			StartingPointSetting.START_COLLECTING_BALES
		},
		{
			"COURSEPLAY_NEAREST_POINT",
			"COURSEPLAY_FIRST_POINT"  ,
			"COURSEPLAY_NEXT_POINT",
			"COURSEPLAY_COLLECT_BALES",
		}
		--- TODO: find a good solution to this problem.
	elseif mode == courseplay.MODE_SHOVEL_FILL_AND_EMPTY or mode == courseplay.MODE_BUNKERSILO_COMPACTER then 
		return {
			StartingPointSetting.START_AT_NEAREST_POINT,
			StartingPointSetting.START_AT_LAST_POINT, --- this is an hack to have the BunkerSiloAIDriver drive to the last wp and then start normal.
			StartingPointSetting.START_AT_CURRENT_POINT,
			StartingPointSetting.START_AT_NEXT_POINT,
		},
		{
			"COURSEPLAY_NEAREST_POINT",
			"COURSEPLAY_FIRST_POINT"  ,
			"COURSEPLAY_CURRENT_POINT",
			"COURSEPLAY_NEXT_POINT",
		}
	else
		return {
			StartingPointSetting.START_AT_NEAREST_POINT,
			StartingPointSetting.START_AT_FIRST_POINT  ,
			StartingPointSetting.START_AT_CURRENT_POINT,
			StartingPointSetting.START_AT_NEXT_POINT,
		},
		{
			"COURSEPLAY_NEAREST_POINT",
			"COURSEPLAY_FIRST_POINT"  ,
			"COURSEPLAY_CURRENT_POINT",
			"COURSEPLAY_NEXT_POINT",
		}
	end
end

function StartingPointSetting:checkAndSetValidValue(new)
	-- make sure we always have a valid set for the current mode
	self.values, self.texts = self:getValuesForMode(self.driverModeSetting:get())
	return SettingList.checkAndSetValidValue(self, new)
end

function StartingPointSetting:isDisabled() 
	return self.vehicle:getIsCourseplayDriving() or not self.vehicle.cp.canDrive
end

--- Pathfinder parameters settings (read from the XML, may be added to the UI later when needed):
---

--- The hybrid A* algorithm's default delta angle at the goal is 6 degrees, meaning the goal node must
--- be within this angle for the goal to be considered as reached. This may lead to very long or completely
--- unsuccessful path finding if the goal is close to an obstacle for example.
--- We'll therefore start relaxing this criteria so bigger angle differences are acceptable as the pathfinder
--- number of unsuccessful iterations grow.
---
--- This is a factor at which we increase the goal angle difference, a value of 0 won't change it.
---@class DeltaAngleRelaxfactor
DeltaAngleRelaxFactor = CpObject(FloatSetting)
function DeltaAngleRelaxFactor:init()
	IntSetting.init(self, 'deltaAngleRelaxFactor', 'DeltaAngleRelaxFactor',
			'Factor to decrease pathfinder accuracy with unsuccessful iterations')
	self:set(10)
end

--- Maximum value of the delta angle at the goal in radians, see above.
---@class MaxDeltaAngleAtGoal
MaxDeltaAngleAtGoal = CpObject(FloatSetting)
function MaxDeltaAngleAtGoal:init()
	IntSetting.init(self, 'maxDeltaAngleAtGoal', 'MaxDeltaAngleAtGoal',
			'Maximum angle difference allowed at goal')
	self:set(math.pi / 4)
end

--- Implement raise/lower  setting
---@class ImplementRaiseLowerTimeSetting : SettingList
ImplementRaiseLowerTimeSetting = CpObject(SettingList)

-- Raise or lower implements early or late
-- implement raised when the front marker reaches the end of the area to be worked
-- implement lowered when the front marker reaches the end of the area to be worked
ImplementRaiseLowerTimeSetting.EARLY	= 1
-- implement raised when the back marker reaches the start of the area to be worked
-- implement lowered when the back marker reaches the start of the area to be worked
ImplementRaiseLowerTimeSetting.LATE		= 2

function ImplementRaiseLowerTimeSetting:init(vehicle, name, label, tooltip)
	SettingList.init(self,  name, label, tooltip, vehicle,
		{
			ImplementRaiseLowerTimeSetting.EARLY,
			ImplementRaiseLowerTimeSetting.LATE,
		},
		{
			'COURSEPLAY_IMPLEMENT_RAISE_LOWER_EARLY',
			'COURSEPLAY_IMPLEMENT_RAISE_LOWER_LATE',
		})
end

---@class ImplementRaiseTimeSetting : ImplementRaiseLowerTimeSetting
ImplementRaiseTimeSetting = CpObject(ImplementRaiseLowerTimeSetting)
function ImplementRaiseTimeSetting:init(vehicle)
	ImplementRaiseLowerTimeSetting.init(self, vehicle, 'implementRaiseTime', 'COURSEPLAY_IMPLEMENT_RAISE_TIME', 'COURSEPLAY_IMPLEMENT_RAISE_TIME_TOOLTIP')
	self:set(ImplementRaiseLowerTimeSetting.EARLY)
end

---@class ImplementLowerTimeSetting : ImplementRaiseLowerTimeSetting
ImplementLowerTimeSetting = CpObject(ImplementRaiseLowerTimeSetting)
function ImplementLowerTimeSetting:init(vehicle)
	ImplementRaiseLowerTimeSetting.init(self, vehicle, 'implementLowerTime', 'COURSEPLAY_IMPLEMENT_LOWER_TIME', 'COURSEPLAY_IMPLEMENT_LOWER_TIME_TOOLTIP')
	self:set(ImplementRaiseLowerTimeSetting.LATE)
end

---@class FoldImplementAtEndSetting : BooleanSetting
FoldImplementAtEndSetting = CpObject(BooleanSetting)
function FoldImplementAtEndSetting:init(vehicle)
	BooleanSetting.init(self, 'foldImplementAtEnd', 'COURSEPLAY_SHOULD_FOLD_IMPLEMENT',
		'COURSEPLAY_SHOULD_FOLD_IMPLEMENT_TOOLTIP', vehicle)
	self:set(true)
end

--- Return to first point after finishing fieldwork
---@class ReturnToFirstPointSetting : SettingList
ReturnToFirstPointSetting = CpObject(SettingList)
ReturnToFirstPointSetting.DEACTIVATED = 0
ReturnToFirstPointSetting.RETURN_TO_START = 1
ReturnToFirstPointSetting.RELEASE_DRIVER = 2
ReturnToFirstPointSetting.RETURN_TO_START_AND_RELEASE_DRIVER = 3
function ReturnToFirstPointSetting:init(vehicle, driverModeSetting)
	self.driverModeSetting = driverModeSetting
	SettingList.init(self, 'returnToFirstPoint', 'COURSEPLAY_RETURN_TO_FIRST_POINT',
		'COURSEPLAY_RETURN_TO_FIRST_POINT', vehicle,
		{
			self.DEACTIVATED,
			self.RETURN_TO_START,
			self.RELEASE_DRIVER,
			self.RETURN_TO_START_AND_RELEASE_DRIVER	
			},
		{
			"COURSEPLAY_DEACTIVATED",
			"COURSEPLAY_ACTIVATED",
			"COURSEPLAY_RETURN_TO_FIRST_POINT_RELEASE_DRIVER",
			"COURSEPLAY_RETURN_TO_FIRST_POINT_RETURN_TO_START_AND_RELEASE_DRIVER"
		})
end

function ReturnToFirstPointSetting:isDisabled()
	return not self.vehicle.cp.canDrive or self.driverModeSetting:get() == courseplay.MODE_BALE_COLLECTOR
end

function ReturnToFirstPointSetting:isReturnToStartActive()
	return self:get() == self.RETURN_TO_START or self:get() == self.RETURN_TO_START_AND_RELEASE_DRIVER
end

function ReturnToFirstPointSetting:isReleaseDriverActive()
	return self:get() == self.RELEASE_DRIVER or self:get() == self.RETURN_TO_START_AND_RELEASE_DRIVER
end

--- Generic offset setting with increment/decrement
---@class OffsetSetting : FloatSetting
OffsetSetting = CpObject(FloatSetting)
function OffsetSetting:init(name, label, toolTip, vehicle, value)
	FloatSetting.init(self, name, label, toolTip, vehicle, value)
	self.disabledText = "..."
end

-- increment/decrement offset
function OffsetSetting:changeBy(changeBy,noEventSend)
	self:set(self.value+changeBy,noEventSend)
end

--- Currently all hud settings use changeByX() or toggle() should consolidate this. 
--- Also apply the change rate of 0.1 here, so the courseplay:..() function are not needed anymore.
function OffsetSetting:changeByX(changeBy,noEventSend)
	self:changeBy(changeBy*0.1,noEventSend)
end

function OffsetSetting:set(value,noEventSend)
	value = courseplay:round(value, 1)
	if abs(value) < 0.1 then
		value = 0
	end
	FloatSetting.set(self,value,noEventSend)
end

function OffsetSetting:getTextX()
	if self.value ~= 0 then
		return ('%.1f%s (%s)'):format(
				abs(self.value),
				courseplay:loc('COURSEPLAY_UNIT_METER'),
				courseplay:loc(self.value > 0 and 'COURSEPLAY_RIGHT' or 'COURSEPLAY_LEFT'))
	else
		return self.disabledText
	end
end

function OffsetSetting:getTextZ()
	if self.value ~= 0 then
		return ('%.1f%s (%s)'):format(
				abs(self.value),
				courseplay:loc('COURSEPLAY_UNIT_METER'),
				courseplay:loc(self.value > 0 and 'COURSEPLAY_FRONT' or 'COURSEPLAY_BACK'))
	else
		return self.disabledText
	end
end

--- TODO: Consider combing the left/right and front/back offset settings into one.
--- 	  Find a better naming convention, then offsetX or offsetZ. 

--- Left/Right offset for tools.
--- Right side is positive and left side is negative.
---@class ToolOffsetXSetting : OffsetSetting
ToolOffsetXSetting = CpObject(OffsetSetting)
function ToolOffsetXSetting:init(vehicle)
	OffsetSetting.init(self, 'toolOffsetX', 'COURSEPLAY_TOOL_OFFSET_X', 'COURSEPLAY_TOOL_OFFSET_X', vehicle, 0)
	self.SET_TO_CONFIGURED_VALUE_EVENT = self:registerFunctionEvent(self.setToConfiguredValue) 
end

--- Set to the configured value if exists, 0 otherwise
function ToolOffsetXSetting:setToConfiguredValue()
	-- set the auto tool offset if exists or 0
	if g_server then 
		self:set(g_vehicleConfigurations:getRecursively(self.vehicle, self.name) or 0)
	else 
		self:raiseEvent(self.SET_TO_CONFIGURED_VALUE_EVENT)
	end
end

function ToolOffsetXSetting:getText()
	return self:getTextX()
end

--- Front/Back offset for tools.
---@class ToolOffsetZSetting : OffsetSetting
--- Front is positive and back is negative.
ToolOffsetZSetting = CpObject(OffsetSetting)
function ToolOffsetZSetting:init(vehicle)
	OffsetSetting.init(self, 'toolOffsetZ', 'COURSEPLAY_TOOL_OFFSET_Z', 'COURSEPLAY_TOOL_OFFSET_Z', vehicle, 0)
end

function ToolOffsetZSetting:getText()
	return self:getTextZ()
end

--- Left/Right offset for loading/unloading at trigger.
--- Right side is positive and left side is negative.
---@class LoadUnloadOffsetXSetting : OffsetSetting
LoadUnloadOffsetXSetting = CpObject(OffsetSetting)
function LoadUnloadOffsetXSetting:init(vehicle)
	OffsetSetting.init(self, 'loadUnloadOffsetX', 'COURSEPLAY_LOAD_UNLOAD_OFFSET_X', 'COURSEPLAY_LOAD_UNLOAD_OFFSET_X', vehicle, 0)
end
function LoadUnloadOffsetXSetting:getText()
	return self:getTextX()
end

--- Front/Back offset for loading/unloading at trigger.
--- Front is positive and back is negative.
---@class LoadUnloadOffsetZSetting : OffsetSetting
LoadUnloadOffsetZSetting = CpObject(OffsetSetting)
function LoadUnloadOffsetZSetting:init(vehicle)
	OffsetSetting.init(self, 'loadUnloadOffsetZ', 'COURSEPLAY_LOAD_UNLOAD_OFFSET_Z', 'COURSEPLAY_LOAD_UNLOAD_OFFSET_Z', vehicle, 0)
end

function LoadUnloadOffsetZSetting:getText()
	return self:getTextZ()
end

--- Left/Right offset for unloading at a combine.
--- Right side is positive and left side is negative.
---@class CombineOffsetXSetting : OffsetSetting
CombineOffsetXSetting = CpObject(OffsetSetting)
function CombineOffsetXSetting:init(vehicle)
	OffsetSetting.init(self, 'combineOffsetX', 'COURSEPLAY_COMBINE_OFFSET_HORIZONTAL', 'COURSEPLAY_COMBINE_OFFSET_HORIZONTAL', vehicle, 0)
	self.disabledText = courseplay:loc("COURSEPLAY_AUTOMATIC")
end

function CombineOffsetXSetting:getText()
	return self:getTextX()
end

--- Front/Back offset for unloading at a combine.
--- Front is positive and back is negative.
---@class CombineOffsetZSetting : OffsetSetting
CombineOffsetZSetting = CpObject(OffsetSetting)
function CombineOffsetZSetting:init(vehicle)
	OffsetSetting.init(self, 'combineOffsetZ', 'COURSEPLAY_COMBINE_OFFSET_VERTICAL', 'COURSEPLAY_COMBINE_OFFSET_VERTICAL', vehicle, 0)
	self.disabledText = courseplay:loc("COURSEPLAY_AUTOMATIC")
end

function CombineOffsetZSetting:getText()
	return self:getTextZ()
end


---@class TurnDiameterSetting : OffsetSetting 
TurnDiameterSetting = CpObject(OffsetSetting)
--- Only use the manuel tool turn diameter in these driver modes.
TurnDiameterSetting.changableDriverModes = {
	[DriverModeSetting.COMBINE_UNLOAD] = true,
	[DriverModeSetting.OVERLOAD] = true,
	[DriverModeSetting.FILLABLE_FIELDWORK] = true,
	[DriverModeSetting.FIELDWORK] = true,
	[DriverModeSetting.BALE_COLLECTOR] = true
}
function TurnDiameterSetting:init(vehicle,driverMode)
	OffsetSetting.init(self,"turnDiameter","COURSEPLAY_TURN_RADIUS","COURSEPLAY_TURN_RADIUS",vehicle,0)
	self.disabledText = courseplay:loc("COURSEPLAY_AUTOMATIC")
	self.autoTurnDiameter = FloatSetting("autoTurnDiameter",nil,nil,vehicle, 0)
	self.driverMode = driverMode
	self.AUTO_TURN_DIAMETER_EVENT = self:registerFloatEvent(self.setAutoTurnDiameterFromNetwork)
end

function TurnDiameterSetting:getAutoTurnDiameter()
	local turnDiameter = self.vehicle.cp.vehicleTurnRadius * 2
	local toolTurnDiameter = AIDriverUtil.getTurningRadius(self.vehicle) * 2

	
	-- Check if we have worktools and if we are in a valid mode
	if self:isChangeable() then
		if toolTurnDiameter > turnDiameter then
			turnDiameter = toolTurnDiameter
		end
	end

	return turnDiameter
end

function TurnDiameterSetting:validateCurrentValue()
	if g_server then 
		self:updateAutoTurnDiameter()
	end
end

function TurnDiameterSetting:updateAutoTurnDiameter()
	local value = self:getAutoTurnDiameter()
	self.autoTurnDiameter:set(value,true)
	self:raiseEvent(self.AUTO_TURN_DIAMETER_EVENT,value)
end

function TurnDiameterSetting:onReadStream(stream)
	OffsetSetting.onReadStream(self,stream)
	self.autoTurnDiameter:onReadStream(stream)
end

function TurnDiameterSetting:onWriteStream(stream)
	OffsetSetting.onWriteStream(self,stream)
	self.autoTurnDiameter:onWriteStream(stream)
end

function TurnDiameterSetting:get()
	if self:isAutomaticActive() then 
		return self.autoTurnDiameter:get()
	end
	return self.value
end

function TurnDiameterSetting:setAutoTurnDiameterFromNetwork(value)
	self.autoTurnDiameter:set(value,true)
end

function TurnDiameterSetting:getText()
	if self.value ~= 0 then
		return ('%.1f%s'):format(
				self.value,
				courseplay:loc('COURSEPLAY_UNIT_METER'))
	else
		return ('%s(%.1f%s)'):format(
			self.disabledText,
			self:get(),
			courseplay:loc('COURSEPLAY_UNIT_METER'))
	end
end

function TurnDiameterSetting:isAutomaticActive()
	return self.value == 0 or not self:isChangeable()
end

function TurnDiameterSetting:set(value,noEventSend)
	value = courseplay:round(value, 1)
	if value < 0.1 then
		value = 0
	end
	FloatSetting.set(self,value,noEventSend)
end

function TurnDiameterSetting:changeByX(changeBy,noEventSend)
	self:changeBy(changeBy,noEventSend)
end

function TurnDiameterSetting:isChangeable()
	local driverModeValue = self.driverMode:get()
	return self.changableDriverModes[driverModeValue]
end

--- Setting to select a field
---@class FieldNumberSetting : SettingList
FieldNumberSetting = CpObject(SettingList)
FieldNumberSetting.Settings = {}
function FieldNumberSetting:init(name, label, toolTip, vehicle)
	local values, texts = self:loadFields()
	SettingList.init(self, name, label, toolTip, vehicle, values, texts)
	--- Store all instances of this setting in a global table,
	--- for direct access to update their manual tool positions.	
	table.insert(FieldNumberSetting.Settings,self)
end

function FieldNumberSetting:loadFields()
	local values = {}
	local texts = {}
	for fieldNumber, _ in pairs( courseplay.fields.fieldData ) do
		table.insert(values, fieldNumber)
	end
	-- numeric sort first
	table.sort( values, function( a, b ) return a < b end )
	-- then convert to text
	for _, fieldNumber in ipairs(values) do
		table.insert(texts, CpFieldUtil.getFieldName(fieldNumber))
	end
	return values, texts
end

--- Refresh current field numbers (as they may change when fields are bought/sold)
function FieldNumberSetting:refresh()
	self.values, self.texts = self:loadFields()
	-- if a value was loaded previously from XML and this is the first refresh after that, take over that
	-- value now. This is because at game load the fields are not known yet.
	if self.valueFromXml then
		self:set(self.valueFromXml)
		self.valueFromXml = nil
	end
	self.current = math.min(self.current, #self.values)
end

--- Refreshes all field number settings on start,
--- as clients might have a corrupted version.
function FieldNumberSetting.refreshOnStart()
	for _,setting in pairs(FieldNumberSetting.Settings) do 
		setting:refresh()
	end
end

-- see above, refresh in case it was not initialized
function FieldNumberSetting:get()
	if #self.values == 0 or self.current > #self.values then
		self:refresh()
	end
	return SettingList.get(self)
end

-- see above, refresh in case it was not initialized
function FieldNumberSetting:getText()
	if #self.values == 0 or self.current > #self.values then
		self:refresh()
	end
	return SettingList.getText(self)
end

function FieldNumberSetting:changeByX(x)
	self:refresh()
	SettingList.changeByX(self, x)
end

--- Field to collect the bales from
---@class BaleCollectionFieldSetting : FieldNumberSetting
BaleCollectionFieldSetting = CpObject(FieldNumberSetting)
function BaleCollectionFieldSetting:init(vehicle)
	FieldNumberSetting.init(self, 'baleCollectionField', 'COURSEPLAY_FIELD', 'COURSEPLAY_FIELD', vehicle)
end

--- Search combine on field
---@class SearchCombineOnFieldSetting : FieldNumberSetting
SearchCombineOnFieldSetting = CpObject(FieldNumberSetting)
function SearchCombineOnFieldSetting:init(vehicle)
	FieldNumberSetting.init(self, 'searchCombineOnField',
		'COURSEPLAY_SEARCH_COMBINE_ON_FIELD', 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD', vehicle)
	self:addNoneSelected()
end

function SearchCombineOnFieldSetting:addNoneSelected()
	-- add value/text for nothing selected
	table.insert(self.values, 1, 0)
	table.insert(self.texts, 1, '--')
end

function SearchCombineOnFieldSetting:refresh()
	local current = self.current
	FieldNumberSetting.refresh(self)
	self:addNoneSelected()
	self.current = math.min(current, #self.values)
end

function SearchCombineOnFieldSetting:changeByX(x)
	if courseplay.fields.numAvailableFields == 0 or not self.vehicle.cp.searchCombineAutomatically then
		self:set(0)
		return
	end
	return FieldNumberSetting.changeByX(self,x)
end

--- SelectedCombineToUnload on field
---@class SelectedCombineToUnloadSetting : SettingList
SelectedCombineToUnloadSetting = CpObject(SettingList)

function SelectedCombineToUnloadSetting:init()
	self.name = 'selectedCombineToUnload'
	self.label = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
	self.tooltip = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
	self.xmlKey = 'selectedCombineToUnload'
	self.xmlAttribute = '#combineId'
	self.current = 0
	self:refresh()
end

function SelectedCombineToUnloadSetting:refresh()
	self.values = {}
	for combine,_ in pairs (g_combineUnloadManager.combines) do
		table.insert(self.values,combine)
	end
end

function SelectedCombineToUnloadSetting:checkAndSetValidValue(new)
	if new > #self.values then
		return 0
	elseif new < 0 then
		return #self.values
	else
		return new
	end
end



--- Use AI Turns?
---@class UseAITurnsSetting : BooleanSetting
UseAITurnsSetting = CpObject(BooleanSetting)
function UseAITurnsSetting:init(vehicle)
	BooleanSetting.init(self, 'useAITurns', 'COURSEPLAY_USE_AI_TURNS',
		'COURSEPLAY_USE_AI_TURNS_TOOLTIP', vehicle)
end

--- Use pathfinding during turns?
---@class UsePathfindingInTurnsSetting : BooleanSetting
UsePathfindingInTurnsSetting = CpObject(BooleanSetting)
function UsePathfindingInTurnsSetting:init(vehicle)
	BooleanSetting.init(self, 'usePathfindingInTurns', 'COURSEPLAY_USE_PATHFINDING_IN_TURNS',
		'COURSEPLAY_USE_PATHFINDING_IN_TURNS_TOOLTIP', vehicle)
end

--- Allow driving reverse for pathfinding during turns?
---@class AllowReverseForPathfindingInTurnsSetting : BooleanSetting
AllowReverseForPathfindingInTurnsSetting = CpObject(BooleanSetting)
function AllowReverseForPathfindingInTurnsSetting:init(vehicle)
	BooleanSetting.init(self, 'allowReverseForPathfindingInTurns', 'COURSEPLAY_ALLOW_REVERSE_FOR_PATHFINDING_IN_TURNS',
			'COURSEPLAY_ALLOW_REVERSE_FOR_PATHFINDING_IN_TURNS_TOOLTIP', vehicle)
end

---@class PipeAlwaysUnfold : BooleanSetting
PipeAlwaysUnfoldSetting = CpObject(BooleanSetting)
function PipeAlwaysUnfoldSetting:init(vehicle)
	BooleanSetting.init(self, 'pipeAlwaysUnfold', 'COURSEPLAY_PIPE_ALWAYS_UNFOLD',
				'COURSEPLAY_YES_NO_PIPE_ALWAYS_UNFOLD', vehicle)
	-- set default while we are transitioning from the the old setting to this new one
	self:set(false)
end

function PipeAlwaysUnfoldSetting:isDisabled()
	return self.vehicle.cp.driver and not self.vehicle.cp.driver:is_a(CombineAIDriver)
end


---@class SowingMachineFertilizerEnabled : BooleanSetting
SowingMachineFertilizerEnabled = CpObject(BooleanSetting)
function SowingMachineFertilizerEnabled:init(vehicle)
	BooleanSetting.init(self, 'sowingMachineFertilizerEnabled', 'COURSEPLAY_FERTILIZE_OPTION',
				'COURSEPLAY_YES_NO_FERTILIZE_OPTION', vehicle)
	-- set default while we are transitioning from the the old setting to this new one
	self:set(true)
end

function SowingMachineFertilizerEnabled:isDisabled()
	return self.vehicle.cp.driver and not AIDriverUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine)
end

---@class StrawSwathSetting : SettingList
StrawSwathSetting = CpObject(SettingList)
StrawSwathSetting.OFF = 0
StrawSwathSetting.ON = 1
StrawSwathSetting.ONLY_CENTER = 2
function StrawSwathSetting:init(vehicle)
	SettingList.init(self, 'strawSwath', 'COURSEPLAY_STRAW_ON_HEADLAND','COURSEPLAY_YES_NO_STRAW_ON_HEADLAND', vehicle,
		{StrawSwathSetting.OFF,StrawSwathSetting.ON,StrawSwathSetting.ONLY_CENTER},
		{'COURSEPLAY_DEACTIVATED','COURSEPLAY_ACTIVATED','COURSEPLAY_STRAW_ON_ONLY_CENTER'})
	-- set default while we are transitioning from the the old setting to this new one
	self:set(1)
end

function StrawSwathSetting:isDisabled()
	return self.vehicle.cp.driver and not self.vehicle.cp.driver:is_a(CombineAIDriver)
end

---@class RidgeMarkersAutomatic : BooleanSetting
RidgeMarkersAutomatic = CpObject(BooleanSetting)
function RidgeMarkersAutomatic:init(vehicle)
	BooleanSetting.init(self, 'ridgeMarkersAutomatic', 'COURSEPLAY_RIDGEMARKERS',
			'COURSEPLAY_YES_NO_RIDGEMARKERS', vehicle)
	self:set(false)
end

function RidgeMarkersAutomatic:isDisabled()
	return self.vehicle.cp.driver and not AIDriverUtil.hasAIImplementWithSpecialization(self.vehicle, RidgeMarker)
end

---@class KeepCurrentSteering : BooleanSetting
KeepCurrentSteering = CpObject(BooleanSetting)
function KeepCurrentSteering:init(vehicle)
	BooleanSetting.init(self, 'keepCurrentSteering', 'COURSEPLAY_KEEP_CURRENT_STEERING',
			'COURSEPLAY_KEEP_CURRENT_STEERING_TOOLTIP', vehicle)
	self:set(false)
end

---@class EnableVisualWaypointsTemporary : BooleanSetting
EnableVisualWaypointsTemporary = CpObject(BooleanSetting)
function EnableVisualWaypointsTemporary:init(vehicle)
	BooleanSetting.init(self, 'enableVisualWaypointsTemporary', 'COURSEPLAY_ENABLE_VISUAL_WAYPOINTS_TEMPORARY',
				'COURSEPLAY_ENABLE_VISUAL_WAYPOINTS_TEMPORARY_TOOLTIP', vehicle)
	-- set default while we are transitioning from the the old setting to this new one
	self:set(false)
end

---@class EnableOpenHudWithMouseVehicle : BooleanSetting
EnableOpenHudWithMouseVehicle = CpObject(BooleanSetting)
function EnableOpenHudWithMouseVehicle:init(vehicle)
	BooleanSetting.init(self, 'enableOpenHudWithMouseVehicle', 'COURSEPLAY_ENABLE_OPEN_HUD_WITH_MOUSE_VEHICLE',
				'COURSEPLAY_YES_NO_ENABLE_OPEN_HUD_WITH_MOUSE_VEHICLE', vehicle)
	-- set default while we are transitioning from the the old setting to this new one
	self:set(true)
end

---@class SelfUnloadSetting : BooleanSetting
SelfUnloadSetting = CpObject(BooleanSetting)
function SelfUnloadSetting:init(vehicle)
	BooleanSetting.init(self, 'selfUnload', 'COURSEPLAY_SELF_UNLOAD', 'COURSEPLAY_SELF_UNLOAD_TOOLTIP', vehicle)
end

function SelfUnloadSetting:isDisabled()
	return self.vehicle.cp.driver and not self.vehicle.cp.driver:is_a(CombineAIDriver)
end


---@class SymmetricLaneChangeSetting : BooleanSetting
SymmetricLaneChangeSetting = CpObject(BooleanSetting)
function SymmetricLaneChangeSetting:init(vehicle)
	BooleanSetting.init(self, 'symmetricLaneChange', 'COURSEPLAY_SYMMETRIC_LANE_CHANGE', 'COURSEPLAY_SYMMETRIC_LANE_CHANGE', vehicle)
end

---@class StopForUnloadSetting : BooleanSetting
StopForUnloadSetting = CpObject(BooleanSetting)
function StopForUnloadSetting:init(vehicle)
	BooleanSetting.init(self, 'stopForUnload', 'COURSEPLAY_STOP_DURING_UNLOADING', 'COURSEPLAY_STOP_DURING_UNLOADING', vehicle)
end

function StopForUnloadSetting:checkAndSetValidValue(new)
	if courseplay:isChopper(self.vehicle) then
		-- can't activate for choppers
		return 1
	end
	return BooleanSetting.checkAndSetValidValue(self, new)
end

---@class AllowUnloadOnFirstHeadlandSetting : BooleanSetting
AllowUnloadOnFirstHeadlandSetting = CpObject(BooleanSetting)
function AllowUnloadOnFirstHeadlandSetting:init(vehicle)
	BooleanSetting.init(self, 'allowUnloadOnFirstHeadland', 'COURSEPLAY_ALLOW_UNLOAD_ON_FIRST_HEADLAND',
			'COURSEPLAY_ALLOW_UNLOAD_ON_FIRST_HEADLAND_TOOLTIP', vehicle)
	self:set(true)
end

function AllowUnloadOnFirstHeadlandSetting:isDisabled()
	return self.vehicle.cp.driver and not self.vehicle.cp.driver:is_a(CombineAIDriver)
end

-----------------------------------------------------------------------

---@class StopAtEndSetting : BooleanSetting
StopAtEndSetting = CpObject(BooleanSetting)
function StopAtEndSetting:init(vehicle, driverModeSetting)
	self.driverModeSetting = driverModeSetting
	BooleanSetting.init(self, 'stopAtEnd', 'COURSEPLAY_STOP_AT_LAST_POINT', 'COURSEPLAY_STOP_AT_LAST_POINT', vehicle)
	self:set(true)
end

function StopAtEndSetting:isDisabled()
	return not self.vehicle.cp.canDrive or not self.driverModeSetting:get() == courseplay.MODE_TRANSPORT
end

---@class AutomaticCoverHandlingSetting : BooleanSetting
AutomaticCoverHandlingSetting = CpObject(BooleanSetting)
function AutomaticCoverHandlingSetting:init(vehicle)
	BooleanSetting.init(self, 'automaticCoverHandling', 'COURSEPLAY_COVER_HANDLING', 'COURSEPLAY_COVER_HANDLING', vehicle)
	self:set(true)
end

---@class DriverPriorityUseFillLevelSetting : BooleanSetting
DriverPriorityUseFillLevelSetting = CpObject(BooleanSetting)
function DriverPriorityUseFillLevelSetting:init(vehicle)
	BooleanSetting.init(self, 'driverPriorityUseFillLevel', 'COURSEPLAY_UNLOADING_DRIVER_PRIORITY', 'COURSEPLAY_UNLOADING_DRIVER_PRIORITY', vehicle, {'COURSEPLAY_DISTANCE','COURSEPLAY_FILLEVEL'})
	self:set(false)
end

---@class UseRecordingSpeedSetting : BooleanSetting
UseRecordingSpeedSetting = CpObject(BooleanSetting)
function UseRecordingSpeedSetting:init(vehicle)
	BooleanSetting.init(self, 'useRecordingSpeed', 'COURSEPLAY_MAX_SPEED_MODE', 'COURSEPLAY_MAX_SPEED_MODE', vehicle, {'COURSEPLAY_MAX_SPEED_MODE_MAX','COURSEPLAY_MAX_SPEED_MODE_RECORDING'})
	self:set(true)
end

---@class WarningLightsModeSetting : SettingList
WarningLightsModeSetting = CpObject(SettingList)
WarningLightsModeSetting.WARNING_LIGHTS_NEVER = 0;
WarningLightsModeSetting.WARNING_LIGHTS_BEACON_ON_STREET = 1;
WarningLightsModeSetting.WARNING_LIGHTS_BEACON_HAZARD_ON_STREET = 2;
WarningLightsModeSetting.WARNING_LIGHTS_BEACON_ALWAYS = 3;

function WarningLightsModeSetting:init(vehicle)
	SettingList.init(self, 'warningLightsMode', 'COURSEPLAY_WARNING_LIGHTS', 'COURSEPLAY_WARNING_LIGHTS', vehicle,
		{ 
			WarningLightsModeSetting.WARNING_LIGHTS_NEVER,
			WarningLightsModeSetting.WARNING_LIGHTS_BEACON_ON_STREET,
			WarningLightsModeSetting.WARNING_LIGHTS_BEACON_HAZARD_ON_STREET,
			WarningLightsModeSetting.WARNING_LIGHTS_BEACON_ALWAYS
		},
		{ 	
			'COURSEPLAY_WARNING_LIGHTS_MODE_0',
			'COURSEPLAY_WARNING_LIGHTS_MODE_1',
			'COURSEPLAY_WARNING_LIGHTS_MODE_2',
			'COURSEPLAY_WARNING_LIGHTS_MODE_3'
		}
		)
	self:set(1)
end

---@class SaveFuelOptionSetting : BooleanSetting
SaveFuelOptionSetting = CpObject(BooleanSetting)
function SaveFuelOptionSetting:init(vehicle)
	BooleanSetting.init(self, 'saveFuelOption', 'COURSEPLAY_FUELSAVEOPTION', 'COURSEPLAY_FUELSAVEOPTION', vehicle)
	self:set(true)
end

---@class AlwaysSearchFuelSetting : BooleanSetting
AlwaysSearchFuelSetting = CpObject(BooleanSetting)
function AlwaysSearchFuelSetting:init(vehicle)
	BooleanSetting.init(self, 'alwaysSearchFuel', 'COURSEPLAY_FUEL_SEARCH_FOR', 'COURSEPLAY_FUEL_SEARCH_FOR', vehicle, {'COURSEPLAY_FUEL_BELOW_20PCT','COURSEPLAY_FUEL_ALWAYS'})
	self:set(false)
end
---@class RealisticDrivingSetting : BooleanSetting
RealisticDrivingSetting = CpObject(BooleanSetting)
function RealisticDrivingSetting:init(vehicle)
	BooleanSetting.init(self, 'useRealisticDriving', 'COURSEPLAY_PATHFINDING', 'COURSEPLAY_PATHFINDING', vehicle)
	self:set(true)
end

---@class DriveUnloadNowSetting : BooleanSetting
DriveUnloadNowSetting = CpObject(BooleanSetting)
function DriveUnloadNowSetting:init(vehicle)
	BooleanSetting.init(self, 'driveUnloadNow', 'COURSEPLAY_DRIVE_NOW', 'COURSEPLAY_DRIVE_NOW', vehicle)
	self:set(false)
end

---@class RequestUnloaderSetting : BooleanSetting
RequestUnloaderSetting = CpObject(BooleanSetting)
function RequestUnloaderSetting:init(vehicle)
	BooleanSetting.init(self, 'requestUnloader', 'COURSEPLAY_DRIVER', 'COURSEPLAY_DRIVER', vehicle, {'COURSEPLAY_REQUEST_UNLOADING_DRIVER','COURSEPLAY_UNLOADING_DRIVER_REQUESTED'})
	self:set(false)
end

---@class KeepUnloadingUntilEndOfRow : BooleanSetting
KeepUnloadingUntilEndOfRow = CpObject(BooleanSetting)
function KeepUnloadingUntilEndOfRow:init(vehicle)
	BooleanSetting.init(self, 'keepUnloadingUntilEndOfRow', 'COURSEPLAY_KEEP_UNLOADING_UNTIL_END_OF_ROW',
			'COURSEPLAY_KEEP_UNLOADING_UNTIL_END_OF_ROW_TOOLTIP', vehicle)
	self:set(false)
end

function KeepUnloadingUntilEndOfRow:isDisabled()
	return self.vehicle.cp.driver and not self.vehicle.cp.driver:is_a(CombineUnloadAIDriver)
end

---@class SiloSelectedFillTypeSetting : LinkedListSetting
SiloSelectedFillTypeSetting = CpObject(LinkedListSetting)
SiloSelectedFillTypeSetting.NetworkTypes = {}
SiloSelectedFillTypeSetting.NetworkTypes.ADD_ELEMENT = 0
SiloSelectedFillTypeSetting.NetworkTypes.DELETE_X = 1
SiloSelectedFillTypeSetting.NetworkTypes.MOVE_UP_X = 2
SiloSelectedFillTypeSetting.NetworkTypes.MOVE_DOWN_X = 3
SiloSelectedFillTypeSetting.NetworkTypes.CHANGE_MAX_FILLLEVEL = 4
SiloSelectedFillTypeSetting.NetworkTypes.CHANGE_RUNCOUNTER = 5
SiloSelectedFillTypeSetting.NetworkTypes.CLEANUP_OLD_FILLTYPES = 6
SiloSelectedFillTypeSetting.NetworkTypes.CHANGE_MIN_FILLEVEL = 7
function SiloSelectedFillTypeSetting:init(vehicle, mode)
	LinkedListSetting.init(self, 'siloSelectedFillType'..mode, 'COURSEPLAY_ADD_FILLTYPE', 'COURSEPLAY_ADD_FILLTYPE', vehicle)
	self.mode = mode
	self.MAX_RUNS = 20
	self.MAX_PERCENT = 100
	self.MIN_PERCENT = 0
	self.runCounterActive = true
	self.MAX_FILLTYPES = 2
	self.disallowedFillTypes = nil
	self.xmlKey = 'siloSelectedFillType'..mode
	self.xmlAttributeSize = '#size'
	self.xmlAttributeRunCounter = '#runCounter'
	self.xmlAttributeFillType = '#fillType'
	self.xmlAttributeMaxFillLevel = '#maxFillLevel'	
	self.xmlAttributeMinFillLevel = '#minFillLevel'	
end

function SiloSelectedFillTypeSetting:getMaxFillTypes()
	return self.MAX_FILLTYPES
end

function SiloSelectedFillTypeSetting:addFilltype()
	if self:isFull() then 
		return
	end
	local supportedFillTypes = {}
	self:getSupportedFillTypes(self.vehicle,supportedFillTypes)
	self:checkSelectedFillTypes(supportedFillTypes)
	if supportedFillTypes then
		g_gui:showSiloDialog({title="Filltype Selection", fillLevels=supportedFillTypes, capacity=100, callback=self.onFillTypeSelection, target=self, hasInfiniteCapacity = true})
	end
end

function SiloSelectedFillTypeSetting:isFull()
	if self:getSize() >= self.MAX_FILLTYPES then 
		return true
	end
end

function SiloSelectedFillTypeSetting:sendEvent(NetworkType, index , value)
	SiloSelectedFillTypeEvent.sendEvent(self.vehicle,self.name,NetworkType, index, value)
end

function SiloSelectedFillTypeSetting:onFillTypeSelection(selectedFillType,noEventSend)
	if selectedFillType and selectedFillType ~= FillType.UNKNOWN then 
		self:addLast(self:fillTypeDataToAdd(selectedFillType))
		if not noEventSend then
			self:sendEvent(self.NetworkTypes.ADD_ELEMENT,nil,selectedFillType)
		end
	end
end  

function SiloSelectedFillTypeSetting:fillTypeDataToAdd(selectedfillType,counter,maxLevel,minLevel)
	local data = nil
	if self.runCounterActive then
		data = {
			fillType = selectedfillType,
			text = g_fillTypeManager:getFillTypeByIndex(selectedfillType).title,
			runCounter = counter or self.MAX_RUNS,
			maxFillLevel = maxLevel or self.MAX_PERCENT,
			minFillLevel = minLevel or self.MIN_PERCENT
		}	
	else
		data = {
			fillType = selectedfillType,
			text = g_fillTypeManager:getFillTypeByIndex(selectedfillType).title,
			maxFillLevel = maxLevel or self.MAX_PERCENT,
			minFillLevel = minLevel or self.MIN_PERCENT
		}	
	end
	return data
end

--deletes not supported fillTypes
function SiloSelectedFillTypeSetting:cleanUpOldFillTypes(noEventSend)
	local supportedFillTypes = {}
	self:getSupportedFillTypes(self.vehicle,supportedFillTypes)
	self:checkSelectedFillTypes(supportedFillTypes,true, noEventSend)
	if not noEventSend then
		self:sendEvent(self.NetworkTypes.CLEANUP_OLD_FILLTYPES)
	end
end

function SiloSelectedFillTypeSetting:validateCurrentValue()
	self:cleanUpOldFillTypes(true)
end


function SiloSelectedFillTypeSetting:checkSelectedFillTypes(supportedFillTypes, cleanUp, noEventSend)
	totalData = self:getData()
	for index,data in ipairs(totalData) do 
		if supportedFillTypes[data.fillType] then --already selected fillTypes disable multi select
			supportedFillTypes[data.fillType]=0
		elseif cleanUp then	--delete not supported fillTypes 
			self:deleteByIndex(index, noEventSend)
			return self:checkSelectedFillTypes(supportedFillTypes, cleanUp, noEventSend)
		end
	end
end 

function SiloSelectedFillTypeSetting:getSupportedFillTypes(object,supportedFillTypes)  
	if object and object.spec_fillUnit and object.getFillUnits then
		if supportedFillTypes ~= nil then 
			for fillUnitIndex, fillUnit in pairs(object:getFillUnits()) do
				for fillType,bool in pairs(object:getFillUnitSupportedFillTypes(fillUnitIndex)) do 
					local found = false
					local specMotor = object.spec_motorized
					--disable motor consumer fillTypes as they get loaded seperatly 
					if specMotor then 
						local consumer = specMotor.consumersByFillType[fillType] 
						if consumer then 
							if consumer.fillUnitIndex == fillUnitIndex then 
								found = true
							end							
						end
					end
					--disabled FillTypes(AIR, sometimes DEF or DIESEL)
					if self.disallowedFillTypes then		
						for _,_fillType in pairs(self.disallowedFillTypes) do 
							if fillType == _fillType then
								found = true
							end
						end
					end	
					--all okay and fillType is new add it to spported
					if bool and not found then 
						if supportedFillTypes[fillType] == nil then
							supportedFillTypes[fillType]=100
						end
					end
				end		
			end
		end
	end
	-- get all attached implements recursively
	for _,impl in pairs(object:getAttachedImplements()) do
		self:getSupportedFillTypes(impl.object,supportedFillTypes)
	end
end

--check if at least one fillType in list and if runCounterActive then check sepreate counters
function SiloSelectedFillTypeSetting:isActive()  
	if self:getSize() == 0 then 
		return false
	end
	if not self.runCounterActive then 
		return true
	end	
	local data = self:getData()
	local runCounterCheck = false
	for _,data in ipairs(data) do 
		if data.runCounter > 0 then 
			runCounterCheck=true
		end
	end
	return runCounterCheck
end

function SiloSelectedFillTypeSetting:isRunCounterActive()
	return self.runCounterActive
end

function SiloSelectedFillTypeSetting:getTexts(index)
	local data = self:getDataByIndex(index)
	
	if data then
		local runCounterText = data.runCounter and data.runCounter.."/"..self.MAX_RUNS or ""
		local maxFillLevelText = data.maxFillLevel and data.maxFillLevel.."%" or ""
		local minFillLevelText = data.minFillLevel and data.minFillLevel.."%" or ""
		return runCounterText,maxFillLevelText,minFillLevelText
	else
		return "","",""
	end
end

function SiloSelectedFillTypeSetting:incrementRunCounter(index)
	local data = self:getDataByIndex(index)
	if data and data.runCounter then 
		if not (data.runCounter >= self.MAX_RUNS) then 
			data.runCounter = data.runCounter+1
			self:sendEvent(self.NetworkTypes.CHANGE_RUNCOUNTER,index,1)
		end
	end
end

function SiloSelectedFillTypeSetting:decrementRunCounterByFillType(lastFillTypes)
	local totalData = self:getData()
	for index,data in ipairs(totalData) do 
		for fillType,_ in pairs(lastFillTypes) do
			if data.fillType == fillType then
				self:decrementRunCounter(index)		
			end
		end
	end
end

function SiloSelectedFillTypeSetting:getMaxFillLevelByFillType(fillType)
	local totalData = self:getData()
	for index,data in ipairs(totalData) do 
		if data.fillType == fillType then
			return data.maxFillLevel		
		end
	end
end

function SiloSelectedFillTypeSetting:decrementRunCounter(index)
	local data = self:getDataByIndex(index)
	if data and data.runCounter then 
		if not (data.runCounter <= 0) then 
			data.runCounter = data.runCounter-1
			self:sendEvent(self.NetworkTypes.CHANGE_RUNCOUNTER,index,-1)
		end
	end
end

function SiloSelectedFillTypeSetting:changeMaxFillLevel(index)
	local diff = nil
	if index < 0 then 
		diff=-1
		index = index*(-1)
	else
		diff = 1
	end
	local data = self:getDataByIndex(index)
	if data and data.maxFillLevel then 
		local newDiff = data.maxFillLevel+diff 
		if newDiff >0 and newDiff <=100 then
			data.maxFillLevel = newDiff
			self:sendEvent(self.NetworkTypes.CHANGE_MAX_FILLLEVEL,index,diff)
		end
	end	
end

function SiloSelectedFillTypeSetting:changeMinFillLevel(index)
	local diff = nil
	if index < 0 then 
		diff=-1
		index = index*(-1)
	else
		diff = 1
	end
	local data = self:getDataByIndex(index)
	if data and data.minFillLevel then 
		local newDiff = data.minFillLevel+diff 
		if newDiff >=0 and newDiff <=100 then
			data.minFillLevel = newDiff
			self:sendEvent(self.NetworkTypes.CHANGE_MIN_FILLEVEL,index,diff)
		end
	end	
end

function SiloSelectedFillTypeSetting:changeRunCounter(index)
	local diff = nil
	if index < 0 then 
		diff=-1
		index = index*(-1)
	else
		diff = 1
	end
	local data = self:getDataByIndex(index)
	if data and data.runCounter then 
		local newDiff = data.runCounter+diff 
		if newDiff >=0 and newDiff <=self.MAX_RUNS then
			data.runCounter = newDiff
			self:sendEvent(self.NetworkTypes.CHANGE_RUNCOUNTER,index,diff)
		end
	end	
end

function SiloSelectedFillTypeSetting:setRunCounterFromNetwork(index,value)
	local data = self:getDataByIndex(index)
	if data and data.runCounter then 
		local diff = data.runCounter+value
		if diff >= 0 and diff <=20 then 
			data.runCounter = diff
		end
	end
end

function SiloSelectedFillTypeSetting:setMaxFillLevelFromNetwork(index,value)
	local data = self:getDataByIndex(index)
	if data and data.maxFillLevel then 
		local diff = data.maxFillLevel+value
		if diff >= 1 and diff <=100 then 
			data.maxFillLevel = diff
		end
	end
end

function SiloSelectedFillTypeSetting:setMinFillLevelFromNetwork(index,value)
	local data = self:getDataByIndex(index)
	if data and data.minFillLevel then 
		local diff = data.minFillLevel+value
		if diff >= 0 and diff <=100 then 
			data.minFillLevel = diff
		end
	end
end


function SiloSelectedFillTypeSetting:moveUpByIndex(index,noEventSend)
	LinkedListSetting.moveUpByIndex(self,index)
	if not noEventSend then
		self:sendEvent(self.NetworkTypes.MOVE_UP_X,index)
	end
end

function SiloSelectedFillTypeSetting:moveDownByIndex(index,noEventSend)
	LinkedListSetting.moveDownByIndex(self,index)
	if not noEventSend then
		self:sendEvent(self.NetworkTypes.MOVE_DOWN_X,index)
	end
end

function SiloSelectedFillTypeSetting:deleteByIndex(index, noEventSend)
	LinkedListSetting.deleteByIndex(self,index)
	if not noEventSend then
		self:sendEvent(self.NetworkTypes.DELETE_X,index)
	end
end

function SiloSelectedFillTypeSetting:getKey(parentKey)
	return parentKey .. '.' .. self.xmlKey
end

function SiloSelectedFillTypeSetting:loadFromXml(xml, parentKey)
	local size = Utils.getNoNil(getXMLInt(xml, self:getKey(parentKey)..self.xmlAttributeSize),0)
	if size and size>0 then
		for key=1,size do 
			local elementKey = string.format("%s.element(%d)", self:getKey(parentKey), key-1)
			local selectedFillType = Utils.getNoNil(getXMLInt(xml, elementKey..self.xmlAttributeFillType), 0)
			local counter
			if self.runCounterActive then
				counter = Utils.getNoNil(getXMLInt(xml, elementKey..self.xmlAttributeRunCounter), self.MAX_RUNS)
			end
			local maxLevel = Utils.getNoNil(getXMLInt(xml, elementKey..self.xmlAttributeMaxFillLevel), 100)
			local minLevel = Utils.getNoNil(getXMLInt(xml, elementKey..self.xmlAttributeMinFillLevel), 1)
			if selectedFillType then 
				self:addLast(self:fillTypeDataToAdd(selectedFillType,counter,maxLevel,minLevel))
			end
		end
	end
end

function SiloSelectedFillTypeSetting:saveToXml(xml, parentKey)
	local size = self:getSize()
	setXMLInt(xml, self:getKey(parentKey)..self.xmlAttributeSize, Utils.getNoNil(size,0))
	if size > 0 then 
		for key,data in ipairs(self:getData()) do
			local elementKey = string.format("%s.element(%d)", self:getKey(parentKey), key-1)
			setXMLInt(xml, elementKey..self.xmlAttributeFillType, Utils.getNoNil(data.fillType,0))
			if self.runCounterActive then
				setXMLInt(xml, elementKey..self.xmlAttributeRunCounter, Utils.getNoNil(data.runCounter,self.MAX_RUNS))
			end
			setXMLInt(xml, elementKey..self.xmlAttributeMaxFillLevel, Utils.getNoNil(data.maxFillLevel,100))
			setXMLInt(xml, elementKey..self.xmlAttributeMinFillLevel, Utils.getNoNil(data.minFillLevel,1))
		end
	end
end

function SiloSelectedFillTypeSetting:onWriteStream(stream)
	local size = self:getSize() or 0
	self:debugWriteStream(size,"size")
	streamWriteInt32(stream, size)
	self:debugWriteStream(self.runCounterActive,"runCounterActive")
	streamWriteBool(stream,self.runCounterActive)
	if size > 0 then 
		for key,data in ipairs(self:getData()) do
			self:debugWriteStream( data.fillType,"fillType")
			streamWriteInt32(stream, data.fillType)
			if self.runCounterActive then
				self:debugWriteStream( data.runCounter,"runCounter")
				streamWriteInt32(stream, data.runCounter)
			end
			self:debugWriteStream( data.maxFillLevel,"maxFillLevel")
			streamWriteInt32(stream, data.maxFillLevel)
			self:debugWriteStream( data.minFillLevel,"minFillLevel")
			streamWriteInt32(stream, data.minFillLevel)
		end
	end
end

function SiloSelectedFillTypeSetting:onReadStream(stream)
	local size = streamReadInt32(stream)
	self:debugReadStream(size,"size")
	self.runCounterActive = streamReadBool(stream)
	self:debugReadStream(self.runCounterActive,"runCounterActive")
	if size and size>0 then
		for key=1,size do 
			local selectedFillType = streamReadInt32(stream)
			self:debugReadStream(selectedFillType,"selectedFillType")
			local counter
			if self.runCounterActive then
				counter = streamReadInt32(stream)
				self:debugReadStream(counter,"counter")
			end
			local maxLevel = streamReadInt32(stream)
			self:debugReadStream(maxLevel,"maxLevel")
			local minLevel = streamReadInt32(stream)
			self:debugReadStream(minLevel,"minLevel")
			if selectedFillType then 
				self:addLast(self:fillTypeDataToAdd(selectedFillType,counter,maxLevel,minLevel))
			end
		end
	end
end

---@class TurnOnFieldSetting : BooleanSetting
TurnOnFieldSetting = CpObject(BooleanSetting)
function TurnOnFieldSetting:init(vehicle)
	BooleanSetting.init(self, 'turnOnField','COURSEPLAY_TURN_ON_FIELD', 'COURSEPLAY_TURN_ON_FIELD', vehicle) 
	self:set(true)
end

---@class RefillUntilPctSetting : PercentageSettingList
RefillUntilPctSetting = CpObject(PercentageSettingList)
function RefillUntilPctSetting:init(vehicle)
	PercentageSettingList.init(self, 'refillUntilPct', 'COURSEPLAY_REFILL_UNTIL_PCT', 'COURSEPLAY_REFILL_UNTIL_PCT', vehicle)
	self:set(100)
end

---@class DriveOnAtFillLevelSetting : PercentageSettingList
DriveOnAtFillLevelSetting = CpObject(PercentageSettingList)
function DriveOnAtFillLevelSetting:init(vehicle)
	PercentageSettingList.init(self, 'driveOnAtFillLevel', 'COURSEPLAY_DRIVE_ON_AT', 'COURSEPLAY_DRIVE_ON_AT', vehicle)
	self:set(90)
end

---@class FollowAtFillLevelSetting : PercentageSettingList
FollowAtFillLevelSetting = CpObject(PercentageSettingList)
function FollowAtFillLevelSetting:init(vehicle)
	PercentageSettingList.init(self, 'followAtFillLevel', 'COURSEPLAY_START_AT', 'COURSEPLAY_START_AT', vehicle)
	self:set(50)
end

---@class MoveOnAtFillLevelSetting : PercentageSettingList
MoveOnAtFillLevelSetting = CpObject(PercentageSettingList)
function MoveOnAtFillLevelSetting:init(vehicle)
	PercentageSettingList.init(self, 'moveOnAtFillLevel', 'COURSEPLAY_MOVE_ON_AT', 'COURSEPLAY_MOVE_ON_AT', vehicle)
	self:set(5)
end

--separate SiloSelectedFillTypeSettings to save their current state
--and disable runCounter for FillableFieldWorkDriver and FieldSupplyDriver

--TODO: figure out how to implement maxFillLevel for separate FillTypes in mode 1 
---@class GrainTransportDriver_SiloSelectedFillTypeSetting : SiloSelectedFillTypeSetting
GrainTransportDriver_SiloSelectedFillTypeSetting = CpObject(SiloSelectedFillTypeSetting)
function GrainTransportDriver_SiloSelectedFillTypeSetting:init(vehicle)
	SiloSelectedFillTypeSetting.init(self, vehicle, "GrainTransportDriver")
	self.MAX_FILLTYPES = 5
	self.disallowedFillTypes = {FillType.AIR}
end

---@class FillableFieldWorkDriver_SiloSelectedFillTypeSetting : SiloSelectedFillTypeSetting
FillableFieldWorkDriver_SiloSelectedFillTypeSetting = CpObject(SiloSelectedFillTypeSetting)
function FillableFieldWorkDriver_SiloSelectedFillTypeSetting:init(vehicle)
	SiloSelectedFillTypeSetting.init(self, vehicle, "FillableFieldWorkDriver")
	self.runCounterActive = false
	self.disallowedFillTypes = {FillType.DIESEL, FillType.DEF,FillType.AIR}
end

---@class FieldSupplyDriver_SiloSelectedFillTypeSetting : SiloSelectedFillTypeSetting
FieldSupplyDriver_SiloSelectedFillTypeSetting = CpObject(SiloSelectedFillTypeSetting)
function FieldSupplyDriver_SiloSelectedFillTypeSetting:init(vehicle)
	SiloSelectedFillTypeSetting.init(self, vehicle, "FieldSupplyDriver")
	self.runCounterActive = false
	self.disallowedFillTypes = {FillType.DEF,FillType.AIR}
end

---@class ShovelModeDriver_SiloSelectedFillTypeSetting : SiloSelectedFillTypeSetting
ShovelModeDriver_SiloSelectedFillTypeSetting = CpObject(SiloSelectedFillTypeSetting)
function ShovelModeDriver_SiloSelectedFillTypeSetting:init(vehicle)
	SiloSelectedFillTypeSetting.init(self, vehicle, "ShovelModeDriver")
	self.MAX_FILLTYPES = 1
	self.disallowedFillTypes = {FillType.DEF,FillType.AIR}
end

---@class MixerWagonAIDriver_SiloSelectedFillTypeSetting : SiloSelectedFillTypeSetting
MixerWagonAIDriver_SiloSelectedFillTypeSetting = CpObject(SiloSelectedFillTypeSetting)
function MixerWagonAIDriver_SiloSelectedFillTypeSetting:init(vehicle)
	SiloSelectedFillTypeSetting.init(self, vehicle, "MixerWagonAIDriver")
	self.MAX_FILLTYPES = 7
	self.disallowedFillTypes = {FillType.DEF,FillType.AIR}
end

---@class ShovelModeAIDriverTriggerHandlerIsActive : BooleanSetting
ShovelModeAIDriverTriggerHandlerIsActive = CpObject(BooleanSetting)
function ShovelModeAIDriverTriggerHandlerIsActive:init(vehicle, driverModeSetting)
	self.driverModeSetting = driverModeSetting
	BooleanSetting.init(self, 'shovelModeAIDriverTriggerHandlerIsActive','COURSEPLAY_SHOVEL_TRIGGERHANDLER_IS_ACTIVE', 'COURSEPLAY_SHOVEL_TRIGGERHANDLER_IS_ACTIVE', vehicle) 
	self:set(false)
end

function ShovelModeAIDriverTriggerHandlerIsActive:isDisabled()
	if self.driverModeSetting:get() ~= courseplay.MODE_SHOVEL_FILL_AND_EMPTY then 
		return true
	end
	return false
end

function ShovelModeAIDriverTriggerHandlerIsActive:onChange()
	if self.driverModeSetting:get() == courseplay.MODE_SHOVEL_FILL_AND_EMPTY then
		courseplay:setAIDriver(self.vehicle, courseplay.MODE_SHOVEL_FILL_AND_EMPTY)
	end	
	BooleanSetting.onChange(self)
end

--- This setting forces to load different fill types.
---@class SeparateFillTypeLoadingSetting : SettingList
SeparateFillTypeLoadingSetting = CpObject(SettingList)
function SeparateFillTypeLoadingSetting:init(vehicle)
	SettingList.init(self, 'separateFillTypeLoading', 'COURSEPLAY_LOADING_SEPARATE_FILLTYPES', 'COURSEPLAY_LOADING_SEPARATE_FILLTYPES', vehicle,
		--- Different fill types needed.
		{ 
			1, 
			2,
			3
		},
		{ 	
			'COURSEPLAY_DEACTIVATED',
			'COURSEPLAY_LOADING_SEPARATE_FILLTYPES_TRAILERS',
			'COURSEPLAY_LOADING_SEPARATE_FILLTYPES_TRAILERS'
		}
		)
	self:set(1)
end

function SeparateFillTypeLoadingSetting:hasDiffFillTypes()
	return self.current>1
end

function SeparateFillTypeLoadingSetting:checkAndSetValidValue(new)
	local diff = new<1 and #self.values or new
	if diff > self:getSeparateFillUnits()  then 
		return 1
	else 
		return SettingList.checkAndSetValidValue(self, new)
	end
end

--gets separate FillUnits example Wilson == 2
function SeparateFillTypeLoadingSetting:getSeparateFillUnits()
	local TrailerInfo = {}
	TrailerInfo.fillUnits = 0
	self:getTrailerFillUnitCount(self.vehicle,TrailerInfo)
	return TrailerInfo.fillUnits
end

function SeparateFillTypeLoadingSetting:getTrailerFillUnitCount(object,TrailerInfo)
	if self:hasNeededSpec(object,Dischargeable) and self:hasNeededSpec(object,Trailer) and not self:hasNeededSpec(object,Pipe) then 
		if object.getFillUnits then 
			for _, fillUnit in pairs(object:getFillUnits()) do
				TrailerInfo.fillUnits = TrailerInfo.fillUnits + 1
			end
		end
	end
	for _,impl in pairs(object:getAttachedImplements()) do
		self:getTrailerFillUnitCount(impl.object, TrailerInfo)
	end
end

function SeparateFillTypeLoadingSetting:hasNeededSpec(object,spec)
	if SpecializationUtil.hasSpecialization(spec, object.specializations) then
		return true
	end
end

function SeparateFillTypeLoadingSetting:getText()
	return self.current>1 and courseplay:loc(self.texts[self.current])..self:get() or SettingList.getText(self)
end

function SeparateFillTypeLoadingSetting:isActive()
	if self.vehicle.cp.driver:is_a(GrainTransportAIDriver) and not self.vehicle.cp.driver:getSiloSelectedFillTypeSetting():isEmpty() then 
		return true
	end
end

---@class ForcedToStopSetting : BooleanSetting
ForcedToStopSetting = CpObject(BooleanSetting)
function ForcedToStopSetting:init(vehicle)
	BooleanSetting.init(self, 'forcedToStop','--', '--', vehicle,{'COURSEPLAY_UNLOADING_DRIVER_STOP','COURSEPLAY_UNLOADING_DRIVER_START'}) 
	self:set(false)
end

---

---@class ReverseSpeedSetting : SpeedSetting
ReverseSpeedSetting = CpObject(SpeedSetting)
function ReverseSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'reverseSpeed','COURSEPLAY_SPEED_REVERSING', 'COURSEPLAY_SPEED_REVERSING', vehicle,3,(vehicle:getCruiseControlMaxSpeed() or 60)) 
	self:set(6)
end

function ReverseSpeedSetting:onChange()
	self.vehicle.cp.speeds.reverse = self:get()
end

---@class TurnSpeedSetting : SpeedSetting
TurnSpeedSetting = CpObject(SpeedSetting)
function TurnSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'turnSpeed','COURSEPLAY_SPEED_TURN', 'COURSEPLAY_SPEED_TURN', vehicle,3,(vehicle:getCruiseControlMaxSpeed() or 60)) 
	self:set(10)
end

function TurnSpeedSetting:onChange()
	self.vehicle.cp.speeds.turn = self:get()
end

---@class FieldSpeedSettting : SpeedSetting
FieldSpeedSettting = CpObject(SpeedSetting)
function FieldSpeedSettting:init(vehicle)
	SpeedSetting.init(self, 'fieldSpeed','COURSEPLAY_SPEED_FIELD', 'COURSEPLAY_SPEED_FIELD', vehicle,3,(vehicle:getCruiseControlMaxSpeed() or 60)) 
	self:set(24)
end

function FieldSpeedSettting:onChange()
	self.vehicle.cp.speeds.field = self:get()
end

---@class StreetSpeedSetting : SpeedSetting
StreetSpeedSetting = CpObject(SpeedSetting)
function StreetSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'streetSpeed','COURSEPLAY_SPEED_MAX', 'COURSEPLAY_SPEED_MAX', vehicle,3,(vehicle:getCruiseControlMaxSpeed() or 60)) 
	self:set(vehicle:getCruiseControlMaxSpeed() or 50)
end

function StreetSpeedSetting:getText()
	if self.vehicle.cp.settings.useRecordingSpeed:is(true) then 
		return courseplay:loc('COURSEPLAY_MAX_SPEED_MODE_AUTOMATIC'):format(SpeedSetting.getText(self))
	else 
		return SpeedSetting.getText(self)
	end	
end

function StreetSpeedSetting:onChange()
	self.vehicle.cp.speeds.street = self:get()
end


---@class BunkerSpeedSetting : SpeedSetting
BunkerSpeedSetting = CpObject(SpeedSetting)
---@param vehicle table
function BunkerSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'bunkerSpeed','COURSEPLAY_MODE10_MAX_BUNKERSPEED', 'COURSEPLAY_MODE10_MAX_BUNKERSPEED', vehicle,5,20)
	self:set(10)
	self.MAX_SPEED_LEVELING = 10
end

function BunkerSpeedSetting:getText()
	if self.isAutomaticActive() then 
		return courseplay:loc('COURSEPLAY_AUTOMATIC')
	else 
		return SpeedSetting.getText(self)
	end
end

function BunkerSpeedSetting:isAutomaticActive()
	return false
end

--[[
---@class CrawlSpeedSetting : SpeedSetting
CrawlSpeedSetting = CpObject(SpeedSetting)
function CrawlSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'crawlSpeed','COURSEPLAY_MODE10_MAX_BUNKERSPEED', 'COURSEPLAY_MODE10_MAX_BUNKERSPEED', vehicle,3,20) 
	
end

---@class DischargeSpeedSetting : SpeedSetting
DischargeSpeedSetting = CpObject(SpeedSetting)
function DischargeSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'dischargeSpeed','COURSEPLAY_MODE10_MAX_BUNKERSPEED', 'COURSEPLAY_MODE10_MAX_BUNKERSPEED', vehicle,3,20) 
	
end

---@class ApproachSpeedSetting : SpeedSetting
ApproachSpeedSetting = CpObject(SpeedSetting)
function ApproachSpeedSetting:init(vehicle)
	SpeedSetting.init(self, 'approachSpeed','COURSEPLAY_MODE10_MAX_BUNKERSPEED', 'COURSEPLAY_MODE10_MAX_BUNKERSPEED', vehicle,3,20) 
	
end
]]--

---@class AssignedCombinesSetting : Setting
AssignedCombinesSetting = CpObject(Setting)
AssignedCombinesSetting.NetworkTypes = {}
AssignedCombinesSetting.NetworkTypes.TOGGLE = 0
AssignedCombinesSetting.NetworkTypes.CHANGE_OFFSET = 1
function AssignedCombinesSetting:init(vehicle)
	Setting.init(self, 'assignedCombines','-', '-', vehicle) 
	self.MAX_COMBINES_FOR_PAGE = 5
	self.offsetHead = 0
	-- table has key (the combine's vehicle) with all combines assigned to this unloader in the HUD, value is true
	self.table = {}
	self.lastPossibleCombines = {}
end

function AssignedCombinesSetting:getPossibleCombines()
	return g_combineUnloadManager:getPossibleCombines(self.vehicle)
end

function AssignedCombinesSetting:onReadStream(streamId)
	while streamReadBool(streamId) do 
		if self.combineIDs == nil then
			self.combineIDs = {}
		end
		local combineId = NetworkUtil.readNodeObjectId(streamId)
		self.combineIDs[combineId] = true
	end
end

-- while onReadStream not every vehicle is created,
-- so assign them after every vehicle is init with the NetworkIds
function AssignedCombinesSetting:fixOnReadStream()
	if self.combineIDs and next(self.combineIDs) then 
		for combineId,isAssigned in pairs(self.combineIDs) do
			local combine = NetworkUtil.getObject(combineId)
			self.table[combine] = true
		end
		self.combineIDs = nil
	end
end

function AssignedCombinesSetting:onWriteStream(streamId)
	for combine,isAssigned in pairs(self.table) do 
		if isAssigned then 
			streamWriteBool(streamId, true)
			NetworkUtil.writeNodeObjectId(streamId, NetworkUtil.getObjectId(combine))
		end
	end
	streamWriteBool(streamId, false)
end

--enables/disables connection between combine/tractor
function AssignedCombinesSetting:toggleAssignedCombine(index)
	local newIndex = index-2+self.offsetHead
	local possibleCombines = self:getPossibleCombines()
	local combine =	possibleCombines[newIndex]
	self:fixOnReadStream()
	if combine then 
		if self.table[combine] then 
			self.table[combine] = nil
		else
			self.table[combine] = true
		end
		AssignedCombinesEvents.sendEvent(self.vehicle,combine)
		self.vehicle.cp.driver:refreshHUD()
	end
end

function AssignedCombinesSetting:toggleAssignedCombineFromNetwork(combine)
	self:fixOnReadStream()
	if self.table[combine] then 
		self.table[combine] = nil
	else
		self.table[combine] = true
	end
	self.vehicle.cp.driver:refreshHUD()
end

function AssignedCombinesSetting:getTexts()
	self:fixOnReadStream()
	local x = 1+self.offsetHead
	local line = 1
	local texts = {}
	for i=x,self.MAX_COMBINES_FOR_PAGE+x do 
		local possibleCombines = self:getPossibleCombines()
		self:clearInactiveCombines(possibleCombines)
		if possibleCombines[i] then
			local combine = possibleCombines[i]
			local fieldNumber = g_combineUnloadManager:getFieldNumber(combine)
			local box = self.table[combine] and "[X]"or "[  ]"
			local text = string.format("%s %s (Field %d)",box, combine.name , fieldNumber)
			texts[line] = text
		else
			texts[line] = ""
		end
		line = line +1
	end
	return texts
end

--removes inactive combines connections 
function AssignedCombinesSetting:clearInactiveCombines(possibleCombines)
	local validCombines = {}
	for index, combine in pairs(possibleCombines) do 
		if self.table[combine] then 
			validCombines[combine] = true
		end
	end
	self.table = validCombines
	self.vehicle.cp.driver:refreshHUD()
end

function AssignedCombinesSetting:allowedToChangeListOffsetUp()
	local possibleCombines = self:getPossibleCombines()
	return #possibleCombines-self.offsetHead > self.MAX_COMBINES_FOR_PAGE 
end

function AssignedCombinesSetting:allowedToChangeListOffsetDown()
	return self.offsetHead >0
end

--move List up/down
function AssignedCombinesSetting:changeListOffset(x,noEventSend)	
	if x>0 and self:allowedToChangeListOffsetUp() then 
		self.offsetHead = self.offsetHead+1
	elseif x<0 and self:allowedToChangeListOffsetDown() then 
		self.offsetHead = self.offsetHead-1
	end
	self.vehicle.cp.driver:refreshHUD()
end

function AssignedCombinesSetting:getData()
	return self.table
end

function AssignedCombinesSetting:__tostring()
	local ret = 'assigned combines: '
	local list = ''
	for combine, _ in pairs(self.table) do
		list = list .. nameNum(combine) .. ', '
	end
	if list == '' then list = 'none' end
	return ret .. list
end

function AssignedCombinesSetting:selectClosest()
	local dMin = math.huge
	local closestCombine
	for _, combine in pairs(self:getPossibleCombines()) do
		local d = calcDistanceFrom(self.vehicle.rootNode, combine.rootNode)
		if d < dMin then
			dMin = d
			closestCombine = combine
		end
	end
	if closestCombine then
		self.table[closestCombine] = true
	end
	self.vehicle.cp.driver:refreshHUD()
end

---@class ShowVisualWaypointsSetting : SettingList
ShowVisualWaypointsSetting = CpObject(SettingList)
ShowVisualWaypointsSetting.DEACTIVATED = 0
ShowVisualWaypointsSetting.START_STOP = 1
ShowVisualWaypointsSetting.ALL = 3
function ShowVisualWaypointsSetting:init(vehicle)
	SettingList.init(self, 'showVisualWaypoints', 'COURSEPLAY_WAYPOINT_MODE', 'COURSEPLAY_WAYPOINT_MODE', vehicle,
		{ 
			ShowVisualWaypointsSetting.DEACTIVATED,
			ShowVisualWaypointsSetting.START_STOP,
			ShowVisualWaypointsSetting.ALL 
		}
		)
	self:set(1)
end

function ShowVisualWaypointsSetting:onChange()
	courseplay.signs:setSignsVisibility(self.vehicle)
end

function ShowVisualWaypointsSetting:isSyncAllowed()
	return false
end

---@class ShowVisualWaypointsCrossPointSetting : BooleanSetting
ShowVisualWaypointsCrossPointSetting = CpObject(BooleanSetting)
function ShowVisualWaypointsCrossPointSetting:init(vehicle)
	BooleanSetting.init(self, 'showVisualWaypointsCrossPoint','-', '-', vehicle) 
	self:set(false)
end
function ShowVisualWaypointsCrossPointSetting:onChange()
	courseplay.signs:setSignsVisibility(self.vehicle)
end

function ShowVisualWaypointsCrossPointSetting:isSyncAllowed()
	return false
end

---@class ConvoyActiveSetting : BooleanSetting
ConvoyActiveSetting = CpObject(BooleanSetting)
function ConvoyActiveSetting:init(vehicle)
	BooleanSetting.init(self, 'convoyActive','COURSEPLAY_COMBINE_CONVOY', 'COURSEPLAY_COMBINE_CONVOY', vehicle) 
	self:set(false)
end

--not sure if this one would still be needed ??
---@class ConvoyMaxDistanceSetting : IntSetting
ConvoyMaxDistanceSetting = CpObject(IntSetting)
function ConvoyMaxDistanceSetting:init(vehicle)
	IntSetting.init(self, 'convoyMaxDistance','COURSEPLAY_CONVOY_MAX_DISTANCE', 'COURSEPLAY_CONVOY_MAX_DISTANCE', vehicle,40,3000) 
	self:set(300)
end

function ConvoyMaxDistanceSetting:getText()
	return string.format('%d%s', self:get(),courseplay:loc('COURSEPLAY_UNIT_METER'));
end

---@class ConvoyMinDistanceSetting : IntSetting
ConvoyMinDistanceSetting = CpObject(IntSetting)
function ConvoyMinDistanceSetting:init(vehicle)
	IntSetting.init(self, 'convoyMinDistance','COURSEPLAY_CONVOY_MIN_DISTANCE', 'COURSEPLAY_CONVOY_MIN_DISTANCE', vehicle,10,2000) 
	self:set(100)
end

function ConvoyMinDistanceSetting:getText()
	return string.format('%d%s', self:get(),courseplay:loc('COURSEPLAY_UNIT_METER'));
end

---@class OppositeTurnModeSetting : BooleanSetting
OppositeTurnModeSetting = CpObject(BooleanSetting)
function OppositeTurnModeSetting:init(vehicle)
	BooleanSetting.init(self, 'oppositeTurnMode','COURSEPLAY_OPPOSITE_TURN_DIRECTION', 'COURSEPLAY_OPPOSITE_TURN_DIRECTION', vehicle,{'COURSEPLAY_OPPOSITE_TURN_AT_END','COURSEPLAY_OPPOSITE_TURN_WHEN_POSSIBLE'}) 
	self:set(false)
end

---@class WorkingToolPositionsSetting : Setting
---@param totalPositionsAmount number of possible postions
---@param validSpecs table Specializations of objects that get saved, nil = every object
---@param labels table Tool position names.
WorkingToolPositionsSetting = CpObject(Setting)
WorkingToolPositionsSetting.NetworkTypes = {}
WorkingToolPositionsSetting.NetworkTypes.SET_OR_CLEAR_POSITION = 0
WorkingToolPositionsSetting.NetworkTypes.PLAY_POSITION = 1
WorkingToolPositionsSetting.Settings = {}
function WorkingToolPositionsSetting:init(name, label, toolTip, vehicle,totalPositionsAmount,validSpecs,labels)
	Setting.init(self, name,label, toolTip, vehicle)
	self.texts = {}
	self.labels = {}
	self:enrichLabels(labels)
	self.hasPosition = {}
	self.totalPositions = totalPositionsAmount or 4
	self.playTestPostion = nil
	self.validSpecs = validSpecs
	self.xmlAttribute = '#hasPositions'
	self.MAX_ROT_SPEED = 0.7
	self.MIN_ROT_SPEED = 0.1
	self.MAX_TRANS_SPEED = 0.7
	self.MIN_TRANS_SPEED = 0.2
	--- Store all instances of this setting in a global table,
	--- for direct access to update their manual tool positions.
	table.insert(WorkingToolPositionsSetting.Settings,self)

	self.PLAY_POSITION_EVENT = self:registerIntEvent(self.setPlayPositionFromNetwork)
	self.SET_OR_CLEAR_POSITION_EVENT = self:registerIntEvent(self.setFromNetwork)
end

function WorkingToolPositionsSetting:enrichLabels(labels)
	for i,l in ipairs(labels) do 
		table.insert(self.labels,courseplay:loc(l))
	end
end

--- Gets the tool position labels and texts.
---@return table labels 
---@return table texts
function WorkingToolPositionsSetting:getLabelsAndTexts()
	local texts = {}
	for i=1,self.totalPositions do 
		local text = ""
		if self.hasPosition[i] then 
			text = "ok"
		end
		texts[i] = text
	end
	return self.labels,texts
end

--- Saves or clears an tool position.
---@param x number position index to save at.
function WorkingToolPositionsSetting:setOrClearPostion(x,noEventSend) 
	if self.hasPosition[x] then 
		self.hasPosition[x] = nil
		if g_server then 
			self:clearPosition(self.vehicle,x)
		end
	else 
		self.hasPosition[x] = true
		if g_server then 
			self:savePosition(self.vehicle,x)
		end
	end
	if noEventSend == false or noEventSend == nil then
		self:raiseEvent(self.SET_OR_CLEAR_POSITION_EVENT,x)
	end
	self.vehicle.cp.driver:refreshHUD()
end

function WorkingToolPositionsSetting:setFromNetwork(position)
	self:setOrClearPostion(position,true)
end


--- Saves all tool positions recursively.
---@param object table vehicle or implement
---@param posX number position index to save at.
function WorkingToolPositionsSetting:savePosition(object,posX)
	local spec = object.spec_cylindered
	if spec and self:isValidSpec(object) then 
		if spec.cpWorkingToolPos == nil then 
			spec.cpWorkingToolPos = {}
		end
		local objectPos = {}
		for toolIndex, tool in ipairs(spec.movingTools) do
			objectPos[toolIndex] = {}
			objectPos[toolIndex].curRot = tool.curRot[tool.rotationAxis]
			objectPos[toolIndex].curTrans = tool.curTrans[tool.translationAxis]
		end
		spec.cpWorkingToolPos[posX] = objectPos
		if spec.cpWorkingToolPosMax == nil then 
			spec.cpWorkingToolPosMax = self.totalPositions
		end
	end	
	for _,impl in pairs(object:getAttachedImplements()) do
		self:savePosition(impl.object,posX)
	end
end

--- Clears all tool positions recursively.
---@param object table vehicle or implement
---@param posX number position index to clear at.
function WorkingToolPositionsSetting:clearPosition(object,posX)
	local spec = object.spec_cylindered
	if spec and spec.cpWorkingToolPos then 
		if spec.cpWorkingToolPos[posX] then 
			spec.cpWorkingToolPos[posX] = nil
		end
	end	
	for _,impl in pairs(object:getAttachedImplements()) do
		self:clearPosition(impl.object,posX)
	end
end

--- Moves a tool position manually.
---@param posX number position index to move.
function WorkingToolPositionsSetting:playPosition(x,noEventSend)
	if g_server then
		if self.hasPosition[x] then
			self.playTestPostion = x
		end
	elseif noEventSend == false or noEventSend == nil then 
		self:raiseEvent(self.PLAY_POSITION_EVENT,x)
	end
end

function WorkingToolPositionsSetting:setPlayPositionFromNetwork(x)
	self:playPosition(x,true)
end

--- Called every frame to update tool positions.
--- 1) Gets called by drivers to update the tool position.
--- 2) Gets called from WorkingToolPositionsSetting.updateManualToolPositions(), 
---	   to update manual tool positions.
---@param dt number
---@param posX number position index to move.
function WorkingToolPositionsSetting:updatePositions(dt,posX)
	callback = {}
	callback.isDirty = false
	callback.diff = 0
	local nextPosX = self.hasPosition[posX] and posX or self.playTestPostion and self.hasPosition[self.playTestPostion] and self.playTestPostion
	if nextPosX == nil then 
		return
	end
	self:updateAndSetPosition(self.vehicle,dt,nextPosX,callback)
	if not callback.isDirty then
		self.playTestPostion = nil
	end
	return not callback.isDirty,callback.diff
end

--- Updates all manual tool positions if necessary.
---@param dt number
function WorkingToolPositionsSetting.updateManualToolPositions(dt)
	for _,setting in pairs(WorkingToolPositionsSetting.Settings) do 
		setting:updatePositions(dt)
	end
end

--- Updates all tools for the given tool position.
---@param object table vehicle or implement
---@param dt number
---@param posX number position index to move.
---@param callback table was any tool moved and also returns the larget difference still left to move.
function WorkingToolPositionsSetting:updateAndSetPosition(object,dt,posX,callback)
	local spec = object.spec_cylindered 
	if spec and spec.cpWorkingToolPos and spec.cpWorkingToolPos[posX] and self:isValidSpec(object) then 
		for toolIndex, tool in ipairs(spec.movingTools) do
			if object:getIsMovingToolActive(tool) then 
				local isRotating,rotDiff = self.checkToolRotation(object,tool,toolIndex,posX,dt,self) 
				local isMoving,moveDiff = self.checkToolTranslation(object,tool,toolIndex,posX,dt,self) 			
				if isRotating or isMoving then 
					callback.isDirty = true
					callback.diff = math.max(rotDiff,moveDiff,callback.diff)
				end
			end
		end
	end
	for _,impl in pairs(object:getAttachedImplements()) do
		self:updateAndSetPosition(impl.object, dt,posX,callback)
	end
end

--- Updates rotation for a tool along an axis.
---@param object table vehicle or implement
---@param tool table part of object.movingTools
---@param posX number position index to move.
---@param dt number
---@param setting table
function WorkingToolPositionsSetting.checkToolRotation(object,tool,toolIndex,posX,dt,setting)
	local spec = object.spec_cylindered
	if tool.rotSpeed == nil then
		return false,0
	end
	
	local curRot = { getRotation(tool.node) }
	local newRot = curRot[tool.rotationAxis]
	-- speed for frontloader, shovel, etc achses
	cpDiff = spec.cpWorkingToolPos[posX][toolIndex].curRot - newRot
	local rotSpeed = math.min(math.max(setting.MIN_ROT_SPEED,math.abs(cpDiff*5)),setting.MAX_ROT_SPEED)--0.7
	if math.abs(cpDiff) > 0.003 then
		if cpDiff < 0 then
			rotSpeed=rotSpeed*(-1)
		end
	else 
		tool.move = 0
		return false,0
	end
	tool.move = rotSpeed
	if tool.move ~= tool.moveToSend then
		tool.moveToSend = tool.move
		object:raiseDirtyFlags(spec.cylinderedInputDirtyFlag)
	end
    return true,math.abs(cpDiff)*2*math.pi
end

--- Updates translation for a tool along an axis.
---@param object table vehicle or implement
---@param tool table part of object.movingTools
---@param posX number position index to move.
---@param dt number
---@param setting table
function WorkingToolPositionsSetting.checkToolTranslation(object,tool,toolIndex,posX,dt,setting)	
	local spec = object.spec_cylindered
	if tool.transSpeed == nil then
		return false,0
	end
	
	local curTrans = { getTranslation(tool.node) }
	local newTrans = curTrans[tool.translationAxis]
	-- speed for telescope arm
	cpDiff = spec.cpWorkingToolPos[posX][toolIndex].curTrans - newTrans
	local transSpeed = math.min(math.max(setting.MIN_TRANS_SPEED,math.abs(cpDiff*5)),setting.MAX_TRANS_SPEED)--0.5
	if math.abs(cpDiff) > 0.003 then
		if cpDiff < 0 then
			transSpeed=transSpeed*(-1)
		end
	else 
		tool.move = 0
		return false,0
	end
	tool.move = transSpeed
	if tool.move ~= tool.moveToSend then
		tool.moveToSend = tool.move
		object:raiseDirtyFlags(spec.cylinderedInputDirtyFlag)
	end
    return true,math.abs(cpDiff)
end

--- Checks if the vehicle or implements has a valid spec.
---@param object table vehicle or implement
function WorkingToolPositionsSetting:isValidSpec(object)
	if self.validSpecs == nil then 
		return true
	end	
	for _,spec in pairs(self.validSpecs) do
		if SpecializationUtil.hasSpecialization(spec, object.specializations)  then 
			return true
		end
	end	
end

--- Are all tool positions set ?
function WorkingToolPositionsSetting:hasValidToolPositions()
	return #self.hasPosition == self.totalPositions
end

function WorkingToolPositionsSetting:getTotalPositions()
	return self.totalPositions
end

function WorkingToolPositionsSetting:getHasPosition(x)
	return self.hasPosition[x]
end

function WorkingToolPositionsSetting:onWriteStream(streamId)
	for i=1,self.totalPositions do 
		self:debugWriteStream(self.hasPosition[i]==true,"hasPos: "..i)
		streamWriteBool(streamId,self.hasPosition[i]==true)
	end
end

function WorkingToolPositionsSetting:onReadStream(streamId)
	for i=1,self.totalPositions do 
		self.hasPosition[i] = streamReadBool(streamId)
		self:debugReadStream(self.hasPosition[i],"hasPos: "..i)
	end
end

function WorkingToolPositionsSetting:loadFromXml(xml, parentKey)
	local value = getXMLBool(xml, self:getKey(parentKey))
	if value then
		for i=1,self.totalPositions do 
			self.hasPosition[i] = true
		end
	end
end

function WorkingToolPositionsSetting:saveToXml(xml, parentKey)
	setXMLBool(xml, self:getKey(parentKey), self:hasValidToolPositions())
end

function WorkingToolPositionsSetting:devSetMaxRotSpeed(x)
	if x ~= nil and type(x) == "number" then
		self.MAX_ROT_SPEED = x
	end
end

function WorkingToolPositionsSetting:devSetMinRotSpeed(x)
	if x ~= nil and type(x) == "number" then
		self.MIN_ROT_SPEED = x
	end
end

function WorkingToolPositionsSetting:devSetMaxTransSpeed(x)
	if x ~= nil and type(x) == "number" then
		self.MAX_TRANS_SPEED = x
	end
end

function WorkingToolPositionsSetting:devSetMinTransSpeed(x)
	if x ~= nil and type(x) == "number" then
		self.MIN_TRANS_SPEED = x
	end
end

function WorkingToolPositionsSetting:saveToXMLFile(xmlFile, key, usedModNames)
	local spec = self.spec_cylindered
	if spec.cpWorkingToolPos == nil or #spec.cpWorkingToolPos < spec.cpWorkingToolPosMax then 
		return 
	end
	for positionIndex, movingTools in ipairs(spec.cpWorkingToolPos) do 
		local positionKey = string.format("%s.cpWorkingToolPos(%d)", key, positionIndex)
		for toolIndex, objectPos in ipairs(movingTools) do
			local toolKey = string.format("%s.movingTool(%d)", positionKey, toolIndex)
			if objectPos.curRot then
				setXMLFloat(xmlFile, toolKey.."#rotation", objectPos.curRot)
			end
			if objectPos.curTrans then
				setXMLFloat(xmlFile, toolKey.."#translation", objectPos.curTrans)
			end
		end
	end
end
Cylindered.saveToXMLFile = Utils.appendedFunction(Cylindered.saveToXMLFile,WorkingToolPositionsSetting.saveToXMLFile)

function WorkingToolPositionsSetting:onLoad(savegame)
	local spec = self.spec_cylindered
	local posIndex = 1
	if savegame == nil then 
		return
	end
	while true do
		local baseKey = string.format("%s.cylindered.cpWorkingToolPos(%d)",savegame.key, posIndex)
		if not hasXMLProperty(savegame.xmlFile, baseKey) then
			break
		end
		if spec.cpWorkingToolPos == nil then 
			spec.cpWorkingToolPos = {}
		end
		if spec.cpWorkingToolPos[posIndex] == nil then 
			spec.cpWorkingToolPos[posIndex] = {}
		end
		local toolIndex=0
		while true do
			local toolKey = string.format("%s.movingTool(%d)", baseKey, toolIndex)
			if not hasXMLProperty(savegame.xmlFile, toolKey) then
				break
			end
			if spec.cpWorkingToolPos[posIndex][toolIndex] == nil then 
				spec.cpWorkingToolPos[posIndex][toolIndex] = {}
			end
			local newCurRot = getXMLFloat(savegame.xmlFile, toolKey.."#rotation")
			if newCurRot ~= nil then 
				spec.cpWorkingToolPos[posIndex][toolIndex].curRot = newCurRot
			end
			local newTrans = getXMLFloat(savegame.xmlFile, toolKey.."#translation")
			if newTrans ~= nil then
				spec.cpWorkingToolPos[posIndex][toolIndex].curTrans = newTrans
			end
			toolIndex = toolIndex + 1
		end
		posIndex = posIndex + 1
	end
	if spec.cpWorkingToolPos then
		spec.cpWorkingToolPosMax = posIndex-1
	end	
end
Cylindered.onLoad = Utils.appendedFunction(Cylindered.onLoad,WorkingToolPositionsSetting.onLoad)

--- Disabled cylinder control of frontloaders, moveable pipes and so on..., while CP is driving.
function WorkingToolPositionsSetting.actionEventInputCylindered(object,superFunc, actionName, inputValue, callbackState, isAnalog, isMouse)
	local rootVehicle = object:getRootVehicle()
	if not rootVehicle:getIsCourseplayDriving() then 
		return superFunc(object, actionName, inputValue, callbackState, isAnalog, isMouse)
	end
end
Cylindered.actionEventInput = Utils.overwrittenFunction(Cylindered.actionEventInput,WorkingToolPositionsSetting.actionEventInputCylindered)

---@class FrontloaderToolPositionsSetting : WorkingToolPositionsSetting
FrontloaderToolPositionsSetting = CpObject(WorkingToolPositionsSetting)
function FrontloaderToolPositionsSetting:init(vehicle, driverModeSetting)
	self.driverModeSetting = driverModeSetting
	local label = "front"
	local toolTip = "front"
	local labels = {
		"COURSEPLAY_SHOVEL_LOADING_POSITION",
		"COURSEPLAY_SHOVEL_TRANSPORT_POSITION",
		"COURSEPLAY_SHOVEL_PRE_UNLOADING_POSITION",
		"COURSEPLAY_SHOVEL_UNLOADING_POSITION"
	}
	WorkingToolPositionsSetting.init(self,"frontloaderToolPositions", label, toolTip, vehicle,4,nil,labels)
end

function FrontloaderToolPositionsSetting:actionEventSavePosition(actionName, inputValue, callbackState, isAnalog)
	self:setOrClearPostion(callbackState)
end

function FrontloaderToolPositionsSetting:actionEventPlayPosition(actionName, inputValue, callbackState, isAnalog)
	self:playPosition(callbackState)
end	

function FrontloaderToolPositionsSetting:isPlayingPositionDisabled(x)
	return not self:getHasPosition(x)
end

function FrontloaderToolPositionsSetting:isDisabled()
	return self.driverModeSetting:get() ~= courseplay.MODE_SHOVEL_FILL_AND_EMPTY
end

---@class PipeToolPositionsSetting : WorkingToolPositionsSetting
PipeToolPositionsSetting = CpObject(WorkingToolPositionsSetting)
function PipeToolPositionsSetting:init(vehicle)
	local label = "pipe"
	local toolTip = "pipe"
	local labels = {
		"COURSEPLAY_SHOVEL_LOADING_POSITION"
	}
	local validSpecs = {Pipe}
	WorkingToolPositionsSetting.init(self,"pipeToolPositions", label, toolTip, vehicle,1,validSpecs,labels)
end

function PipeToolPositionsSetting:getText()
	if self.hasPosition[1] then 
		return "ok"
	end
end

function PipeToolPositionsSetting:validateCurrentValue()
	self.hasMoveablePipe = nil
	local pipeImplement = AIDriverUtil.getImplementWithSpecialization(self.vehicle,Pipe) or self.vehicle.spec_pipe and self.vehicle
	if pipeImplement then
		self.hasMoveablePipe = pipeImplement.spec_cylindered ~= nil
	end
end

function PipeToolPositionsSetting:getHasMoveablePipe()
	return self.hasMoveablePipe
end

---@class MixerWagonToolPositionsSetting : WorkingToolPositionsSetting
MixerWagonToolPositionsSetting = CpObject(WorkingToolPositionsSetting)
function MixerWagonToolPositionsSetting:init(vehicle)
	local label = "mixerWagon"
	local toolTip = "mixerWagon"
	local labels = {
		"COURSEPLAY_SHOVEL_LOADING_POSITION",
		"COURSEPLAY_SHOVEL_TRANSPORT_POSITION"
	}
	WorkingToolPositionsSetting.init(self,"mixerWagonToolPositions", label, toolTip, vehicle,2,nil,labels)
end

---@class SugarCaneTrailerToolPositionsSetting : WorkingToolPositionsSetting
SugarCaneTrailerToolPositionsSetting = CpObject(WorkingToolPositionsSetting)
SugarCaneTrailerToolPositionsSetting.TRANSPORT_POSITION = 1
SugarCaneTrailerToolPositionsSetting.UNLOADING_POSITION = 2
function SugarCaneTrailerToolPositionsSetting:init(vehicle)
	local label = "sugarCaneTrailer"
	local toolTip = "sugarCaneTrailer"
	local labels = {
		"COURSEPLAY_SHOVEL_TRANSPORT_POSITION",
		"COURSEPLAY_SHOVEL_UNLOADING_POSITION"
	}
	local validSpecs = {Shovel}
	WorkingToolPositionsSetting.init(self,"sugarCaneTrailerToolPositions", label, toolTip, vehicle,2,validSpecs,labels)
end

function SugarCaneTrailerToolPositionsSetting:validateCurrentValue()
	self.hasSugarCaneTrailer = AIDriverUtil.hasSugarCaneTrailer(self.vehicle)
end

function SugarCaneTrailerToolPositionsSetting:getHasSugarCaneTrailer()
	return self.hasSugarCaneTrailer
end

---@class AlwaysWaitForShovelPositionsSetting : BooleanSetting
AlwaysWaitForShovelPositionsSetting = CpObject(BooleanSetting)
function AlwaysWaitForShovelPositionsSetting:init(vehicle)
	BooleanSetting.init(self, 'alwaysWaitForShovelPositions','COURSEPLAY_SHOVEL_STOP_AND_GO', 'COURSEPLAY_SHOVEL_STOP_AND_GO', vehicle) 
	self:set(true)
end

---@class LevelCompactModeSetting : SettingList
LevelCompactModeSetting = CpObject(SettingList)
LevelCompactModeSetting.COMPACTING = 1
LevelCompactModeSetting.LEVELING = 2
LevelCompactModeSetting.FILLING = 3
function LevelCompactModeSetting:init(vehicle)
	SettingList.init(self, 'levelCompactMode', 'COURSEPLAY_MODE10_MODE', 'COURSEPLAY_MODE10_MODE', vehicle,
		{ 
			LevelCompactModeSetting.COMPACTING,
			LevelCompactModeSetting.LEVELING,
			LevelCompactModeSetting.FILLING 
		},
		{
			'COURSEPLAY_MODE10_MODE_COMPACTING',
			'COURSEPLAY_MODE10_MODE_LEVELING',
			'COURSEPLAY_MODE10_MODE_BUILDUP'
		}
		)
end

function LevelCompactModeSetting:checkAndSetValidValue(x)
	local new = SettingList.checkAndSetValidValue(self, x)
	if self:hasLeveler() then
		if new == LevelCompactModeSetting.COMPACTING then 
			return LevelCompactModeSetting.LEVELING
		end
	else 
		if new > LevelCompactModeSetting.COMPACTING then 
			return LevelCompactModeSetting.COMPACTING
		end
	end
	return new
end

function LevelCompactModeSetting:hasLeveler()
	return AIDriverUtil.getImplementWithSpecialization(self.vehicle, Leveler) ~= nil
end

---@class LevelCompactSearchOnlyAutomatedDriverSetting : BooleanSetting
LevelCompactSearchOnlyAutomatedDriverSetting = CpObject(BooleanSetting)
function LevelCompactSearchOnlyAutomatedDriverSetting:init(vehicle)
	BooleanSetting.init(self, 'levelCompactSearchOnlyAutomatedDriver', 'COURSEPLAY_MODE10_SEARCH_MODE', 'COURSEPLAY_MODE10_SEARCH_MODE', vehicle,
		{'COURSEPLAY_MODE10_SEARCH_MODE_ALL','COURSEPLAY_MODE10_SEARCH_MODE_CP'})
	self:set(true)
end

---@class LevelCompactSearchRadiusSetting : IntSetting
LevelCompactSearchRadiusSetting = CpObject(IntSetting)
function LevelCompactSearchRadiusSetting:init(vehicle)
	IntSetting.init(self, 'levelCompactSearchRadius', 'COURSEPLAY_MODE10_SEARCHRADIUS', 'COURSEPLAY_MODE10_SEARCHRADIUS', vehicle,10,200)
	self:set(50)
	self.hasChanged = false
end

--- Creates a small timer to show the radius change.
function LevelCompactSearchRadiusSetting:onChange()
	courseplay:setCustomTimer(self.vehicle, "showSearchRadius", 5)
	self.hasChanged = true
end

--- Checks if the timer is active and is finished.
function LevelCompactSearchRadiusSetting:update()
	if not self:isShowRadiusActive() then 
		return
	end
	
	if courseplay:timerIsThrough(self.vehicle, "showSearchRadius") then 
		courseplay:resetCustomTimer(self.vehicle, "showSearchRadius", true)
		self.hasChanged = false
	end
end

function LevelCompactSearchRadiusSetting:isShowRadiusActive()
	return self.hasChanged
end


function LevelCompactSearchRadiusSetting:getText()
	return ('%d%s'):format(self:get(), courseplay:loc('COURSEPLAY_UNIT_METER'))
end

---@class LevelCompactSiloTypSetting : BooleanSetting
LevelCompactSiloTypSetting = CpObject(BooleanSetting)
function LevelCompactSiloTypSetting:init(vehicle)
	local texts = {
		'COURSEPLAY_MODE10_REVERSE_UNLOADING',
		'COURSEPLAY_MODE10_DRIVINGTHROUGH'
	}
	BooleanSetting.init(self, 'levelCompactSiloTyp', 'COURSEPLAY_MODE10_SILO_LOADEDBY', 'COURSEPLAY_MODE10_SILO_LOADEDBY', vehicle,texts)
end

---@class LevelCompactShieldHeightSetting : SettingList
LevelCompactShieldHeightSetting = CpObject(SettingList)
LevelCompactShieldHeightSetting.Automatic = -1
function LevelCompactShieldHeightSetting:init(vehicle)
	local texts = {}
	local values = {}
	texts[1] = 'COURSEPLAY_AUTOMATIC'
	values[1] = -1
	for i=2,17 do
		local x = (i-2)*0.1
		values[i] = x
		texts[i] = x
	end
	SettingList.init(self, 'levelCompactShieldHeight', 'COURSEPLAY_MODE10_BLADE_HEIGHT', 'COURSEPLAY_MODE10_BLADE_HEIGHT', vehicle,values,texts)
	self:set(-1)
end

function LevelCompactShieldHeightSetting:isAutomaticActive()
	return self:get() == self.Automatic
end

function LevelCompactShieldHeightSetting:getText()
	if not self:isAutomaticActive() then
		return ('%.1f%s'):format(self:get(), courseplay:loc('COURSEPLAY_UNIT_METER'))
	else 
		return SettingList.getText(self)
	end
end

function LevelCompactShieldHeightSetting:changeByX(x)
	if not self:isDisabled() then 
		SettingList.changeByX(self,x) 
	end
end

function LevelCompactShieldHeightSetting:isDisabled()
	return self.vehicle.cp.settings.levelCompactMode:get() == LevelCompactModeSetting.COMPACTING
end

--- The wait time setting can be incremented by 15 seconds up to max: 60min.
---
---@class WaitTimeSetting : IntSetting
WaitTimeSetting = CpObject(IntSetting)
function WaitTimeSetting:init(vehicle)
	local max = 3600 --- 60 min
	IntSetting.init(self,"waitTime","COURSEPLAY_WAITING_TIME","COURSEPLAY_WAITING_TIME",vehicle,0,max)
	self:set(0)
	--- Allways increments/decrements the setting by 15 sec
	self.scale = 15
end

function WaitTimeSetting:isDisabled()
	return not self.vehicle.cp.driver:isStoppingAtWaitPointAllowed()
end

function WaitTimeSetting:getText()
	local totalSeconds = self:get()
	local minutes, seconds = math.floor(totalSeconds/60), totalSeconds % 60;
	if minutes>0 then 
		if seconds > 0 then 
			return courseplay:loc('COURSEPLAY_MINUTES'):format(minutes)..','..courseplay:loc('COURSEPLAY_SECONDS'):format(seconds)
		end
		return courseplay:loc('COURSEPLAY_MINUTES'):format(minutes)
	elseif seconds > 0 then 
		return courseplay:loc('COURSEPLAY_SECONDS'):format(seconds)
	end
	return '---' 
end

function WaitTimeSetting:changeByX(x)
	--- For 1-60 sec increment by 1 sec, else increment by the scale*sec
	x = self:get()>=60 and x*self.scale or x
	IntSetting.changeByX(self,x)
end

--- @class CourseDrawModeSetting : SettingList
CourseDrawModeSetting = CpObject(SettingList)
-- 2D lines display options
CourseDrawModeSetting.COURSE_2D_DISPLAY_OFF	 = 0;
CourseDrawModeSetting.COURSE_2D_DISPLAY_2DONLY	 = 1;
function CourseDrawModeSetting:init(vehicle)
	local values = {
		self.COURSE_2D_DISPLAY_OFF,
		self.COURSE_2D_DISPLAY_2DONLY,
	}
	local texts = {
		"",
		"2D",
	}
	SettingList.init(self,"courseDrawMode","","",vehicle,values,texts)
	self:set(self.COURSE_2D_DISPLAY_OFF)
end

function CourseDrawModeSetting:isSyncAllowed() 
	return false
end

function CourseDrawModeSetting:isDeactivated()
	return self:get() == self.COURSE_2D_DISPLAY_OFF
end

function CourseDrawModeSetting:isCourseMapVisible()
	return self:get() == self.COURSE_2D_DISPLAY_2DONLY
end


--[[
---@class SearchCombineAutomaticallySetting : BooleanSetting
SearchCombineAutomaticallySetting = CpObject(BooleanSetting)
function SearchCombineAutomaticallySetting:init(vehicle)
	BooleanSetting.init(self, 'searchCombineAutomatically','COURSEPLAY_COMBINE_SEARCH_MODE', 'COURSEPLAY_COMBINE_SEARCH_MODE', vehicle, {'COURSEPLAY_MANUAL_SEARCH','COURSEPLAY_AUTOMATIC_SEARCH'}) 
	self:set(false)
end

---@class ShowSelectedFieldEdgePathSetting : SettingList
ShowSelectedFieldEdgePathSetting = CpObject(SettingList)
function ShowSelectedFieldEdgePathSetting:init(vehicle)
	SettingList.init(self, 'showSelectedFieldEdgePath','COURSEPLAY_CURRENT_FIELD_EDGE_PATH_NUMBER', 'COURSEPLAY_CURRENT_FIELD_EDGE_PATH_NUMBER', vehicle) 
	self:set(false)
end


]]--

--- Container for settings
--- @class SettingsContainer
SettingsContainer = CpObject()

function SettingsContainer:init(name)
	self.name = name
end

--- Add a setting which then can be addressed by its name like container['settingName'] or container.settingName
function SettingsContainer:addSetting(settingClass, ...)
	local s = settingClass(...)
	s.syncValue = true -- Only sync values that are part of a SettingsContainer
	s:setParent(self.name)
	self[s.name] = s
end

function SettingsContainer:saveToXML(xml, parentKey)
	for _, setting in pairs(self) do
		if self.validateSetting(setting) then 
			setting:saveToXml(xml, parentKey)
		end
	end
end

function SettingsContainer:loadFromXML(xml, parentKey)
	for _, setting in pairs(self) do
		if self.validateSetting(setting) then 
			setting:loadFromXml(xml, parentKey)
		end
	end
end

function SettingsContainer:validateCurrentValues()
	for k, setting in pairs(self) do
		if self.validateSetting(setting) then 
			setting:validateCurrentValue()
		end
	end
end

--TODO: test if in pairs() or in ipairs() is needed, as ipairs would be safer as
--		I am not sure if the order is the same on Client and Server,
--		but doesn't seem to work at the moment

function SettingsContainer:onReadStream(stream)
	for k, setting in pairs(self) do
		if self.validateSetting(setting) and setting:isSyncAllowed() then 
			setting:onReadStream(stream)
		end
	end
end

function SettingsContainer:onWriteStream(stream)
	for k, setting in pairs(self) do
		if self.validateSetting(setting) and setting:isSyncAllowed() then 
			setting:onWriteStream(stream)
		end
	end
end

function SettingsContainer:validateSetting(setting)
	if setting == self.name then 
		return false
	end
	return true
end

function SettingsContainer:debug(channel)
	for key, setting in pairs(self) do
		if key ~= 'name' then
			if setting.vehicle then
				courseplay.debugVehicle(channel, setting.vehicle, setting:getDebugString())
			else
				courseplay.debugFormat(channel, setting:getDebugString())
			end
		end
	end
end

function SettingsContainer.createGlobalPathfinderSettings()
	local container = SettingsContainer('globalPathfinderSettings')
	container:addSetting(MaxDeltaAngleAtGoal)
	container:addSetting(DeltaAngleRelaxFactor)
	return container
end

function SettingsContainer.createVehicleSpecificSettings(vehicle)
	---@type SettingsContainer
	local container = SettingsContainer("settings")
	container:addSetting(DriverModeSetting, vehicle)
	container:addSetting(SearchCombineOnFieldSetting, vehicle)
	container:addSetting(SelectedCombineToUnloadSetting)
	container:addSetting(ReturnToFirstPointSetting, vehicle,container.driverMode)
	container:addSetting(UseAITurnsSetting, vehicle)
	container:addSetting(UsePathfindingInTurnsSetting, vehicle)
	container:addSetting(AllowReverseForPathfindingInTurnsSetting, vehicle)
	container:addSetting(ImplementRaiseTimeSetting, vehicle)
	container:addSetting(ImplementLowerTimeSetting, vehicle)
	container:addSetting(AutoDriveModeSetting, vehicle)
	container:addSetting(SelfUnloadSetting, vehicle)
	container:addSetting(StartingPointSetting, vehicle,container.driverMode)
	container:addSetting(SymmetricLaneChangeSetting, vehicle)
	container:addSetting(PipeAlwaysUnfoldSetting, vehicle)
	container:addSetting(RidgeMarkersAutomatic, vehicle)
	container:addSetting(KeepCurrentSteering, vehicle)
	container:addSetting(StopForUnloadSetting, vehicle)
	container:addSetting(StrawSwathSetting, vehicle)
	container:addSetting(AllowUnloadOnFirstHeadlandSetting, vehicle)
	container:addSetting(SowingMachineFertilizerEnabled, vehicle)
	container:addSetting(EnableOpenHudWithMouseVehicle, vehicle)
	container:addSetting(EnableVisualWaypointsTemporary, vehicle)
	container:addSetting(StopAtEndSetting, vehicle,container.driverMode)
	container:addSetting(AutomaticCoverHandlingSetting, vehicle)
	container:addSetting(BaleCollectionFieldSetting, vehicle)
	container:addSetting(DriverPriorityUseFillLevelSetting, vehicle)
	container:addSetting(UseRecordingSpeedSetting, vehicle)
	container:addSetting(WarningLightsModeSetting, vehicle)
	container:addSetting(SaveFuelOptionSetting, vehicle)
	container:addSetting(AlwaysSearchFuelSetting, vehicle)
	container:addSetting(RealisticDrivingSetting, vehicle)
	container:addSetting(DriveUnloadNowSetting, vehicle)
	container:addSetting(RequestUnloaderSetting, vehicle)
	container:addSetting(TurnOnFieldSetting, vehicle)
	container:addSetting(GrainTransportDriver_SiloSelectedFillTypeSetting, vehicle)
	container:addSetting(FillableFieldWorkDriver_SiloSelectedFillTypeSetting, vehicle)
	container:addSetting(FieldSupplyDriver_SiloSelectedFillTypeSetting, vehicle)
	container:addSetting(ShovelModeDriver_SiloSelectedFillTypeSetting, vehicle)
	container:addSetting(ShovelModeAIDriverTriggerHandlerIsActive, vehicle,container.driverMode)
	container:addSetting(DriveOnAtFillLevelSetting, vehicle)
	container:addSetting(MoveOnAtFillLevelSetting, vehicle)
	container:addSetting(RefillUntilPctSetting, vehicle)
	container:addSetting(FollowAtFillLevelSetting,vehicle)
	container:addSetting(ForcedToStopSetting,vehicle)
	container:addSetting(SeparateFillTypeLoadingSetting,vehicle)
	container:addSetting(ReverseSpeedSetting, vehicle)
	container:addSetting(TurnSpeedSetting, vehicle)
	container:addSetting(FieldSpeedSettting,vehicle)
	container:addSetting(StreetSpeedSetting,vehicle)
	container:addSetting(ShowVisualWaypointsSetting,vehicle)
	container:addSetting(ShowVisualWaypointsCrossPointSetting,vehicle)
	container:addSetting(OppositeTurnModeSetting,vehicle)
	container:addSetting(FoldImplementAtEndSetting, vehicle)
	container:addSetting(ConvoyActiveSetting,vehicle)
	container:addSetting(ConvoyMinDistanceSetting,vehicle)
	container:addSetting(ConvoyMaxDistanceSetting,vehicle) -- do we need this one ?
	container:addSetting(FrontloaderToolPositionsSetting,vehicle,container.driverMode)
	container:addSetting(PipeToolPositionsSetting,vehicle)
	container:addSetting(AlwaysWaitForShovelPositionsSetting,vehicle)
	container:addSetting(LevelCompactModeSetting,vehicle)
	container:addSetting(LevelCompactSearchOnlyAutomatedDriverSetting,vehicle)
	container:addSetting(LevelCompactSearchRadiusSetting,vehicle)
	container:addSetting(LevelCompactShieldHeightSetting,vehicle)
	container:addSetting(BunkerSpeedSetting,vehicle)
	container:addSetting(LevelCompactSiloTypSetting,vehicle)
	container:addSetting(ToolOffsetXSetting, vehicle)
	container:addSetting(ToolOffsetZSetting, vehicle)
	container:addSetting(LoadUnloadOffsetXSetting, vehicle)
	container:addSetting(LoadUnloadOffsetZSetting, vehicle)
	container:addSetting(CombineOffsetXSetting, vehicle)
	container:addSetting(CombineOffsetZSetting, vehicle)
	container:addSetting(WaitTimeSetting, vehicle)
	container:addSetting(MixerWagonAIDriver_SiloSelectedFillTypeSetting, vehicle)
	container:addSetting(MixerWagonToolPositionsSetting, vehicle)
	container:addSetting(CourseDrawModeSetting,vehicle)
	container:addSetting(SugarCaneTrailerToolPositionsSetting, vehicle)
	container:addSetting(TurnDiameterSetting,vehicle,container.driverMode)
	return container
end




-- do not remove this comment
-- vim: set noexpandtab:
