--[[
    DefaultVehiclesConfigurations

    Default Vehicle Configuration script for AgriBumper

	@author: 		BayernGamers
	@date: 			18.03.2025
	@version:		1.0

	History:		v1.0 @18.03.2025 - initial implementation in FS 25
                    ------------------------------------------------------------------------------------------------------
	
	License:        Terms:
                        Usage:
                            Feel free to use this work as-is as long as you adhere to the following terms:
						Attribution:
							You must give appropriate credit to the original author when using this work.
						No Derivatives:
							You may not alter, transform, or build upon this work in any way.
						Usage: 
							The work may be used for personal and commercial purposes, provided it is not modified or adapted.
						Additional Clause:
							This script may not be converted, adapted, or incorporated into any other game versions or platforms except by GIANTS Software.
]]
source(Utils.getFilename("scripts/utils/Logger.lua", g_currentModDirectory))

local log = Logger.new(true, Logger.DEBUG_LEVELS.HIGH, "DefaultVehiclesConfigurations.lua")

DefaultVehiclesConfigurations = {}
DefaultVehiclesConfigurations.MOD_DIR = g_currentModDirectory
DefaultVehiclesConfigurations.MOD_NAME = g_currentModName

DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH = "defaultVehicles.vehicle(?)"
DefaultVehiclesConfigurations.DEFAULT_VEHICLE_XML = Utils.getFilename("xml/defaultVehicles.xml", DefaultVehiclesConfigurations.MOD_DIR)
DefaultVehiclesConfigurations.DEFAULT_VEHICLE_SCHEMA = XMLSchema.new("agriBumperDefaultVehicles")

