--[[
    AttacgPowerTakeOffsManually

        Changes the attacher joints behavior to allow attaching PTOs manually.

	@author: 		BayernGamers
	@date: 			31.05.2025
	@version:		1.0

	History:		v1.0 @31.05.2025 - initial implementation in FS25
                    ------------------------------------------------------------------------------------------------------

	
	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))
source(Utils.getFilename("scripts/utils/ExtendedSpecializationUtil.lua", g_currentModDirectory))
source(Utils.getFilename("scripts/events/TogglePTOAttachStateEvent.lua", g_currentModDirectory))
source(Utils.getFilename("scripts/AttachImplementsManuallySettingsManager.lua", g_currentModDirectory))
source(Utils.getFilename("scripts/utils/PowerTakeOffUtil.lua", g_currentModDirectory))

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

AttachPowerTakeOffsManually = {}
AttachPowerTakeOffsManually.MOD_DIRECTORY = g_currentModDirectory
AttachPowerTakeOffsManually.MOD_NAME = g_currentModName

function AttachPowerTakeOffsManually.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(PowerTakeOffs, specializations) and SpecializationUtil.hasSpecialization(AttachImplementsManually, specializations)
end

function AttachPowerTakeOffsManually.initSpecialization()
    local schemaSavegame = Vehicle.xmlSchemaSavegame
    schemaSavegame:register(XMLValueType.INT, "vehicles.vehicle(?).FS25_attachImplementsManually.attachPowerTakeOffsManually.attachedImplement(?)#jointIndex", "Index of attacherJoint")
    schemaSavegame:register(XMLValueType.STRING, "vehicles.vehicle(?).FS25_attachImplementsManually.attachPowerTakeOffsManually.attachedImplement(?)#attachedVehicleUniqueId", "Unique id of attached vehicle")
    schemaSavegame:register(XMLValueType.INT, "vehicles.vehicle(?).FS25_attachImplementsManually.attachPowerTakeOffsManually.attachedImplement(?)#inputJointIndex", "Index of input attacher joint on the attached vehicle")

    schemaSavegame:register(XMLValueType.BOOL, "vehicles.vehicle(?).FS25_attachImplementsManually.attachPowerTakeOffsManually.attachedImplement(?)#ptoAttached")
end

function AttachPowerTakeOffsManually.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onPreLoad", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onDelete", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterExternalActionEvents", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onReadStream", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", AttachPowerTakeOffsManually)
    SpecializationUtil.registerEventListener(vehicleType, "onPostAttachImplement", AttachPowerTakeOffsManually)
    
    ExtendedSpecializationUtil.registerOverwrittenEventListener(vehicleType, "onPreDetachImplement", PowerTakeOffs, AttachPowerTakeOffsManually)
    ExtendedSpecializationUtil.registerOverwrittenEventListener(vehicleType, "onPreAttachImplement", PowerTakeOffs, AttachPowerTakeOffsManually)
end

function AttachPowerTakeOffsManually.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "loadAttachedPTOsFromXMLFile", AttachPowerTakeOffsManually.loadAttachedPTOsFromXMLFile)
    SpecializationUtil.registerFunction(vehicleType, "onAttachPowerTakeOffsManuallyVehicleLoaded", AttachPowerTakeOffsManually.onAttachPowerTakeOffsManuallyVehicleLoaded)
    SpecializationUtil.registerFunction(vehicleType, "getPTOSoundSample", AttachPowerTakeOffsManually.getPTOSoundSample)
    SpecializationUtil.registerFunction(vehicleType, "togglePTOAttachState", AttachPowerTakeOffsManually.togglePTOAttachState)
end

function AttachPowerTakeOffsManually.registerOverwrittenFunctions(vehicleType)
    ExtendedSpecializationUtil.registerAppendedFunction(vehicleType, "onAttacherJointsVehicleLoaded", AttachPowerTakeOffsManually.onAttachPowerTakeOffsManuallyVehicleLoaded)
end

