
--- Global class
Courseplay = CpObject()
Courseplay.MOD_NAME = g_currentModName
Courseplay.BASE_DIRECTORY = g_currentModDirectory
Courseplay.baseXmlKey = "Courseplay"
Courseplay.xmlKey = Courseplay.baseXmlKey.."."

function Courseplay:init()
	g_gui:loadProfiles( Utils.getFilename("config/gui/GUIProfiles.xml", Courseplay.BASE_DIRECTORY) )

	--- Base cp folder
	self.baseDir = getUserProfileAppPath() .. "modSettings/" .. Courseplay.MOD_NAME ..  "/"
	createFolder(self.baseDir)
	--- Base cp folder
	self.cpFilePath = self.baseDir.."courseplay.xml"
end

function Courseplay:registerXmlSchema()
	self.xmlSchema = XMLSchema.new("Courseplay")
	self.xmlSchema:register(XMLValueType.STRING, self.baseXmlKey.."#lastVersion")
	self.globalSettings:registerXmlSchema(self.xmlSchema, self.xmlKey)
	CpBaseHud.registerXmlSchema(self.xmlSchema, self.xmlKey)
	CpHudInfoTexts.registerXmlSchema(self.xmlSchema, self.xmlKey)
end

--- Loads data not tied to a savegame.
function Courseplay:loadUserSettings()
	local xmlFile = XMLFile.loadIfExists("cpXmlFile", self.cpFilePath, self.xmlSchema)
	if xmlFile then
		self:showUserInformation(xmlFile, self.baseXmlKey)
		self.globalSettings:loadFromXMLFile(xmlFile, self.xmlKey)
		CpBaseHud.loadFromXmlFile(xmlFile, self.xmlKey)
		CpHudInfoTexts.loadFromXmlFile(xmlFile, self.xmlKey)
		xmlFile:save()
		xmlFile:delete()
	end
end

--- Saves data not tied to a savegame.
function Courseplay:saveUserSettings()
	local xmlFile = XMLFile.create("cpXmlFile", self.cpFilePath, self.baseXmlKey, self.xmlSchema)
	if xmlFile then 
		self.globalSettings:saveUserSettingsToXmlFile(xmlFile, self.xmlKey)
		CpBaseHud.saveToXmlFile(xmlFile, self.xmlKey)
		CpHudInfoTexts.saveToXmlFile(xmlFile, self.xmlKey)
		if self.currentVersion then
			xmlFile:setValue(self.baseXmlKey.."#lastVersion", self.currentVersion)
		end
		xmlFile:save()
		xmlFile:delete()
	end
end

------------------------------------------------------------------------------------------------------------------------
-- User info with github reference and update notification.
------------------------------------------------------------------------------------------------------------------------

function Courseplay:showUserInformation(xmlFile, key)
	local showInfoDialog = true
	self.currentVersion = g_modManager:getModByName(self.MOD_NAME).version
	local lastLoadedVersion = xmlFile:getValue(key.."#lastVersion")
	if lastLoadedVersion then 
		if self.currentVersion == lastLoadedVersion then 
			showInfoDialog = false
		end
		CpUtil.info("Current version: %s, last version: %s", self.currentVersion, lastLoadedVersion)
	else 
		CpUtil.info("Current version: %s", self.currentVersion)
	end
	if showInfoDialog then
		g_gui:showInfoDialog({
			text = string.format(g_i18n:getText("CP_infoText"), self.currentVersion)

		})
		self.currentVersion = self.currentVersion
		xmlFile:setValue(key.."#lastVersion", self.currentVersion)
	end
end

------------------------------------------------------------------------------------------------------------------------
-- Global Giants functions listener 
------------------------------------------------------------------------------------------------------------------------

--- This function is called on loading a savegame.
---@param filename string
function Courseplay:loadMap(filename)
	self.globalSettings = CpGlobalSettings()
	self:registerXmlSchema()
	self:loadUserSettings()
	self:load()
	self:setupGui()
	if g_currentMission.missionInfo.savegameDirectory ~= nil then
		local saveGamePath = g_currentMission.missionInfo.savegameDirectory .."/"
		local filePath = saveGamePath .. "Courseplay.xml"
		self.xmlFile = XMLFile.load("cpXml", filePath , self.xmlSchema)
		if self.xmlFile == nil then return end
		self.globalSettings:loadFromXMLFile(self.xmlFile, g_Courseplay.xmlKey)
		self.xmlFile:delete()

		g_assignedCoursesManager:loadAssignedCourses(saveGamePath)
	end

	--- Ugly hack to get access to the global AutoDrive table, as this global is dependent on the auto drive folder name.
	self.autoDrive = FS22_AutoDrive and FS22_AutoDrive.AutoDrive
	CpUtil.info("Auto drive found: %s", tostring(self.autoDrive~=nil))

	g_courseEditor:load()
end

function Courseplay:deleteMap()
	g_courseEditor:delete()
	BufferedCourseDisplay.deleteBuffer()
	g_signPrototypes:delete()
	g_customFieldManager:delete()
end

function Courseplay:deleteMap()
	g_devHelper:delete()