function DefaultVehiclesConfigurations.initSchema(schema)

	if schema == nil then
		schema = DefaultVehiclesConfigurations.DEFAULT_VEHICLE_SCHEMA
	end

    schema:setXMLSpecializationType("AgriBumperDefaultVehicles")

    schema:register(XMLValueType.STRING, DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH .. "#filename")
    local configBasePath = DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH .. ".agriBumperConfigurations"
    VehicleConfigurationItem.registerXMLPaths(schema, configBasePath, configBasePath .. ".agriBumperConfiguration(?)")
	VehicleConfigurationDataSize.registerXMLPaths(schema, nil, configBasePath .. ".agriBumperConfiguration(?)")
    StoreItemUtil.registerConfigurationSetXMLPaths(schema, DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH)

    schema:register(XMLValueType.STRING, configBasePath .. ".agriBumperConfiguration(?).agriBumper#rootNode")
    schema:register(XMLValueType.VECTOR_TRANS, configBasePath .. ".agriBumperConfiguration(?).agriBumper#translation")
    schema:register(XMLValueType.VECTOR_ROT, configBasePath .. ".agriBumperConfiguration(?).agriBumper#rotation")
    schema:register(XMLValueType.VECTOR_SCALE, configBasePath .. ".agriBumperConfiguration(?).agriBumper#scale")
    schema:register(XMLValueType.STRING, configBasePath .. ".agriBumperConfiguration(?).agriBumper#filename")

    schema:register(XMLValueType.INT, configBasePath .. ".agriBumperConfiguration(?).agriBumper.disableCollisions#attacherJointIndex", "Attacher joint which disables the collisions", 2)
    schema:register(XMLValueType.STRING, configBasePath .. ".agriBumperConfiguration(?).agriBumper.disableCollisions#onConfigurationActive", "Configuration which disables the collisions", nil)
    schema:register(XMLValueType.VECTOR_N, configBasePath .. ".agriBumperConfiguration(?).agriBumper.disableCollisions#configurationIndices", "Configuration indices which disables the collisions")
    schema:register(XMLValueType.NODE_INDEX, configBasePath .. ".agriBumperConfiguration(?).agriBumper.disableCollisions.disableCollision(?)#node", "Node index of the collision to disable", nil)

    schema:register(XMLValueType.NODE_INDEX, configBasePath .. ".agriBumperConfiguration(?).agriBumper.clearPTOs.clearPTO(?)#node", "Node index of the PTO to clear", nil)
    schema:register(XMLValueType.NODE_INDEX, configBasePath .. ".agriBumperConfiguration(?).agriBumper.clearPTOs.clearPTO(?)#outputNode", "Output node index of the PTO to clear", nil)

    schema:register(XMLValueType.STRING, configBasePath .. ".agriBumperConfiguration(?).agriBumper.toolBox#xmlFilename", "XML filename of the tool box", "toolBox/toolBox.xml")
    schema:register(XMLValueType.NODE_INDEX, configBasePath .. ".agriBumperConfiguration(?).agriBumper.toolBox#lockPositionNode", "Output node index of the PTO to clear", nil)

    schema:register(XMLValueType.STRING, configBasePath .. ".agriBumperConfiguration(?).agriBumper.attacherJointOverride#node")
    schema:register(XMLValueType.VECTOR_ROT, configBasePath .. ".agriBumperConfiguration(?).agriBumper.attacherJointOverride.bottomArm#startRotation")

    ObjectChangeUtil.registerObjectChangeXMLPaths(schema, configBasePath .. ".agriBumperConfiguration(?)")
    ObjectChangeUtil.addAdditionalObjectChangeXMLPaths(schema, function(_schema, key)
        _schema:register(XMLValueType.ANGLE, key .. "#movingToolRotMaxActive", "Moving tool max. rotation if object change active")
        _schema:register(XMLValueType.ANGLE, key .. "#movingToolRotMaxInactive", "Moving tool max. rotation if object change inactive")
        _schema:register(XMLValueType.ANGLE, key .. "#movingToolRotMinActive", "Moving tool min. rotation if object change active")
        _schema:register(XMLValueType.ANGLE, key .. "#movingToolRotMinInactive", "Moving tool min. rotation if object change inactive")

        _schema:register(XMLValueType.ANGLE, key .. "#movingToolStartRotActive", "Moving tool start rotation if object change inactive")
        _schema:register(XMLValueType.ANGLE, key .. "#movingToolStartRotInactive", "Moving tool start rotation if object change inactive")

        _schema:register(XMLValueType.FLOAT, key .. "#movingToolTransMaxActive", "Moving tool max. translation if object change active")
        _schema:register(XMLValueType.FLOAT, key .. "#movingToolTransMaxInactive", "Moving tool max. translation if object change inactive")
        _schema:register(XMLValueType.FLOAT, key .. "#movingToolTransMinActive", "Moving tool min. translation if object change active")
        _schema:register(XMLValueType.FLOAT, key .. "#movingToolTransMinInactive", "Moving tool min. translation if object change inactive")

        _schema:register(XMLValueType.FLOAT, key .. "#movingToolStartTransActive", "Moving tool start translation if object change inactive")
        _schema:register(XMLValueType.FLOAT, key .. "#movingToolStartTransInactive", "Moving tool start translation if object change inactive")

        _schema:register(XMLValueType.BOOL, key .. "#movingPartUpdateActive", "moving part active state if object change active")
        _schema:register(XMLValueType.BOOL, key .. "#movingPartUpdateInactive", "moving part active state if object change inactive")
    end)
    
    local colorConfigBasePath = DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH .. ".agriBumperColorConfigurations"
    VehicleConfigurationItemColor.registerXMLPaths(schema, colorConfigBasePath, colorConfigBasePath .. ".agriBumperColorConfiguration(?)")

    for i = 2, 3 do
        local colorConfigBasePath = DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH .. string.format(".agriBumperColor%dConfigurations", i)
        VehicleConfigurationItemColor.registerXMLPaths(schema, colorConfigBasePath, colorConfigBasePath .. string.format(".agriBumperColor%dConfiguration(?)", i))
    end

    schema:setXMLSpecializationType()

	return schema
end