function AttachPowerTakeOffsManually:onPreLoad(savegame)
    self.spec_attachPowerTakeOffsManually = {}
end

function AttachPowerTakeOffsManually:onLoad(savegame)
    local spec = self.spec_attachPowerTakeOffsManually

    spec.actionEventAttachPTOData = nil
    spec.modSettingsManager = AttachImplementsManuallySettingsManager.getInstance()
    spec.attachedPTOs = {}
    spec.ptoAttachmentDataToLoad = {}
    spec.attachPTOSoundSamples = {}
    spec.powerTakeOffsToAttach = {}
    spec.delayedPowerTakeOffsMounting = true
    spec.dt = 0

    local soundXMLFile = XMLFile.loadIfExists("attachImplementsManuallySounds", AttachPowerTakeOffsManually.MOD_DIRECTORY .. "sounds/attachImplementsManuallySounds.xml", nil)

    if soundXMLFile ~= nil then
        soundXMLFile:iterate("sounds.attachPowerTakeOffsManually.sounds.attachPTO", function (index, key)
            local sample = g_soundManager:loadSampleFromXML(soundXMLFile, "sounds.attachPowerTakeOffsManually.sounds", string.format("attachPTO(%d)", index - 1), AttachPowerTakeOffsManually.MOD_DIRECTORY, self.components, 1, AudioGroup.VEHICLE, self.i3dMappings, self)
            if sample ~= nil then
                table.insert(spec.attachPTOSoundSamples, sample)
            else
                log:error("Failed to load PTO sound sample '%s'!", key)
            end
        end)
    end

    g_messageCenter:subscribe(MessageType.VEHICLE_RESET, AttachPowerTakeOffsManually.onVehicleReset, self)
end

function AttachPowerTakeOffsManually:onLoadFinished(savegame)
    local spec = self.spec_powerTakeOffs
    local spec_attachImplementsManually = self.spec_attachImplementsManually

    if spec_attachImplementsManually ~= nil and spec_attachImplementsManually.externalControlTrigger ~= nil then
        local names = {
            --"attachImplementManually",
            "attachPTOManually",
            --"attachHosesManually",
        }
        local funcKey = ""

        for _, name in ipairs(names) do
            SpecializationUtil.raiseEvent(self, "onRegisterExternalActionEvents", spec_attachImplementsManually.externalControlTrigger, name, self.xmlFile, funcKey)
        end
    end

    self:loadAttachedPTOsFromXMLFile(savegame)
end

function AttachPowerTakeOffsManually:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_attacherJoints
    local spec_attachPowerTakeOffsManually = self.spec_attachPowerTakeOffsManually

    if spec.attacherJoints ~= nil then
        for index, implement in ipairs(spec.attachedImplements) do
            if implement.object ~= nil then
                local attacherJointKey = string.format("%s.attachedImplement(%d)", key, index - 1)

                xmlFile:setValue(attacherJointKey .. "#jointIndex", implement.jointDescIndex)
                xmlFile:setValue(attacherJointKey .. "#attachedVehicleUniqueId", implement.object:getUniqueId())
                xmlFile:setValue(attacherJointKey .. "#inputJointIndex", implement.inputJointDescIndex)

                xmlFile:setValue(attacherJointKey .. "#ptoAttached", spec_attachPowerTakeOffsManually.attachedPTOs[implement.jointDescIndex] ~= nil)
            end
        end
    end
end

