-- NH3TankAwareCultivator.lua
-- Version 1.2.1.2
-- Tank-aware helper for Behnke TB17/TB37/TB60 NH3 toolbars.
--
-- Goal:
--   No NH3 tank attached: behave like a normal cultivator under Giants AI/Courseplay.
--   NH3 tank attached: keep NH3/fertilizing behavior and auto-turn-on for AI/CP.
--
-- Important: this targets the normal "Fertilizing cultivator" configuration, not the
-- optional "Side dresser" configuration.

NH3TankAwareCultivator = NH3TankAwareCultivator or {}
NH3TankAwareCultivator.debug = false
NH3TankAwareCultivator.updateIntervalMs = 400
NH3TankAwareCultivator.hooks = NH3TankAwareCultivator.hooks or {}

local function log(fmt, ...)
    if NH3TankAwareCultivator.debug then
        print(('[NH3TankAwareCultivator] ' .. fmt):format(...))
    end
end

local function lowerText(v)
    return v ~= nil and string.lower(tostring(v)) or ''
end

function NH3TankAwareCultivator.getConfigFileName(vehicle)
    if vehicle == nil then
        return ''
    end
    return lowerText(vehicle.configFileName or vehicle.xmlFilename or vehicle.filename or '')
end

function NH3TankAwareCultivator.isToolbar(vehicle)
    local f = NH3TankAwareCultivator.getConfigFileName(vehicle)
    return f:find('behnketb17cultivator', 1, true) ~= nil
        or f:find('behnketb37cultivator', 1, true) ~= nil
        or f:find('behnketb60cultivator', 1, true) ~= nil
end

function NH3TankAwareCultivator.isKnownTank(vehicle)
    if vehicle == nil then
        return false
    end

    local f = NH3TankAwareCultivator.getConfigFileName(vehicle)
    if f:find('behnkefw1000', 1, true) ~= nil
        or f:find('behnkewg2000', 1, true) ~= nil
        or f:find('behnkegn4000', 1, true) ~= nil then
        return true
    end

    -- Generic fallback for other compatible NH3 tanks: any attached implement with a
    -- real-capacity ANHYDROUS fill unit counts. The toolbar's own zero-capacity
    -- ANHYDROUS fill unit is ignored by the capacity > 1 check.
    local anhydrousIndex = nil
    if g_fillTypeManager ~= nil then
        anhydrousIndex = g_fillTypeManager:getFillTypeIndexByName('ANHYDROUS')
    end

    if anhydrousIndex ~= nil and vehicle.spec_fillUnit ~= nil and vehicle.spec_fillUnit.fillUnits ~= nil then
        for index, fillUnit in pairs(vehicle.spec_fillUnit.fillUnits) do
            local capacity = fillUnit ~= nil and fillUnit.capacity or 0
            local supports = false
            if vehicle.getFillUnitSupportsFillType ~= nil then
                local ok, result = pcall(vehicle.getFillUnitSupportsFillType, vehicle, index, anhydrousIndex)
                supports = ok and result == true
            elseif fillUnit ~= nil and fillUnit.supportedFillTypes ~= nil then
                supports = fillUnit.supportedFillTypes[anhydrousIndex] == true
            end

            if supports and capacity > 1 then
                return true
            end
        end
    end

    return false
end

local function scanAttached(vehicle, depth, seen)
    if vehicle == nil or depth > 8 then
        return false
    end

    seen = seen or {}
    if seen[vehicle] then
        return false
    end
    seen[vehicle] = true

    if NH3TankAwareCultivator.isKnownTank(vehicle) then
        return true
    end

    if vehicle.getAttachedImplements ~= nil then
        local attached = vehicle:getAttachedImplements() or {}
        for _, implement in pairs(attached) do
            local object = implement.object or implement
            if scanAttached(object, depth + 1, seen) then
                return true
            end
        end
    end

    if vehicle.spec_attacherJoints ~= nil and vehicle.spec_attacherJoints.attachedImplements ~= nil then
        for _, implement in pairs(vehicle.spec_attacherJoints.attachedImplements) do
            local object = implement.object or implement
            if scanAttached(object, depth + 1, seen) then
                return true
            end
        end
    end

    return false