end

function Courseplay:setupGui()
	local vehicleSettingsFrame = CpVehicleSettingsFrame.new()
	local globalSettingsFrame = CpGlobalSettingsFrame.new()
	local courseManagerFrame = CpCourseManagerFrame.new(self.courseStorage)
	g_gui:loadGui(Utils.getFilename("config/gui/VehicleSettingsFrame.xml", Courseplay.BASE_DIRECTORY),
				 "CpVehicleSettingsFrame", vehicleSettingsFrame, true)
	g_gui:loadGui(Utils.getFilename("config/gui/GlobalSettingsFrame.xml", Courseplay.BASE_DIRECTORY),
				 "CpGlobalSettingsFrame", globalSettingsFrame, true)
	g_gui:loadGui(Utils.getFilename("config/gui/CourseManagerFrame.xml", Courseplay.BASE_DIRECTORY),
				 "CpCourseManagerFrame", courseManagerFrame, true)
	local function predicateFunc()
		return CpInGameMenuAIFrameExtended.getVehicle() ~= nil
	end
	
	--- As precision farming decided to be moved in between the normal map and the ai map,
	--- we move it down one position.
	local pos = g_modIsLoaded["FS22_precisionFarming"] and 4 or 3

	CpGuiUtil.fixInGameMenuPage(vehicleSettingsFrame, "pageCpVehicleSettings",
			{896, 0, 128, 128}, pos + 1, predicateFunc)
	CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings",
			{768, 0, 128, 128}, pos + 1, function () return true end)
	CpGuiUtil.fixInGameMenuPage(courseManagerFrame, "pageCpCourseManager",
			{256, 0, 128, 128}, pos + 1, predicateFunc)
	self.infoTextsHud = CpHudInfoTexts()

end

--- Adds cp help info to the in game help menu.
function Courseplay:loadMapDataHelpLineManager(superFunc, ...)
	local ret = superFunc(self, ...)
	if ret then
		self:loadFromXML(Utils.getFilename("config/HelpMenu.xml", Courseplay.BASE_DIRECTORY))
		return true
	end
	return false
end
HelpLineManager.loadMapData = Utils.overwrittenFunction( HelpLineManager.loadMapData, Courseplay.loadMapDataHelpLineManager)

--- Saves all global data, for example global settings.
function Courseplay.saveToXMLFile(missionInfo)
	if missionInfo.isValid then 
		local saveGamePath = missionInfo.savegameDirectory .."/"
		local xmlFile = XMLFile.create("cpXml", saveGamePath.. "Courseplay.xml", 
				"Courseplay", g_Courseplay.xmlSchema)
		if xmlFile then	
			g_Courseplay.globalSettings:saveToXMLFile(xmlFile, g_Courseplay.xmlKey)
			xmlFile:save()
			xmlFile:delete()
		end
		g_Courseplay:saveUserSettings()
		g_assignedCoursesManager:saveAssignedCourses(saveGamePath)
	end
end
FSCareerMissionInfo.saveToXMLFile = Utils.prependedFunction(FSCareerMissionInfo.saveToXMLFile, Courseplay.saveToXMLFile)

function Courseplay:update(dt)
	g_devHelper:update()
end

function Courseplay:draw()
	if not g_gui:getIsGuiVisible() then
		g_vineScanner:draw()
	end
	g_devHelper:draw()
	CpDebug:draw()
	if not g_gui:getIsGuiVisible() and not g_noHudModeEnabled then
		self.infoTextsHud:draw()
	end
end

---@param posX number
---@param posY number
---@param isDown boolean
---@param isUp boolean
---@param button number
function Courseplay:mouseEvent(posX, posY, isDown, isUp, button)
	if not g_gui:getIsGuiVisible() then
		local vehicle = g_currentMission.controlledVehicle
		local hud = vehicle and vehicle.getCpHud and vehicle:getCpHud()
		if hud then
			hud:mouseEvent(posX, posY, isDown, isUp, button)
		end
		self.infoTextsHud:mouseEvent(posX, posY, isDown, isUp, button)
	end
end

---@param unicode number
---@param sym number
---@param modifier number
---@param isDown boolean
function Courseplay:keyEvent(unicode, sym, modifier, isDown)
	g_devHelper:keyEvent(unicode, sym, modifier, isDown)
end

function Courseplay:load()
	--- Sub folder for debug information
	self.debugDir = self.baseDir .. "Debug/"
	createFolder(self.debugDir) 
	--- Sub folder for debug prints
	self.debugPrintDir = self.debugDir .. "DebugPrints/"
	createFolder(self.debugPrintDir) 
	--- Default path to save prints without an explicit name.
	self.defaultDebugPrintPath = self.debugDir .. "DebugPrint.xml"

	self.courseDir = self.baseDir .. "Courses"
	createFolder(self.courseDir) 
	self.courseStorage = FileSystem(self.courseDir, g_currentMission.missionInfo.mapId)
	self.courseStorage:fixCourseStorageRoot()

	self.customFieldDir = self.baseDir .. "CustomFields"
	createFolder(self.customFieldDir)
	g_customFieldManager = CustomFieldManager(FileSystem(self.customFieldDir, g_currentMission.missionInfo.mapId))
	--- Adds reference to the custom fields, for extern mod support.
	g_fieldManager.cpCustomFields = g_customFieldManager.fields
	g_vehicleConfigurations:loadFromXml()
	g_assignedCoursesManager:registerXmlSchema()

	--- Register additional AI messages.
	AIMessageErrorIsFull.register()	
	g_vineScanner:setup()