function AttachPowerTakeOffsManually:loadAttachedPTOsFromXMLFile(savegame)
    local spec = self.spec_attachPowerTakeOffsManually

    if savegame ~= nil and savegame.xmlFile ~= nil then
        local xmlFile = savegame.xmlFile

        for index, attachedImplementKey in xmlFile:iterator(savegame.key..".FS25_attachImplementsManually.attachPowerTakeOffsManually.attachedImplement") do
            local ptoAttachmentData = {}
            ptoAttachmentData.jointIndex = xmlFile:getValue(attachedImplementKey .. "#jointIndex")
            ptoAttachmentData.attachedVehicleUniqueId = xmlFile:getValue(attachedImplementKey .. "#attachedVehicleUniqueId")
            ptoAttachmentData.inputJointIndex = xmlFile:getValue(attachedImplementKey .. "#inputJointIndex")
            ptoAttachmentData.ptoAttached = xmlFile:getValue(attachedImplementKey .. "#ptoAttached", false)

            local vehicle = g_currentMission.vehicleSystem:getVehicleByUniqueId(ptoAttachmentData.attachedVehicleUniqueId)
            table.insert(spec.ptoAttachmentDataToLoad, ptoAttachmentData)

            if vehicle ~= nil then
                self:onAttachPowerTakeOffsManuallyVehicleLoaded(vehicle)
            end
        end
    end
end