function DefaultVehiclesConfigurations.getConfigurationsFromXML(manager, xmlFile, key, baseDir, customEnvironment, isMod, storeItem)
    local configurations = {}
    local defaultConfigurationIds = {}
    local numConfigs = 0

    local configurationDescs = manager:getConfigurations()
    for _, configurationDesc in pairs(configurationDescs) do

        if configurationDesc.configurationsKey ~= nil and configurationDesc.configurationsKey:find("agriBumper") ~= nil then
            local configurationItems = {}

            local configDescConfigsKey = configurationDesc.configurationsKey:gsub("vehicle.", key .. ".")
            local configDescConfigKey = configurationDesc.configurationKey:gsub("vehicle.", key .. ".")

            if configurationDesc.itemClass.preLoad ~= nil then
                configurationDesc.itemClass.preLoad(xmlFile, configDescConfigsKey, baseDir, customEnvironment, isMod, configurationItems)
            end

            local i = 0
            while true do
                if i > 2 ^ ConfigurationUtil.SEND_NUM_BITS then
                    Logging.xmlWarning(xmlFile, "Maximum number of configurations are reached for %s. Only %d configurations per type are allowed!", configurationDesc.name, 2 ^ ConfigurationUtil.SEND_NUM_BITS)
                end
                local configKey = string.format(configDescConfigKey .."(%d)", i)
                if not xmlFile:hasProperty(configKey) then
                    break
                end

                local configItem = configurationDesc.itemClass.new(configurationDesc.name)
                configItem:setIndex(#configurationItems + 1)
                if configItem:loadFromXML(xmlFile, configDescConfigsKey, configKey, baseDir, customEnvironment) then
                    table.insert(configurationItems, configItem)
                end

                i = i + 1
            end

            if configurationDesc.itemClass.postLoad ~= nil then
                configurationDesc.itemClass.postLoad(xmlFile, configDescConfigsKey, baseDir, customEnvironment, isMod, configurationItems, storeItem, configurationDesc.name)
            end

            if #configurationItems > 0 then
                defaultConfigurationIds[configurationDesc.name] = ConfigurationUtil.getDefaultConfigIdFromItems(configurationItems)

                configurations[configurationDesc.name] = configurationItems
                numConfigs = numConfigs + 1
            end
        end
    end

    if numConfigs == 0 then
        return nil, nil
    end

    return configurations, defaultConfigurationIds
end

function DefaultVehiclesConfigurations.getMatchingDefaultVehicleKey(defaultVehiclesXML, xmlFilename, resolvedFilename)
    if defaultVehiclesXML == nil or xmlFilename == nil then
        return nil
    end

    local pattern = ".*pdlc"
    local xmlDLCFilename = nil
    local filenameForDlc = resolvedFilename ~= nil and resolvedFilename or xmlFilename

    if filenameForDlc:find("pdlc") ~= nil then
        xmlDLCFilename = "pdlc" .. filenameForDlc:gsub(pattern, "")
    end

    local foundKey = nil

    defaultVehiclesXML:iterate(DefaultVehiclesConfigurations.DEFAULT_VEHICLE_BASE_PATH:sub(1, -4), function (_, key)
        local filename = defaultVehiclesXML:getString(key .. "#filename")

        if filename ~= nil then
            local dlcFilename = nil
            if filename:find("pdlc") ~= nil then
                dlcFilename = "pdlc" .. filename:gsub(pattern, "")
            end

            filename = Utils.getFilename(filename)

            if xmlFilename:find(filename) ~= nil or (dlcFilename ~= nil and xmlDLCFilename ~= nil and xmlDLCFilename == dlcFilename) then
                foundKey = key
                return false
            end
        end
    end)

    return foundKey
end

function DefaultVehiclesConfigurations.loadItem(storeItem, self, rawXMLFilename, baseDir, customEnvironment, isMod, isBundleItem, dlcTitle, extraContentId, ignoreAdd)
	local defaultVehiclesXML = XMLFile.loadIfExists("agriBumperDefaultVehicles", DefaultVehiclesConfigurations.DEFAULT_VEHICLE_XML, DefaultVehiclesConfigurations.DEFAULT_VEHICLE_SCHEMA)

    if defaultVehiclesXML ~= nil then
        local fullXMLFilename = nil
        if baseDir ~= nil and baseDir ~= "" then
            fullXMLFilename = Utils.getFilename(rawXMLFilename, baseDir)
        end

        log:printDevInfo("baseDir: " .. tostring(baseDir), Logger.DEBUG_LEVELS.LOW)
        log:printDevInfo("rawXMLFilename: " .. tostring(rawXMLFilename), Logger.DEBUG_LEVELS.LOW)

        local key = DefaultVehiclesConfigurations.getMatchingDefaultVehicleKey(defaultVehiclesXML, rawXMLFilename, fullXMLFilename)

        if key ~= nil then
			local configurations, defaultConfigurationIds = DefaultVehiclesConfigurations.getConfigurationsFromXML(g_vehicleConfigurationManager, defaultVehiclesXML, key, baseDir, customEnvironment, isMod, storeItem)

			if configurations ~= nil then
				for config, configTable in pairs(configurations) do
                    storeItem.configurations[config] = configTable
                end
			end

			if defaultConfigurationIds ~= nil then
                for config, id in pairs(defaultConfigurationIds) do
                    storeItem.defaultConfigurationIds[config] = id
                end
			end

            local configurationSets = ConfigurationUtil.getConfigurationSetsFromXML(storeItem, defaultVehiclesXML, key, baseDir, customEnvironment, isMod)

            if configurationSets ~= nil and #configurationSets > 0 then
                storeItem.configurationSets = configurationSets
            end
		end

        defaultVehiclesXML:delete()
	end

	return storeItem
end

function DefaultVehiclesConfigurations.getSizeValuesFromXMLByKey(xmlFilename, superFunc, xmlFile, baseName, baseKey, elementKey, configKey, rotationOffset, configurations, defaults)
    local baseSizeKey = string.format("%s.%s.%s", baseName, baseKey, elementKey)
	local originalXMLFile = xmlFile
    local defaultVehicleXMLFile = nil

    local size = {
        width = xmlFile:getValue(baseSizeKey .. "#width", defaults.width),
        length = xmlFile:getValue(baseSizeKey .. "#length", defaults.length),
        height = xmlFile:getValue(baseSizeKey .. "#height", defaults.height),
        widthOffset = xmlFile:getValue(baseSizeKey .. "#widthOffset", defaults.widthOffset),
        lengthOffset = xmlFile:getValue(baseSizeKey .. "#lengthOffset", defaults.lengthOffset),
        heightOffset = xmlFile:getValue(baseSizeKey .. "#heightOffset", defaults.heightOffset)
    }

    if configurations ~= nil then
        for name, id in pairs(configurations) do
            local configItem = ConfigurationUtil.getConfigItemByConfigId(xmlFilename, name, id)
            if configItem ~= nil and configItem.onSizeLoad ~= nil then

				if configItem.configKey:find("defaultVehicles") ~= nil and configItem.configKey:find("agriBumper") ~= nil then
                    if defaultVehicleXMLFile == nil then
                        defaultVehicleXMLFile = XMLFile.loadIfExists("agriBumperDefaultVehicles", DefaultVehiclesConfigurations.DEFAULT_VEHICLE_XML, DefaultVehiclesConfigurations.DEFAULT_VEHICLE_SCHEMA)
                    end

                    xmlFile = defaultVehicleXMLFile ~= nil and defaultVehicleXMLFile or originalXMLFile
				else
					xmlFile = originalXMLFile
				end

                configItem.onSizeLoad(configItem, xmlFile, size)
            end
        end
    end

    rotationOffset = math.floor(rotationOffset / math.rad(90) + 0.5) * math.rad(90)
    rotationOffset = rotationOffset % (2*math.pi)
    if rotationOffset < 0 then
        rotationOffset = rotationOffset + 2*math.pi
    end

    local rotationIndex = math.floor(rotationOffset / math.rad(90) + 0.5)
    if rotationIndex == 1 then
        size.width, size.length = size.length, size.width
        size.widthOffset,size.lengthOffset = size.lengthOffset, -size.widthOffset
    elseif rotationIndex == 2 then
        size.widthOffset, size.lengthOffset = -size.widthOffset, -size.lengthOffset
    elseif rotationIndex == 3 then
        size.width, size.length = size.length, size.width
        size.widthOffset, size.lengthOffset = -size.lengthOffset, size.widthOffset
    end

	if defaultVehicleXMLFile ~= nil then
		defaultVehicleXMLFile:delete()
	end

    return size
end

function DefaultVehiclesConfigurations:onLoad(superFunc, object, configId)
    local originalConfigKey = self.configKey
    for _, data in ipairs(VehicleConfigurationItem.GLOBAL_DATA) do
        if data.onLoad ~= nil then
            
            if self.configKey:find("defaultVehicles") ~= nil and self.configKey:find("agriBumper") ~= nil then
                self.configKey = ""
            else
                self.configKey = originalConfigKey
            end

            data.onLoad(object, self, configId)
        end
    end
end

function DefaultVehiclesConfigurations:onPostLoadColors(object, configId)
    VehicleConfigurationItemColor:superClass().onPostLoad(self, object, configId)

    local configurationDesc = g_vehicleConfigurationManager:getConfigurationDescByName(self.configName)

    local defaultVehiclesXML = XMLFile.loadIfExists("agriBumperDefaultVehicles", DefaultVehiclesConfigurations.DEFAULT_VEHICLE_XML, DefaultVehiclesConfigurations.DEFAULT_VEHICLE_SCHEMA)

    if defaultVehiclesXML ~= nil then
        local key = DefaultVehiclesConfigurations.getMatchingDefaultVehicleKey(defaultVehiclesXML, object.xmlFile.filename, object.xmlFile.filename)

        if key ~= nil then
            local configDescConfigsKey = configurationDesc.configurationsKey:gsub("vehicle.", key .. ".")

            defaultVehiclesXML:iterate(configDescConfigsKey .. ".material", function(index, materialKey)
                local material = VehicleMaterial.new(object.baseDirectory)
        
                local color, materialTemplateName = self:getColorAndMaterialFromVehicle(object)
        
                if not defaultVehiclesXML:getValue(materialKey .. "#materialTemplateUseColorOnly", false) then
                    material:setTemplateName(materialTemplateName, nil, object.customEnvironment)
                end
        
                material:setColor(color)
                material:loadFromXML(defaultVehiclesXML, materialKey, object.customEnvironment)
                if material.targetMaterialSlotName ~= nil then
                    if not material:applyToVehicle(object) then
                        if materialKey:find("agriBumperColor") == nil then
                            Logging.xmlWarning(defaultVehiclesXML, "Failed to find material by material slot name '%s' in '%s'", material.targetMaterialSlotName, materialKey)
                        else
                            log:printDevXMLWarning(defaultVehiclesXML, string.format("Failed to find material by material slot name '%s' in '%s'", material.targetMaterialSlotName, materialKey), Logger.DEBUG_LEVELS.LOW)
                        end
                    end
                else
                    Logging.xmlWarning(defaultVehiclesXML, "Missing material slot name in '%s'", materialKey)
                end
            end)
        end

        defaultVehiclesXML:delete()
    end         
end

function DefaultVehiclesConfigurations.appendedFunctionUseReturnValue(func, appendedFunc)
	return function(...)
		local ret = func(...)
		if ret ~= nil then
			return appendedFunc(ret, ...)
		end
	end
end

DefaultVehiclesConfigurations.initSchema()
StoreManager.loadItem = DefaultVehiclesConfigurations.appendedFunctionUseReturnValue(StoreManager.loadItem, DefaultVehiclesConfigurations.loadItem)
StoreItemUtil.getSizeValuesFromXMLByKey = Utils.overwrittenFunction(StoreItemUtil.getSizeValuesFromXMLByKey, DefaultVehiclesConfigurations.getSizeValuesFromXMLByKey)
VehicleConfigurationItem.onLoad = Utils.overwrittenFunction(VehicleConfigurationItem.onLoad, DefaultVehiclesConfigurations.onLoad)
VehicleConfigurationItemColor.onPostLoad = Utils.appendedFunction(VehicleConfigurationItemColor.onPostLoad, DefaultVehiclesConfigurations.onPostLoadColors)