-- BaleFermentation_Grass_fermented.lua
-- Version 1.0.0.0
-- Autor: Tidde23

AnimalFoodOverride = {}
AnimalFoodOverride.MOD_NAME = g_currentModName or "AdvancedSilageTypes"
AnimalFoodOverride.DEBUG = false

AnimalFoodOverride.FILLTYPE_MAPPINGS = {
    { addFillType = "GRASS_FERMENTED",              baseFillType = "SILAGE" },
    { addFillType = "CCM",                          baseFillType = "SILAGE" },
    { addFillType = "CHOPPEDMAIZE_FERMENTED",       baseFillType = "SILAGE" },
    { addFillType = "PROTEINGRIST",                 baseFillType = "SUNFLOWER",     animalTypes = { "PIG" } },
    { addFillType = "GRAINGRIST",                   baseFillType = "MINERAL_FEED",  animalTypes = { "COW" } },
    { addFillType = "CCM",                          baseFillType = "SORGHUM",       animalTypes = { "PIG" } },
}

function AnimalFoodOverride.init()
    Mission00.loadMission00Finished = Utils.appendedFunction(Mission00.loadMission00Finished, AnimalFoodOverride.onMissionLoaded)
end

local function logDebug(...)
    if AnimalFoodOverride.DEBUG then
        Logging.info(...)
    end
end

local function hasFillType(fillTypes, fillTypeIndex)
    if fillTypes == nil or fillTypeIndex == nil then
        return false
    end

    for _, existingFillTypeIndex in ipairs(fillTypes) do
        if existingFillTypeIndex == fillTypeIndex then
            return true
        end
    end

    return false
end

local function addFillTypeAfterBase(fillTypes, baseFillTypeIndex, fillTypeIndexToAdd)
    if fillTypes == nil or baseFillTypeIndex == nil or fillTypeIndexToAdd == nil then
        return false
    end

    if hasFillType(fillTypes, fillTypeIndexToAdd) then
        return false
    end

    for index, existingFillTypeIndex in ipairs(fillTypes) do
        if existingFillTypeIndex == baseFillTypeIndex then
            table.insert(fillTypes, index + 1, fillTypeIndexToAdd)
            return true
        end
    end

    return false
end

local function patchIngredientList(ingredients, baseFillTypeIndex, fillTypeIndexToAdd)
    local changes = 0

    if ingredients ~= nil then
        for _, ingredient in ipairs(ingredients) do
            if addFillTypeAfterBase(ingredient.fillTypes, baseFillTypeIndex, fillTypeIndexToAdd) then
                changes = changes + 1
            end
        end
    end

    return changes
end

local function patchAnimalFoodGroups(animalFoodSystem, baseFillTypeIndex, fillTypeIndexToAdd)
    local changes = 0

    if animalFoodSystem.animalFood ~= nil then
        for _, animalFood in ipairs(animalFoodSystem.animalFood) do
            if animalFood.groups ~= nil then
                for _, foodGroup in ipairs(animalFood.groups) do
                    if addFillTypeAfterBase(foodGroup.fillTypes, baseFillTypeIndex, fillTypeIndexToAdd) then
                        changes = changes + 1
                    end
                end
            end
        end
    end

    return changes
end

local function patchAnimalFoodGroupsFiltered(animalFoodSystem, baseFillTypeIndex, fillTypeIndexToAdd, allowedIndexSet)
    local changes = 0

    if animalFoodSystem.animalFood ~= nil then
        for _, animalFood in ipairs(animalFoodSystem.animalFood) do
            if allowedIndexSet[animalFood.animalTypeIndex] and animalFood.groups ~= nil then
                for _, foodGroup in ipairs(animalFood.groups) do
                    if addFillTypeAfterBase(foodGroup.fillTypes, baseFillTypeIndex, fillTypeIndexToAdd) then
                        changes = changes + 1
                    end
                end
            end
        end
    end

    return changes
end