function AttachPowerTakeOffsManually:onUpdate(dt, isActivbeForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self.spec_attachPowerTakeOffsManually
    local spec_attachImplementsManually = self.spec_attachImplementsManually
    local externalControlTrigger = spec_attachImplementsManually ~= nil and spec_attachImplementsManually.externalControlTrigger or nil
    local isPlayerInRange = externalControlTrigger ~= nil and externalControlTrigger.isPlayerInRange
    local isActive = self.getIsActive ~= nil and self:getIsActive()

    if not self.isClient then
        return
    end

    if not isActive and not isPlayerInRange then
        return
    end

    spec.dt = spec.dt + dt

    if spec.dt >= 100 then
        AttachPowerTakeOffsManually.updateActionEvents(self)
        spec.dt = 0
    end
end

function AttachPowerTakeOffsManually:onRegisterExternalActionEvents(trigger, name, xmlFile, key)
    local spec = self.spec_attachImplementsManually
    local spec_attacherJoints = self.spec_attacherJoints

    if name == "attachPTOManually" then
        self:registerExternalActionEvent(trigger, name, AttachPowerTakeOffsManually.externalActionEventRegister, AttachPowerTakeOffsManually.externalActionEventUpdate)
    end
end

function AttachPowerTakeOffsManually:onWriteStream(streamId, connection)
    local spec = self.spec_attachPowerTakeOffsManually
    log:printDevInfo("onWriteStream called", Logger.DEBUG_LEVELS.HIGH)

    local numAttachedPTOs = 0
    -- Fix weird # behavior
    for _, _ in pairs(spec.attachedPTOs) do
        numAttachedPTOs = numAttachedPTOs + 1
    end
    log:printDevInfo("Number of attached PTOs: " .. tostring(numAttachedPTOs), Logger.DEBUG_LEVELS.HIGH)

    streamWriteUInt8(streamId, numAttachedPTOs)

    for index, attachedPTO in pairs(spec.attachedPTOs) do
        log:printDevInfo("Writing PTO index: " .. tostring(index) .. ", attached: " .. tostring(attachedPTO), Logger.DEBUG_LEVELS.HIGH)
        streamWriteInt8(streamId, index)
        streamWriteBool(streamId, attachedPTO)
    end
end

function AttachPowerTakeOffsManually:onReadStream(streamId, connection)
    local spec = self.spec_attachPowerTakeOffsManually
    log:printDevInfo("onReadStream called for vehicle: " .. tostring(self:getName()), Logger.DEBUG_LEVELS.HIGH)

    local numAttachedPTOs = streamReadUInt8(streamId)
    log:printDevInfo("Number of attached PTOs: " .. tostring(numAttachedPTOs), Logger.DEBUG_LEVELS.HIGH)
    for i = 1, numAttachedPTOs do
        local jointDescIndex = streamReadInt8(streamId)
        local ptoAttached = streamReadBool(streamId)
        log:printDevInfo("Read PTO index: " .. tostring(jointDescIndex) .. ", attached: " .. tostring(ptoAttached), Logger.DEBUG_LEVELS.HIGH)

        if ptoAttached then
            spec.powerTakeOffsToAttach[jointDescIndex] = true
        end
    end

    if self.isClient and not spec.delayedPowerTakeOffsMounting then
        for index, _ in pairs(spec.powerTakeOffsToAttach) do
            local implement = self:getImplementFromAttacherJointIndex(index)
            log:printDevInfo("implement: " .. tostring(implement), Logger.DEBUG_LEVELS.HIGH)
            
            if implement ~= nil and implement.object ~= nil then
                log:printDevInfo("implement.object: " .. tostring(implement.object), Logger.DEBUG_LEVELS.HIGH)
                log:printDevInfo("onReadStream: Attaching PTO for implement: " .. tostring(implement.object:getName()) .. " at joint index: " .. tostring(index), Logger.DEBUG_LEVELS.HIGH)
                self:togglePTOAttachState(implement.object, index, implement.inputJointDescIndex)
                spec.powerTakeOffsToAttach[index] = nil
            end
        end

        spec.powerTakeOffsToAttach = {}
        numAttachedPTOs = 0
    end

    if numAttachedPTOs == 0 then
        log:printDevInfo("onReadStream : No power take offs to attach found, removing event listener", Logger.DEBUG_LEVELS.HIGH)
        SpecializationUtil.removeEventListener(self, "onPostAttachImplement", AttachPowerTakeOffsManually)
    end
end

function AttachPowerTakeOffsManually:onPreAttachImplement(object, inputJointDescIndex, jointDescIndex)
    local spec = self.spec_attachPowerTakeOffsManually

    local info = {
        attacherVehicle = self,
        attachable = object,
        attachableJointDescIndex = inputJointDescIndex,
        attacherVehicleJointDescIndex = jointDescIndex
    }

    log:printDevInfo("Info: " .. tostring(info), Logger.DEBUG_LEVELS.HIGH)
    log:printDevInfo("onPreAttachImplement called isServer:" .. tostring(self.isServer), Logger.DEBUG_LEVELS.HIGH)
    log:printDevInfo("EnableAttachImplementsManually: " .. tostring(spec.modSettingsManager:getEnableAttachImplementsManually()), Logger.DEBUG_LEVELS.HIGH)
    log:printDevInfo("getIsManualAttachForNexatDisabled: " .. tostring(AttachImplementsManually.getIsManualAttachForNexatDisabled(self)), Logger.DEBUG_LEVELS.HIGH)
    --log:printDevInfo("getAttachPTOIfAttachedFromInside: " .. tostring(AttacherJointUtil.getAttachPTOIfAttachedFromInside(info)))
    
    if self.isServer then
        if not spec.modSettingsManager:getEnableAttachImplementsManually() or AttachImplementsManually.getIsManualAttachForNexatDisabled(self) or AttacherJointUtil.getAttachPTOIfAttachedFromInside(info) then
            log:printDevInfo("Calling original onPreAttachImplement", Logger.DEBUG_LEVELS.HIGH)
            PowerTakeOffs.onPreAttachImplement(self, object, inputJointDescIndex, jointDescIndex)
            spec.attachedPTOs[jointDescIndex] = true
        end
    end
end

function AttachPowerTakeOffsManually:onPreDetachImplement(implement)
    local spec = self.spec_attachPowerTakeOffsManually
    local attacherVehicle = implement.object.getAttacherVehicle ~= nil and implement.object:getAttacherVehicle() or nil

    local info = {
        attacherVehicle = self,
        attachable = implement.object,
        attachableJointDescIndex = implement.inputJointDescIndex,
        attacherVehicleJointDescIndex = implement.jointDescIndex
    }

    local function callPreDetachImplement()
        log:printDevInfo("Calling original onPreDetachImplement", Logger.DEBUG_LEVELS.HIGH)
        PowerTakeOffs.onPreDetachImplement(self, implement)
        spec.attachedPTOs[implement.jointDescIndex] = nil
    end

    log:printDevInfo("onPreDetachImplement called. IsServer: " .. tostring(self.isServer), Logger.DEBUG_LEVELS.HIGH)
    if self.isServer then
        if (spec ~= nil and spec.modSettingsManager ~= nil and not spec.modSettingsManager:getEnableAttachImplementsManually()) or AttachImplementsManually.getIsManualAttachForNexatDisabled(self) or AttacherJointUtil.getAttachPTOIfAttachedFromInside(info) then
            callPreDetachImplement()
        end
    end

    if implement.object:getIsBeingDeleted() or implement.object.isReconfiguring or (attacherVehicle ~= nil and (attacherVehicle.isReconfiguring or attacherVehicle:getIsBeingDeleted())) then
        callPreDetachImplement()
    end
end

function AttachPowerTakeOffsManually:onPostAttachImplement(attachable, inputJointDescIndex, jointDescIndex)
    local spec = self.spec_attachPowerTakeOffsManually

    log:printDevInfo("onPostAttachImplement called with attachable: " .. tostring(attachable:getName()) .. "for vehicle: " .. tostring(self:getName()), Logger.DEBUG_LEVELS.HIGH)
    
    local numAttachedPTOs = 0
    for index, _ in pairs(spec.powerTakeOffsToAttach) do
        numAttachedPTOs = numAttachedPTOs + 1

        if index == jointDescIndex then
            self:togglePTOAttachState(attachable, jointDescIndex, inputJointDescIndex)
            spec.powerTakeOffsToAttach[index] = nil
        end
    end

    log:printDevInfo("Vehicle: " .. tostring(self:getName()) .. " has " .. tostring(numAttachedPTOs) .. " PTOs to attach and is " .. (self:getIsSynchronized() and "synchronized" or "not synchronized"), Logger.DEBUG_LEVELS.HIGH)
    if self:getIsSynchronized() and numAttachedPTOs == 0 then
        log:printDevInfo("onPostAttachImplement : No power take offs to attach found, removing event listener", Logger.DEBUG_LEVELS.HIGH)
        spec.powerTakeOffsToAttach = {}
        SpecializationUtil.removeEventListener(self, "onPostAttachImplement", AttachPowerTakeOffsManually)
    end

    if self.isServer then
        local info = {
            attacherVehicle = self,
            attachable = attachable,
            attachableJointDescIndex = inputJointDescIndex,
            attacherVehicleJointDescIndex = jointDescIndex
        }

        if not spec.modSettingsManager:getEnableAttachImplementsManually() or AttachImplementsManually.getIsManualAttachForNexatDisabled(self) or AttacherJointUtil.getAttachPTOIfAttachedFromInside(info) then
            PowerTakeOffs.onPostAttachImplement(self, attachable, inputJointDescIndex, jointDescIndex)
            spec.attachedPTOs[jointDescIndex] = true
        end
    end

    spec.delayedPowerTakeOffsMounting = false
end

function AttachPowerTakeOffsManually.externalActionEventRegister(data, vehicle)
    local spec = vehicle.spec_attachPowerTakeOffsManually
    local spec_attacherJoints = vehicle.spec_attacherJoints
    local spec_attachImplementsManually = vehicle.spec_attachImplementsManually

    local function createVehicleActionEvent(func)
        return function (...)
            return func(vehicle, ...)
        end
    end

    if spec_attachImplementsManually.externalControlTrigger.isPlayerInRange then
        local actionEvent = createVehicleActionEvent(AttachPowerTakeOffsManually.actionEventAttachPTO)
        local _, actionEventId = g_inputBinding:registerActionEvent(InputAction.AIM_ATTACH_PTO, data, actionEvent, false, true, false, true)
        data.actionEventId = actionEventId
        g_inputBinding:setActionEventText(data.actionEventId, g_i18n:getText("input_AIM_ATTACH_PTO"))
        g_inputBinding:setActionEventTextPriority(data.actionEventId, GS_PRIO_VERY_HIGH)
        g_inputBinding:setActionEventActive(data.actionEventId, false)
    end

    spec.actionEventAttachPTOData = data
end

function AttachPowerTakeOffsManually.externalActionEventUpdate(data, vehicle)
    local spec = vehicle.spec_attachPowerTakeOffsManually
    local spec_attachImplementsManually = vehicle.spec_attachImplementsManually
    local isDetaching = false

    local attachedVehicle = spec_attachImplementsManually.closestVehicleInPlayerRange

    if spec_attachImplementsManually ~= nil and spec_attachImplementsManually.isDetachingImplement then
        isDetaching = true
    end

    if spec.modSettingsManager:getEnableAttachImplementsManually() and not isDetaching and not AttachImplementsManually.getIsManualAttachForNexatDisabled(vehicle) then
        if attachedVehicle ~= nil then
            local implementIndex = vehicle:getImplementIndexByObject(attachedVehicle)
            local jointDescIndex = vehicle:getAttacherJointIndexFromImplementIndex(implementIndex)

            local info = {
                attacherVehicle = vehicle,
                attachable = attachedVehicle,
                attacherVehicleJointDescIndex = jointDescIndex,
                attachableJointDescIndex = attachedVehicle:getActiveInputAttacherJointDescIndex()
            }

            local attachesPTOIfAttachedFromInside = AttacherJointUtil.getAttachPTOIfAttachedFromInside(info)

            --[[
            local hasPTO = false
            if SpecializationUtil.hasSpecialization(PowerTakeOffs, attachedVehicle.specializations) then
                local spec_powerTakeOffs = attachedVehicle.spec_powerTakeOffs

                hasPTO = #spec_powerTakeOffs.inputPowerTakeOffs > 0 and #vehicle.spec_powerTakeOffs.outputPowerTakeOffs > 0
            end
            ]]

            local hasPTO = PowerTakeOffUtil.getHasInputPTO(attachedVehicle, info.attachableJointDescIndex) and PowerTakeOffUtil.getHasOutputPTO(vehicle, info.attacherVehicleJointDescIndex)

            if attachesPTOIfAttachedFromInside then
                g_inputBinding:setActionEventTextVisibility(data.actionEventId, false)
            end

            g_inputBinding:setActionEventActive(data.actionEventId, hasPTO)

            if spec.attachedPTOs[jointDescIndex] == nil then
                g_inputBinding:setActionEventText(data.actionEventId, g_i18n:getText("input_AIM_ATTACH_PTO"))
                g_inputBinding:setActionEventTextPriority(data.actionEventId, GS_PRIO_VERY_HIGH)
            else
                if attachedVehicle.getIsTurnedOn == nil or (attachedVehicle.getIsTurnedOn ~= nil and not attachedVehicle:getIsTurnedOn()) then
                    g_inputBinding:setActionEventText(data.actionEventId, g_i18n:getText("input_AIM_DETACH_PTO"))
                    g_inputBinding:setActionEventTextPriority(data.actionEventId, GS_PRIO_VERY_HIGH)
                else
                    g_inputBinding:setActionEventTextVisibility(data.actionEventId, false)
                end
            end
        else
            --g_inputBinding:setActionEventActive(data.actionEventId, false)
        end
    else
        if data ~= nil then
            g_inputBinding:setActionEventActive(data.actionEventId, false)
        end
    end
end

function AttachPowerTakeOffsManually:actionEventAttachPTO(actionName, actionName, inputValue, callbackState, isAnalog)
    local spec = self.spec_attachPowerTakeOffsManually
    local spec_attachImplementsManually = self.spec_attachImplementsManually
    local spec_attacherJoints = self.spec_attacherJoints
    local spec_powerTakeOffs = self.spec_powerTakeOffs

    --self:attachPowerTakeOff(info.attachable, info.attachableJointDescIndex, info.attacherVehicleJointDescIndex)
    local attachedVehicle = spec_attachImplementsManually.closestVehicleInPlayerRange

    if self.isServer then
        self:togglePTOAttachState(attachedVehicle)
    else
        TogglePTOAttachStateEvent.sendEvent(self, attachedVehicle, false)
    end
end

function AttachPowerTakeOffsManually:updateActionEvents()
    local spec = self.spec_attachPowerTakeOffsManually
    local spec_attachImplementsManually = self.spec_attachImplementsManually

    if spec_attachImplementsManually.externalControlTrigger ~= nil and spec_attachImplementsManually.externalControlTrigger.isPlayerInRange and spec.actionEventAttachPTOData ~= nil then
        AttachPowerTakeOffsManually.externalActionEventUpdate(spec.actionEventAttachPTOData, self)
    end
end

function AttachPowerTakeOffsManually:onDelete()
    g_messageCenter:unsubscribe(MessageType.VEHICLE_RESET, self)
end

function AttachPowerTakeOffsManually:onAttachPowerTakeOffsManuallyVehicleLoaded(vehicle)
    local spec = self.spec_attachPowerTakeOffsManually
    local spec_powerTakeOffs = self.spec_powerTakeOffs

    if spec.ptoAttachmentDataToLoad ~= nil then
        local vehicleUniqueId = vehicle:getUniqueId()

        for index = #spec.ptoAttachmentDataToLoad, 1, -1 do
            local ptoAttachmentData = spec.ptoAttachmentDataToLoad[index]

            if ptoAttachmentData.attachedVehicleUniqueId == vehicleUniqueId then
                local jointIndex = ptoAttachmentData.jointIndex
                local inputJointIndex = ptoAttachmentData.inputJointIndex
                local ptoAttached = ptoAttachmentData.ptoAttached

                if ptoAttached and spec.attachedPTOs[jointIndex] == nil then
                    self:attachPowerTakeOff(vehicle, inputJointIndex, jointIndex)

                    for i=#spec_powerTakeOffs.delayedPowerTakeOffsMountings, 1, -1 do
                        local delayedMounting = spec_powerTakeOffs.delayedPowerTakeOffsMountings[i]
                        if delayedMounting.jointDescIndex == jointIndex then
                            local input = delayedMounting.input
                            local output = delayedMounting.output

                            if input.attachFunc ~= nil then
                                input.attachFunc(self, input, output)
                            end

                            ObjectChangeUtil.setObjectChanges(input.objectChanges, true, self, self.setMovingToolDirty)
                            ObjectChangeUtil.setObjectChanges(output.objectChanges, true, self, self.setMovingToolDirty)

                            table.remove(spec_powerTakeOffs.delayedPowerTakeOffsMountings, i)
                            spec.attachedPTOs[jointIndex] = true
                        end
                    end
                end

                table.remove(spec.ptoAttachmentDataToLoad, index)
            end
        end
    end
end

function AttachPowerTakeOffsManually:getPTOSoundSample()
    local spec = self.spec_attachPowerTakeOffsManually
    local randomIndex = math.random(1, #spec.attachPTOSoundSamples)

    return spec.attachPTOSoundSamples[randomIndex]
end

function AttachPowerTakeOffsManually:togglePTOAttachState(attachedVehicle, jointDescIndex, inputJointDescIndex)
    log:printDevInfo("togglePTOAttachState called with attachedVehicle: " .. tostring(attachedVehicle), Logger.DEBUG_LEVELS.HIGH)
    local spec = self.spec_attachPowerTakeOffsManually
    local spec_powerTakeOffs = self.spec_powerTakeOffs

    if attachedVehicle ~= nil then
        log:printDevInfo("attachedVehicle: " .. tostring(attachedVehicle:getName()), Logger.DEBUG_LEVELS.HIGH)
        local implementIndex = self:getImplementIndexByObject(attachedVehicle)
        jointDescIndex = jointDescIndex or self:getAttacherJointIndexFromImplementIndex(implementIndex)
        inputJointDescIndex = inputJointDescIndex or attachedVehicle.spec_attachable.inputAttacherJointDescIndex

        local loadFromSavegame = false
        if self.getAttachedImplements ~= nil then
            for _, implement in pairs(self:getAttachedImplements()) do
                if implement.object == attachedVehicle then
                    loadFromSavegame = implement.loadFromSavegame
                    break
                end
            end 
        end

        local info = {
            attacherVehicle = self,
            attachable = attachedVehicle,
            attacherVehicleJointDescIndex = jointDescIndex,
            attachableJointDescIndex = inputJointDescIndex
        }

        log:printDevInfo("implementIndex: " .. tostring(implementIndex), Logger.DEBUG_LEVELS.HIGH)
        log:printDevInfo("jointDescIndex: " .. tostring(jointDescIndex), Logger.DEBUG_LEVELS.HIGH)
        log:printDevInfo("inputJointDescIndex: " .. tostring(inputJointDescIndex), Logger.DEBUG_LEVELS.HIGH)
        log:printDevInfo("spec.attachedPTOs[jointDescIndex]: " .. tostring(spec.attachedPTOs[jointDescIndex]), Logger.DEBUG_LEVELS.HIGH)
        if spec.attachedPTOs[jointDescIndex] == nil then

            if not loadFromSavegame then
                if AttacherJointUtil.getAttachPTOIfAttachedFromInside(info) then
                    g_currentMission:showBlinkingWarning(g_i18n:getText("warning_ptoIsAutomaticallyAttachedWhenAttachingFromInside"), 2000)
                    return
                end
            end


            log:printDevInfo("Attaching PTO", Logger.DEBUG_LEVELS.HIGH)
            g_soundManager:playSample(self:getPTOSoundSample())
            self:attachPowerTakeOff(attachedVehicle, inputJointDescIndex, jointDescIndex)

            for i=#spec_powerTakeOffs.delayedPowerTakeOffsMountings, 1, -1 do
                local delayedMounting = spec_powerTakeOffs.delayedPowerTakeOffsMountings[i]
                if delayedMounting.jointDescIndex == jointDescIndex then
                    local input = delayedMounting.input
                    local output = delayedMounting.output

                    if input.attachFunc ~= nil then
                        input.attachFunc(self, input, output)
                    end

                    ObjectChangeUtil.setObjectChanges(input.objectChanges, true, self, self.setMovingToolDirty)
                    ObjectChangeUtil.setObjectChanges(output.objectChanges, true, self, self.setMovingToolDirty)


                    table.remove(spec_powerTakeOffs.delayedPowerTakeOffsMountings, i)
                end
            end

            spec.attachedPTOs[jointDescIndex] = true
        else
            if not loadFromSavegame then
                if AttacherJointUtil.getAttachPTOIfAttachedFromInside(info) then
                    g_currentMission:showBlinkingWarning(g_i18n:getText("warning_ptoIsAutomaticallyDetachedWhenAttachingFromInside"), 2000)
                    return
                end

                if attachedVehicle.getIsTurnedOn ~= nil and attachedVehicle:getIsTurnedOn() then
                    log:printDevInfo("Cannot detach PTO while implement is turned on!", Logger.DEBUG_LEVELS.HIGH)
                    g_currentMission:showBlinkingWarning(g_i18n:getText("warning_turnOffImplementFirst"), 2000)
                    return
                end
            end

            log:printDevInfo("Detaching PTO", Logger.DEBUG_LEVELS.HIGH)
            g_soundManager:playSample(self:getPTOSoundSample())
            self:detachPowerTakeOff(self, attachedVehicle, jointDescIndex)

            if self.isClient then
                g_soundManager:stopSamples(spec_powerTakeOffs.samples.turnedOn)
            end

            spec.attachedPTOs[jointDescIndex] = nil
        end
    end
end

function AttachPowerTakeOffsManually:onVehicleReset(vehicle)
    if vehicle == self then
        local spec = self.spec_attachPowerTakeOffsManually

        if spec ~= nil then
            spec.attachedPTOs = {}
            spec.ptoAttachmentDataToLoad = {}
            spec.powerTakeOffsToAttach = {}
        end
    end
end