end

function NH3TankAwareCultivator.hasTankAttached(toolbar)
    if not NH3TankAwareCultivator.isToolbar(toolbar) then
        return false
    end

    -- Only look behind/under the toolbar. The toolbar itself has a zero-capacity
    -- ANHYDROUS fill unit and must not count as its own tank.
    if toolbar.getAttachedImplements ~= nil then
        for _, implement in pairs(toolbar:getAttachedImplements() or {}) do
            local object = implement.object or implement
            if scanAttached(object, 1, {}) then
                return true
            end
        end
    end

    if toolbar.spec_attacherJoints ~= nil and toolbar.spec_attacherJoints.attachedImplements ~= nil then
        for _, implement in pairs(toolbar.spec_attacherJoints.attachedImplements) do
            local object = implement.object or implement
            if scanAttached(object, 1, {}) then
                return true
            end
        end
    end

    return false
end

function NH3TankAwareCultivator.isPlainCultivatorMode(vehicle)
    return NH3TankAwareCultivator.isToolbar(vehicle)
       and not NH3TankAwareCultivator.hasTankAttached(vehicle)
end

local function boolBypass(classTable, hookKey, methodName, valueWhenPlain)
    if NH3TankAwareCultivator.hooks[hookKey] then
        return
    end
    if classTable ~= nil and classTable[methodName] ~= nil and Utils ~= nil and Utils.overwrittenFunction ~= nil then
        classTable[methodName] = Utils.overwrittenFunction(classTable[methodName], function(vehicle, superFunc, ...)
            if NH3TankAwareCultivator.isPlainCultivatorMode(vehicle) then
                return valueWhenPlain
            end
            return superFunc(vehicle, ...)
        end)
        NH3TankAwareCultivator.hooks[hookKey] = true
        log('installed hook %s.%s', hookKey, methodName)
    end
end