local function patchMixtures(animalFoodSystem, baseFillTypeIndex, fillTypeIndexToAdd)
    local changes = 0
    local visited = {}

    if animalFoodSystem.mixtureFillTypeIndexToMixture ~= nil then
        for _, mixture in pairs(animalFoodSystem.mixtureFillTypeIndexToMixture) do
            if mixture ~= nil and not visited[mixture] then
                visited[mixture] = true
                changes = changes + patchIngredientList(mixture.ingredients, baseFillTypeIndex, fillTypeIndexToAdd)
            end
        end
    end

    if animalFoodSystem.mixtures ~= nil then
        for _, mixture in pairs(animalFoodSystem.mixtures) do
            if mixture ~= nil and not visited[mixture] then
                visited[mixture] = true
                changes = changes + patchIngredientList(mixture.ingredients, baseFillTypeIndex, fillTypeIndexToAdd)
            end
        end
    end

    return changes
end

local function patchRecipes(animalFoodSystem, baseFillTypeIndex, fillTypeIndexToAdd)
    local changes = 0
    local visited = {}

    if animalFoodSystem.recipeFillTypeIndexToRecipe ~= nil then
        for _, recipe in pairs(animalFoodSystem.recipeFillTypeIndexToRecipe) do
            if recipe ~= nil and not visited[recipe] then
                visited[recipe] = true
                changes = changes + patchIngredientList(recipe.ingredients, baseFillTypeIndex, fillTypeIndexToAdd)
            end
        end
    end

    if animalFoodSystem.recipes ~= nil then
        for _, recipe in pairs(animalFoodSystem.recipes) do
            if recipe ~= nil and not visited[recipe] then
                visited[recipe] = true
                changes = changes + patchIngredientList(recipe.ingredients, baseFillTypeIndex, fillTypeIndexToAdd)
            end
        end
    end

    return changes
end

local function updateFoodGroupLookup(animalFoodSystem, fillTypeIndexToAdd)
    if animalFoodSystem.fillTypeToFoodGroup == nil or animalFoodSystem.animalFood == nil then
        return 0
    end

    local changes = 0

    for _, animalFood in ipairs(animalFoodSystem.animalFood) do
        local animalTypeIndex = animalFood.animalTypeIndex

        if animalTypeIndex ~= nil and animalFood.groups ~= nil then
            local lookup = animalFoodSystem.fillTypeToFoodGroup[animalTypeIndex]

            if lookup == nil then
                lookup = {}
                animalFoodSystem.fillTypeToFoodGroup[animalTypeIndex] = lookup
            end

            for _, foodGroup in ipairs(animalFood.groups) do
                if hasFillType(foodGroup.fillTypes, fillTypeIndexToAdd) and lookup[fillTypeIndexToAdd] ~= foodGroup then
                    lookup[fillTypeIndexToAdd] = foodGroup
                    changes = changes + 1
                end
            end
        end
    end

    return changes
end

local function buildNameToIndexMap(animalFoodSystem)
    if animalFoodSystem == nil or animalFoodSystem.animalFood == nil then
        return nil
    end

    if AnimalFoodOverride.DEBUG then
        local entries = {}
        for i, animalFood in ipairs(animalFoodSystem.animalFood) do
            table.insert(entries, "entry[" .. i .. "].animalTypeIndex=" .. tostring(animalFood.animalTypeIndex))
        end
        logDebug("[%s] animalFood indices: %s", AnimalFoodOverride.MOD_NAME, table.concat(entries, ", "))
    end
    local knownOrder = { "COW", "SHEEP", "PIG", "HORSE", "CHICKEN" }
    local map = {}
    for position, animalFood in ipairs(animalFoodSystem.animalFood) do
        local typeName = knownOrder[position]
        local idx = animalFood.animalTypeIndex
        if typeName ~= nil and idx ~= nil then
            map[typeName] = idx
        end
    end

    if next(map) ~= nil then
        return map
    end

    return nil
end