end

------------------------------------------------------------------------------------------------------------------------
-- Player action events
------------------------------------------------------------------------------------------------------------------------

--- Adds player mouse action event, for global info texts.
function Courseplay.addPlayerActionEvents(mission)
	if mission.player then
		CpUtil.debugFormat(CpDebug.DBG_HUD, "Added player input events")
		mission.player.inputInformation.registrationList[InputAction.CP_TOGGLE_MOUSE] = {
			text = "",
			triggerAlways = false,
			triggerDown = false,
			eventId = "",
			textVisibility = false,
			triggerUp = true,
			callback = Courseplay.onOpenCloseMouseEvent,
			activeType = Player.INPUT_ACTIVE_TYPE.STARTS_ENABLED
		}
		mission.player.updateActionEvents = Utils.appendedFunction(mission.player.updateActionEvents, Courseplay.updatePlayerActionEvents)
		mission.player.removeActionEvents = Utils.prependedFunction(mission.player.removeActionEvents, Courseplay.removePlayerActionEvents)
	end
end
FSBaseMission.onStartMission = Utils.appendedFunction(FSBaseMission.onStartMission , Courseplay.addPlayerActionEvents)

--- Open/close mouse in player state.
function Courseplay.onOpenCloseMouseEvent(player, forceReset)
	if g_Courseplay.infoTextsHud:isVisible() and not player:hasHandtoolEquipped() then
		if forceReset or g_Courseplay.globalSettings.infoTextHudPlayerMouseActive:getValue() then
			local showMouseCursor = not g_inputBinding:getShowMouseCursor()
			g_inputBinding:setShowMouseCursor(showMouseCursor)
			local leftRightRotationEventId = player.inputInformation.registrationList[InputAction.AXIS_LOOK_LEFTRIGHT_PLAYER].eventId
			local upDownRotationEventId = player.inputInformation.registrationList[InputAction.AXIS_LOOK_UPDOWN_PLAYER].eventId
			g_inputBinding:setActionEventActive(leftRightRotationEventId, not showMouseCursor)
			g_inputBinding:setActionEventActive(upDownRotationEventId, not showMouseCursor)
			player.wasCpMouseActive = showMouseCursor
		end
	end
end

--- Enables/disables the player mouse action event, if there are any info texts.
function Courseplay.updatePlayerActionEvents(player)
	local eventId = player.inputInformation.registrationList[InputAction.CP_TOGGLE_MOUSE].eventId
	g_inputBinding:setActionEventTextVisibility(eventId, false)
	if not player:hasHandtoolEquipped() then
		if g_Courseplay.infoTextsHud:isVisible() and g_Courseplay.globalSettings.infoTextHudPlayerMouseActive:getValue() then 
			g_inputBinding:setActionEventTextVisibility(eventId, true)
		elseif player.wasCpMouseActive then 
			Courseplay.onOpenCloseMouseEvent(player, true)
		end
	end
end

--- Resets the mouse cursor on entering a vehicle for example.
function Courseplay.removePlayerActionEvents(player)
	if player.wasCpMouseActive then
		g_inputBinding:setShowMouseCursor(false)
	end
	player.wasCpMouseActive = nil
end

--- Registers all cp specializations.
---@param typeManager TypeManager
function Courseplay.register(typeManager)
	--- TODO: make this function async. 
	for typeName, typeEntry in pairs(typeManager.types) do	
		if CourseplaySpec.prerequisitesPresent(typeEntry.specializations) then
			typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".courseplaySpec")	
		end
		if CpVehicleSettings.prerequisitesPresent(typeEntry.specializations) then
			typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpVehicleSettings")	
		end
		if CpCourseGeneratorSettings.prerequisitesPresent(typeEntry.specializations) then
			typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpCourseGeneratorSettings")	
		end
		if CpCourseManager.prerequisitesPresent(typeEntry.specializations) then
			typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpCourseManager")	
		end
		CpAIWorker.register(typeManager, typeName, typeEntry.specializations)
		CpAIFieldWorker.register(typeManager, typeName, typeEntry.specializations)
		CpAIBaleFinder.register(typeManager, typeName, typeEntry.specializations)
		CpGamePadHud.register(typeManager, typeName,typeEntry.specializations)
		CpHud.register(typeManager, typeName, typeEntry.specializations)
		CpInfoTexts.register(typeManager, typeName, typeEntry.specializations)
	end
end
TypeManager.finalizeTypes = Utils.prependedFunction(TypeManager.finalizeTypes, Courseplay.register)

g_Courseplay = Courseplay()
addModEventListener(g_Courseplay)