function NH3TankAwareCultivator.installHooks()
    -- Giants/base sprayer and fertilizing cultivator gates.
    boolBypass(Sprayer, 'Sprayer_getIsSprayerReadyToSpray', 'getIsSprayerReadyToSpray', true)
    boolBypass(Sprayer, 'Sprayer_getNeedsToBeFilled', 'getNeedsToBeFilled', false)
    boolBypass(Sprayer, 'Sprayer_getIsEmpty', 'getIsEmpty', false)
    boolBypass(Sprayer, 'Sprayer_getIsFillUnitEmpty', 'getIsFillUnitEmpty', false)
    boolBypass(Sprayer, 'Sprayer_getCanAIWorkerContinueWork', 'getCanAIWorkerContinueWork', true)
    boolBypass(Sprayer, 'Sprayer_getCanStartAIVehicle', 'getCanStartAIVehicle', true)
    boolBypass(Sprayer, 'Sprayer_getCanStartAIWorker', 'getCanStartAIWorker', true)
    boolBypass(Sprayer, 'Sprayer_getCanBeTurnedOn', 'getCanBeTurnedOn', true)

    -- Critical Courseplay compatibility: Courseplay's FertilizingCultivatorController
    -- stops CP when sprayFillLevel is 0 AND getIsSprayerExternallyFilled() is false.
    -- In no-tank/plain-cultivator mode we return true here only to prevent that
    -- stop; the sprayer fill level is forced to 0 below, so it does not apply free NH3.
    boolBypass(Sprayer, 'Sprayer_getIsSprayerExternallyFilled', 'getIsSprayerExternallyFilled', true)

    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getNeedsToBeFilled', 'getNeedsToBeFilled', false)
    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getIsEmpty', 'getIsEmpty', false)
    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getCanAIWorkerContinueWork', 'getCanAIWorkerContinueWork', true)
    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getCanStartAIVehicle', 'getCanStartAIVehicle', true)
    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getCanStartAIWorker', 'getCanStartAIWorker', true)
    boolBypass(FertilizingCultivator, 'FertilizingCultivator_getCanBeTurnedOn', 'getCanBeTurnedOn', true)

    if not NH3TankAwareCultivator.hooks.Sprayer_processSprayerArea
        and Sprayer ~= nil and Sprayer.processSprayerArea ~= nil then
        Sprayer.processSprayerArea = Utils.overwrittenFunction(Sprayer.processSprayerArea, function(vehicle, superFunc, workArea, dt, ...)
            if NH3TankAwareCultivator.isPlainCultivatorMode(vehicle) then
                return 0, 0
            end
            return superFunc(vehicle, workArea, dt, ...)
        end)
        NH3TankAwareCultivator.hooks.Sprayer_processSprayerArea = true
    end

    if not NH3TankAwareCultivator.hooks.FertilizingCultivator_processCultivatorArea
        and FertilizingCultivator ~= nil and FertilizingCultivator.processCultivatorArea ~= nil then
        FertilizingCultivator.processCultivatorArea = Utils.overwrittenFunction(FertilizingCultivator.processCultivatorArea, function(vehicle, superFunc, ...)
            if NH3TankAwareCultivator.isPlainCultivatorMode(vehicle) then
                -- Courseplay has its own FertilizingCultivatorController wrapper.
                -- That wrapper checks the CP fertilizer setting before the base cultivator
                -- work is processed. In no-tank mode we temporarily make CP see
                -- fertilizer as disabled, so it keeps cultivating instead of stopping
                -- for an empty NH3/fertilizer fill unit.
                local specSpray = vehicle.spec_sprayer
                if specSpray ~= nil and specSpray.workAreaParameters ~= nil then
                    specSpray.workAreaParameters.sprayFillLevel = 0
                    if FillType ~= nil and FillType.UNKNOWN ~= nil then
                        specSpray.workAreaParameters.sprayFillType = FillType.UNKNOWN
                    end
                end

                local rootVehicle = vehicle.rootVehicle
                local setting = nil
                local oldGetValue = nil
                if rootVehicle ~= nil and rootVehicle.getCpSettings ~= nil then
                    local ok, settings = pcall(rootVehicle.getCpSettings, rootVehicle)
                    if ok and settings ~= nil then
                        setting = settings.sowingMachineFertilizerEnabled
                    end
                end

                if setting ~= nil and setting.getValue ~= nil then
                    oldGetValue = setting.getValue
                    setting.getValue = function() return false end
                end

                local results = { pcall(superFunc, vehicle, ...) }

                if setting ~= nil and oldGetValue ~= nil then
                    setting.getValue = oldGetValue
                end

                if results[1] then
                    table.remove(results, 1)
                    return unpack(results)
                else
                    print('[NH3TankAwareCultivator] processCultivatorArea error: ' .. tostring(results[2]))
                    return 0, 0
                end
            end
            return superFunc(vehicle, ...)
        end)
        NH3TankAwareCultivator.hooks.FertilizingCultivator_processCultivatorArea = true
    end

    -- Courseplay classes are loaded by the Courseplay mod, so install these lazily
    -- after all mods have initialized. Without this, source load order can make the
    -- hooks silently miss Courseplay.
    if not NH3TankAwareCultivator.hooks.CP_SprayerController_needsRefilling
        and SprayerController ~= nil and SprayerController.needsRefilling ~= nil then
        SprayerController.needsRefilling = Utils.overwrittenFunction(SprayerController.needsRefilling, function(controller, superFunc, ...)
            if controller ~= nil and NH3TankAwareCultivator.isPlainCultivatorMode(controller.sprayer or controller.implement) then
                return false
            end
            return superFunc(controller, ...)
        end)
        NH3TankAwareCultivator.hooks.CP_SprayerController_needsRefilling = true
    end