function AnimalFoodOverride.patchAnimalFoodSystem(animalFoodSystem)
    if animalFoodSystem == nil then
        Logging.warning("[%s] AnimalFoodSystem is nil, skipping runtime extension", AnimalFoodOverride.MOD_NAME)
        return false
    end

    local anyChange = false

    for _, mapping in ipairs(AnimalFoodOverride.FILLTYPE_MAPPINGS) do
        local addName     = mapping.addFillType
        local baseName    = mapping.baseFillType
        local animalTypes = mapping.animalTypes 

        local baseIndex = g_fillTypeManager:getFillTypeIndexByName(baseName)
        local addIndex  = g_fillTypeManager:getFillTypeIndexByName(addName)

        if baseIndex == nil then
            Logging.warning("[%s] Base FillType '%s' is not registered, skipping mapping for '%s'",
                AnimalFoodOverride.MOD_NAME, baseName, addName)

        elseif addIndex == nil then
            logDebug("[%s] FillType '%s' is not registered, skipping mapping", AnimalFoodOverride.MOD_NAME, addName)

        elseif animalTypes ~= nil then
            local nameToIndex = buildNameToIndexMap(animalFoodSystem)
            local allowedIndexSet = {}

            if nameToIndex ~= nil then
                for _, typeName in ipairs(animalTypes) do
                    local idx = nameToIndex[typeName:upper()]
                    if idx ~= nil then
                        allowedIndexSet[idx] = true
                    else
                        Logging.warning("[%s] animalTypes filter: '%s' not found", AnimalFoodOverride.MOD_NAME, typeName)
                    end
                end
            else
                Logging.warning("[%s] Cannot resolve animal type names, applying '%s' to all animals",
                    AnimalFoodOverride.MOD_NAME, addName)
            end

            local animalFilter = table.concat(animalTypes, ",")
            local foodGroupChanges
            if next(allowedIndexSet) ~= nil then
                foodGroupChanges = patchAnimalFoodGroupsFiltered(animalFoodSystem, baseIndex, addIndex, allowedIndexSet)
            else
                foodGroupChanges = patchAnimalFoodGroups(animalFoodSystem, baseIndex, addIndex)
            end
            local mixtureChanges = patchMixtures(animalFoodSystem, baseIndex, addIndex)
            local recipeChanges  = patchRecipes(animalFoodSystem, baseIndex, addIndex)
            local lookupChanges  = updateFoodGroupLookup(animalFoodSystem, addIndex)

            if foodGroupChanges > 0 or mixtureChanges > 0 or recipeChanges > 0 or lookupChanges > 0 then
                logDebug("[%s] Added '%s' after '%s' (animals: %s): foodGroups=%d, mixtures=%d, recipes=%d, lookups=%d",
                    AnimalFoodOverride.MOD_NAME, addName, baseName, animalFilter,
                    foodGroupChanges, mixtureChanges, recipeChanges, lookupChanges)
                anyChange = true
            else
                logDebug("[%s] No changes for '%s' after '%s' (animals: %s)",
                    AnimalFoodOverride.MOD_NAME, addName, baseName, animalFilter)
            end


        else
            local foodGroupChanges = patchAnimalFoodGroups(animalFoodSystem, baseIndex, addIndex)
            local mixtureChanges   = patchMixtures(animalFoodSystem, baseIndex, addIndex)
            local recipeChanges    = patchRecipes(animalFoodSystem, baseIndex, addIndex)
            local lookupChanges    = updateFoodGroupLookup(animalFoodSystem, addIndex)

            if foodGroupChanges > 0 or mixtureChanges > 0 or recipeChanges > 0 or lookupChanges > 0 then
                logDebug("[%s] Added '%s' after '%s' (animals: all): foodGroups=%d, mixtures=%d, recipes=%d, lookups=%d",
                    AnimalFoodOverride.MOD_NAME, addName, baseName,
                    foodGroupChanges, mixtureChanges, recipeChanges, lookupChanges)
                anyChange = true
            else
                logDebug("[%s] No changes required for '%s' after '%s'",
                    AnimalFoodOverride.MOD_NAME, addName, baseName)
            end
        end
    end

    return anyChange
end

function AnimalFoodOverride.onMissionLoaded(mission, ...)
    local animalFoodSystem = mission ~= nil and mission.animalFoodSystem or nil
    local anyChange, _ = AnimalFoodOverride.patchAnimalFoodSystem(animalFoodSystem)

    if anyChange and animalFoodSystem ~= nil then
        local originalStart = mission.startMission
        mission.startMission = function(m, ...)
            local result = originalStart(m, ...)
            local animalSystem = m.animalSystem
            if animalSystem ~= nil and animalSystem.animalTypes ~= nil then
                for _, animalType in pairs(animalSystem.animalTypes) do
                    animalFoodSystem:assignFoodToAnimalType(animalType)
                end
                logDebug("[%s] Reassigned active animalFood runtime data to animal types", AnimalFoodOverride.MOD_NAME)
            end
            return result
        end
    end
end

AnimalFoodOverride.init()