end

local function getRootVehicle(vehicle)
    if vehicle ~= nil and vehicle.getRootVehicle ~= nil then
        local ok, root = pcall(vehicle.getRootVehicle, vehicle)
        if ok and root ~= nil then
            return root
        end
    end
    return vehicle
end

local function isUnderAI(vehicle)
    local root = getRootVehicle(vehicle)
    if root == nil then
        return false
    end

    if root.getIsAIActive ~= nil then
        local ok, active = pcall(root.getIsAIActive, root)
        if ok and active then
            return true
        end
    end

    if root.spec_aiVehicle ~= nil then
        local spec = root.spec_aiVehicle
        if spec.isActive or spec.isStarted or spec.aiDriveStrategy ~= nil or spec.driveStrategy ~= nil then
            return true
        end
    end

    if root.getIsCpActive ~= nil then
        local ok, active = pcall(root.getIsCpActive, root)
        if ok and active then
            return true
        end
    end

    if root.getCpDriveStrategy ~= nil then
        local ok, strategy = pcall(root.getCpDriveStrategy, root)
        if ok and strategy ~= nil then
            return true
        end
    end

    return false
end

local function getIsTurnedOn(vehicle)
    if vehicle == nil then
        return false
    end

    if vehicle.getIsTurnedOn ~= nil then
        local ok, state = pcall(vehicle.getIsTurnedOn, vehicle)
        if ok then
            return state == true
        end
    end

    if vehicle.spec_turnOnVehicle ~= nil then
        return vehicle.spec_turnOnVehicle.isTurnedOn == true
    end

    return false
end

local function setTurnedOn(vehicle, state)
    if vehicle == nil or getIsTurnedOn(vehicle) == state then
        return
    end

    if vehicle.setIsTurnedOn ~= nil then
        pcall(vehicle.setIsTurnedOn, vehicle, state, false)
        return
    end

    if vehicle.spec_turnOnVehicle ~= nil and vehicle.spec_turnOnVehicle.setIsTurnedOn ~= nil then
        pcall(vehicle.spec_turnOnVehicle.setIsTurnedOn, vehicle, state, false)
        return
    end

    if TurnOnVehicle ~= nil and TurnOnVehicle.setIsTurnedOn ~= nil then
        pcall(TurnOnVehicle.setIsTurnedOn, vehicle, state, false)
    end
end

local function updateToolbar(vehicle, dt)
    NH3TankAwareCultivator.installHooks()

    if not NH3TankAwareCultivator.isToolbar(vehicle) then
        return
    end

    vehicle.nh3TankAwareNextUpdate = (vehicle.nh3TankAwareNextUpdate or 0) - dt
    if vehicle.nh3TankAwareNextUpdate > 0 then
        return
    end
    vehicle.nh3TankAwareNextUpdate = NH3TankAwareCultivator.updateIntervalMs

    local hasTank = NH3TankAwareCultivator.hasTankAttached(vehicle)
    if vehicle.nh3TankAwareLastHasTank ~= hasTank then
        vehicle.nh3TankAwareLastHasTank = hasTank
        log('%s', hasTank and 'NH3 tank attached; application mode' or 'no NH3 tank; plain cultivator mode')
    end

    if isUnderAI(vehicle) then
        if hasTank then
            -- Tank attached: make AI/Courseplay behave like the user pressed B.
            setTurnedOn(vehicle, true)
        else
            -- No tank: keep the NH3/application side off while allowing the
            -- normal cultivator work area to run.
            setTurnedOn(vehicle, false)
        end
    end
end

if Vehicle ~= nil and Vehicle.update ~= nil then
    Vehicle.update = Utils.appendedFunction(Vehicle.update, updateToolbar)
end
