function Overloading.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Fillable, specializations) and SpecializationUtil.hasSpecialization(FillVolume, specializations);
end

function Overloading:load(savegame)
    self.setOverloadingActive           = Overloading.setOverloadingActive;
    self.doOverload                     = Overloading.doOverload;
    self.getIsFoldAllowed               = Utils.overwrittenFunction(self.getIsFoldAllowed, Overloading.getIsFoldAllowed);
    self.getPtoRpm                      = Utils.overwrittenFunction(self.getPtoRpm, Overloading.getPtoRpm);
    self.getDoConsumePtoPower           = Utils.overwrittenFunction(self.getDoConsumePtoPower, Overloading.getDoConsumePtoPower);
    self.getIsOverloadingAllowed        = Overloading.getIsOverloadingAllowed;
    self.findValidTrailer               = Overloading.findValidTrailer;
    self.loadTrailerTriggers            = Overloading.loadTrailerTriggers;
    self.onOverloadingTrailerTrigger    = Overloading.onOverloadingTrailerTrigger;
    self.getAutomaticOverloadingState   = Overloading.getAutomaticOverloadingState;
    self.getOverloadingActive = Utils.overwrittenFunction(self.getOverloadingActive, Overloading.getOverloadingActive);
    self.startOverloadingDelay          = Overloading.startOverloadingDelay;
    self.stopOverloadingDelay           = Overloading.stopOverloadingDelay;
    self.updateOverloadingDelay         = Overloading.updateOverloadingDelay;
    self.setOverloadingDelay            = Overloading.setOverloadingDelay;
    self.getOverloadingDelayIsActive    = Overloading.getOverloadingDelayIsActive;
    self.getAllowDelayedUnloading       = Overloading.getAllowDelayedUnloading;
    self.getAllowDelayEffects           = Overloading.getAllowDelayEffects;
    self.getAllowDelayedEffects         = Overloading.getAllowDelayedEffects;
    Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.combineTrailerTriggers.combineTrailerTrigger", "vehicle.overloading.trailerTriggers.trailerTrigger")
    Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.overloadingCapacity", "vehicle.overloading#capacity")
    Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.trailerTriggers#foldMinTime", "vehicle.overloading#foldMinTime")
    Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.trailerTriggers#foldMaxTime", "vehicle.overloading#foldMaxTime")
    Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.trailerTriggers#allowFoldWhileOverloading", "vehicle.overloading#allowFoldWhileOverloading")
    self.overloading = {};
    self.overloading.trailerTriggers = {};
    self:loadTrailerTriggers(self.overloading.trailerTriggers, self.xmlFile);
    for _, trigger in pairs(self.overloading.trailerTriggers) do
        addTrigger(trigger.node, "onOverloadingTrailerTrigger", self);
    end
    self.overloading.trailersInRange = {};
    self.overloading.capacity = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.overloading#capacity"), 10);
    self.overloading.fillUnitIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.overloading#fillUnitIndex"), 1);
    self.overloading.unloadInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.overloading#unloadInfoIndex"), 1);
    self.overloading.loadInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.overloading#loadInfoIndex"), 1);
    self.overloading.dischargeInfoIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.overloading#dischargeInfoIndex"), 1);
    self.overloading.stopOverloadIfEmpty = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.overloading#stopOverloadIfEmpty"), true);
    self.overloading.forceActive = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.overloading#forceActive"), false);
    self.overloading.canToggleOverloading = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.overloading#canToggleOverloading"), true);
    self.overloading.foldMinTime = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.overloading#foldMinTime"), 0);
    self.overloading.foldMaxTime = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.overloading#foldMaxTime"), 1);
    self.overloading.allowFoldWhileOverloading = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.overloading#allowFoldWhileOverloading"), false);
    self.overloading.isActive = false;
    self.overloading.didOverload = false;
    self.overloading.delay = {};
    self.overloading.delay.current = 0;
    self.overloading.delay.unloadStartTime = 0;
    self.overloading.delay.sentCurrent = 0;
    self.overloading.delay.sentUnloadStartTime = 0;
    self.overloading.delay.dirtyFlag = self:getNextDirtyFlag();
    self.overloading.delay.time = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.overloading#delay"), Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.pipe#delay"), 0)) * 1000;
    self.isSelectable = true;
    local linkNode = Utils.indexToObject(self.components, Utils.getNoNil(getXMLString(self.xmlFile, "vehicle.fillSound#linkNode"), "0>"));
    self.sampleFill = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.fillSound", nil, self.baseDirectory, linkNode);
    self.sampleFillStopTime = 0;
    self.overloadingDirtyFlag = self:getNextDirtyFlag();
    self:addConflictCheckedInput(InputBinding.EMPTY_KOTTE);
end

function Overloading:delete()
    for _, trailerTrigger in pairs(self.overloading.trailerTriggers) do
        removeTrigger(trailerTrigger.node);
    end
    if self.isClient then
        SoundUtil.deleteSample(self.sampleFill);
    end
end


function Overloading:readStream(streamId, connection)
    if connection:getIsServer() then
        self.overloading.didOverload = streamReadBool(streamId);
        self:setOverloadingActive(streamReadBool(streamId), true);
    end
end

function Overloading:writeStream(streamId, connection)
    if not connection:getIsServer() then
        streamWriteBool(streamId, self.overloading.didOverload);
        streamWriteBool(streamId, self.overloading.isActive);
    end
end

function Overloading:readUpdateStream(streamId, timestamp, connection)
    if connection:getIsServer() then
        if streamReadBool(streamId) then
            self.overloading.didOverload = streamReadBool(streamId);
        end;
        if streamReadBool(streamId) then
            self.overloading.delay.current = streamReadUIntN(streamId, 6)/63*self.overloading.delay.time;
            if streamReadBool(streamId) then
                self.overloading.delay.unloadStartTime = 1;
            else
                self.overloading.delay.unloadStartTime = -1;
            end;
        end;
    end
end

function Overloading:writeUpdateStream(streamId, connection, dirtyMask)
    if not connection:getIsServer() then
        if streamWriteBool(streamId, bitAND(dirtyMask, self.overloadingDirtyFlag) ~= 0) then
            streamWriteBool(streamId, self.overloading.didOverload);
        end;
        if streamWriteBool(streamId, bitAND(dirtyMask, self.overloading.delay.dirtyFlag) ~= 0) then
            streamWriteUIntN(streamId, Utils.clamp(math.floor(self.overloading.delay.current/self.overloading.delay.time*63), 0, 63), 6);
            streamWriteBool(streamId, self.overloading.delay.unloadStartTime > 0);
        end;
    end
end

function Overloading:update(dt)
    if self.isClient and self:getIsActive() then
        if InputBinding.hasEvent(InputBinding.EMPTY_KOTTE) and self.overloading.canToggleOverloading then
            if self:getIsActiveForInput(true) and not self:hasInputConflictWithSelection() then
                if self:getIsOverloadingAllowed() then
                    self:setOverloadingActive(not self.overloading.isActive);
                end
            end
        end
    end
end

function Overloading:updateTick(dt)
    -- sound for a more holistic refactoring
    if self.isClient then
        if self.overloading.didOverload or self.isFilling then
            self.sampleFillStopTime = g_currentMission.time + 150;
        end
        if self.sampleFillStopTime > g_currentMission.time then
            if self:getIsActiveForSound(true) then
                SoundUtil.playSample(self.sampleFill, 0, 0, nil);
                SoundUtil.stop3DSample(self.sampleFill);
            else
                SoundUtil.stopSample(self.sampleFill);
                SoundUtil.play3DSample(self.sampleFill);
            end
        else
            SoundUtil.stopSample(self.sampleFill);
            SoundUtil.stop3DSample(self.sampleFill);
        end
    end
    if self.isServer then
        self:updateOverloadingDelay(dt);
        if self.overloading.delay.current ~= self.overloading.delay.sentCurrent or self.overloading.delay.unloadStartTime ~= self.overloading.delay.sentUnloadStartTime then
            self:raiseDirtyFlags(self.overloading.delay.dirtyFlag);
            self.overloading.delay.sentCurrent = self.overloading.delay.current;
            self.overloading.delay.sentUnloadStartTime = self.overloading.delay.unloadStartTime;
        end;
        if self:getUnitFillLevel(self.overloading.fillUnitIndex) <= 0 and self:getOverloadingDelayIsActive()  then
            self:stopOverloadingDelay();
        end;
        self.overloading.didOverload = false;
        if self:getIsActive() or self.overloading.forceActive or next(self.overloading.trailersInRange) ~= nil or self:getOverloadingActive() then -- and self:getUnitFillLevel(self.overloading.fillUnitIndex) > 0) then
            -- first update the target trailer
            self.targetTrailer = self:findValidTrailer(self.overloading.trailersInRange);
            -- second, update status
            if self.overloading.isActive then
                if self:getUnitFillLevel(self.overloading.fillUnitIndex) > 0 and not self:getOverloadingDelayIsActive() then
                    self:startOverloadingDelay();
                end;
                if not self:getIsOverloadingAllowed() then
                    self:setOverloadingActive(false);
                else
                    if self.overloading.stopOverloadIfEmpty then
                        if self:getUnitFillLevel(self.overloading.fillUnitIndex) == 0 then
                            self:setOverloadingActive(false);
                        end
                    end
                end
                -- third, overload if required
                if (self.targetTrailer ~= nil or self.shovelTargetFound ~= nil) and self:getAllowDelayedUnloading() then
                    local fillType = self:getUnitLastValidFillType(self.overloading.fillUnitIndex);
                    local fillLevel = self:getUnitFillLevel(self.overloading.fillUnitIndex);
                    if self.lastLostFillLevel ~= nil then
                        fillLevel = math.max(fillLevel, self.lastLostFillLevel);
                    end
                    local deltaFill = fillLevel;
                    if self:getUnitCapacity(self.overloading.fillUnitIndex) > 0 then
                        deltaFill = math.min(fillLevel, self.overloading.capacity*dt/1000.0);
                    end
                    if self.targetTrailer ~= nil then
                        self.overloading.didOverload = self:doOverload(self.targetTrailer, fillType, deltaFill);
                    elseif self.shovelTargetFound ~= nil then
                        local realDelta = self.shovelTargetFound:addPipeFillLevel(self, deltaFill, fillType);
                        if realDelta > 0 then
                            self:setUnitFillLevel(self.overloading.fillUnitIndex, self:getUnitFillLevel(self.overloading.fillUnitIndex) - realDelta, fillType, false, self.fillVolumeUnloadInfos[self.overloading.unloadInfoIndex]);
                            self.overloading.didOverload = true;
                        else
                            self:setOverloadingActive(false);
                        end;
                    end;
                end
            else
                if self:getAutomaticOverloadingState() then
                    self:setOverloadingActive(true);
                end
            end
        end
        if self.overloading.didOverload ~= self.sendDidOverload then
            self:raiseDirtyFlags(self.overloadingDirtyFlag);
            self.sendDidOverload = self.overloading.didOverload;
        end
    end
end

function Overloading:draw()
    if self.isClient and self:getIsActiveForInput(true) and not self:hasInputConflictWithSelection() then
        if self.overloading.canToggleOverloading and self:getIsOverloadingAllowed() then
            if self.overloading.isActive then
                g_currentMission:addHelpButtonText(g_i18n:getText("action_stopOverloading"), InputBinding.EMPTY_KOTTE, nil, GS_PRIO_HIGH);
            else
                if self:getUnitFillLevel(self.overloading.fillUnitIndex) > 0 and self.targetTrailer ~= nil then
                    g_currentMission:addHelpButtonText(g_i18n:getText("action_startOverloading"), InputBinding.EMPTY_KOTTE, nil, GS_PRIO_HIGH);
                end
            end
        end
    end
end

function Overloading:setOverloadingActive(isActive, noEventSend)
    if isActive ~= self.overloading.isActive then
        OverloadingToggleUnloadEvent.sendEvent(self, isActive, noEventSend);
        self.overloading.isActive = isActive;
        if not isActive then
            self.pipeUnloadingStartTime = -1;
        end;
    end
end

function Overloading:getOverloadingActive(superFunc)
    if self.overloading.isActive then
        return true;
    end;
    if superFunc ~= nil then
        return superFunc(self);
    end
    return false;
end

function Overloading:doOverload(targetTrailer, fillType, deltaFill)
    targetTrailer:resetFillLevelIfNeeded(fillType);
    local trailerFillLevel = targetTrailer:getFillLevel(fillType);
    local trailerCapacity = targetTrailer:getCapacity(fillType);
    deltaFill = math.min(deltaFill, trailerCapacity - trailerFillLevel);
    if deltaFill > 0 then
        targetTrailer:setFillLevel(trailerFillLevel+deltaFill, fillType, false, self.fillVolumeDischargeInfos[self.overloading.dischargeInfoIndex]);
        self:setFillLevel(self:getUnitFillLevel(self.overloading.fillUnitIndex)-deltaFill, fillType, false, self.fillVolumeUnloadInfos[self.overloading.unloadInfoIndex]);
        return true;
    else
        return false;
    end
end

function Overloading:getIsFoldAllowed(superFunc, onAiTurnOn)
    if not self.overloading.allowFoldWhileOverloading and self.overloading.isActive then
        if self:getUnitFillLevel(self.overloading.fillUnitIndex) > 0 then
            return false;
        end
    end
    if self.foldAnimTime ~= nil and (self.foldAnimTime > self.overloading.foldMaxTime or self.foldAnimTime < self.overloading.foldMinTime) then
        return false;
    end
    if superFunc ~= nil then
        return superFunc(self, onAiTurnOn);
    end
    return true;
end

function Overloading:getPtoRpm(superFunc)
    local ptoRpm = 0;
    if superFunc ~= nil then
        ptoRpm = superFunc(self);
    end
    if self.overloading.didOverload and self.powerConsumer ~= nil then
        ptoRpm = math.max(ptoRpm, self.powerConsumer.ptoRpm);
    end
    return ptoRpm;
end

function Overloading:getDoConsumePtoPower(superFunc)
    local doConsumePower = superFunc(self);
    return self.overloading.didOverload or doConsumePower;
end

function Overloading:getIsOverloadingAllowed()
    if self.foldAnimTime ~= nil then
        if self.foldAnimTime > self.overloading.foldMaxTime or self.foldAnimTime < self.overloading.foldMinTime then
            return false;
        end
    end
    return true;
end

function Overloading:findValidTrailer(trailers)
    local fillType = self:getUnitLastValidFillType(self.overloading.fillUnitIndex);
    for trailer,value in pairs(trailers) do
        if not entityExists(trailer.components[1].node) then
            value = 0;
        end
        if value > 0 then
            if trailer:allowFillType(fillType) then
                if trailer:getFillLevel(fillType) < trailer:getCapacity(fillType) then
                    return trailer;
                end
            end
        end
    end
end

function Overloading:getAutomaticOverloadingState()
    return false
end

function Overloading:loadTrailerTriggers(triggers, xmlFile)
    local i = 0;
    while true do
        local key = string.format("vehicle.overloading.trailerTriggers.trailerTrigger(%d)", i);
        if not hasXMLProperty(self.xmlFile, key) then
            break;
        end
        local node = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key.."#index"));
        if node ~= nil then
            triggers[node] = {node=node};
        end
        i = i + 1;
    end
end

function Overloading:onOverloadingTrailerTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onLeave then
        local trailer = g_currentMission.nodeToVehicle[otherId];
        if trailer ~= nil and trailer.fillRootNode ~= nil and trailer ~= self then
            if onEnter then
                self.overloading.trailersInRange[trailer] = Utils.getNoNil(self.overloading.trailersInRange[trailer], 0) + 1;
            else
                self.overloading.trailersInRange[trailer] = math.max(Utils.getNoNil(self.overloading.trailersInRange[trailer], 0) - 1, 0);
                if self.overloading.trailersInRange[trailer] == 0 then
                    self.overloading.trailersInRange[trailer] = nil
                end
            end
        end
    end
end


function Overloading:startOverloadingDelay()
    self.overloading.delay.unloadStartTime = g_currentMission.time;
    self.overloading.delay.current = 0;
end

function Overloading:stopOverloadingDelay()
    self.overloading.delay.unloadStartTime = -1;
end

function Overloading:updateOverloadingDelay(dt)
    if self.overloading.delay.unloadStartTime > 0 then
        if self.overloading.delay.current < self.overloading.delay.time then
            self:setOverloadingDelay(math.min(self.overloading.delay.current + dt, self.overloading.delay.time));
        end;
    else
        if self.overloading.delay.current > 0 then
            self:setOverloadingDelay(math.max(self.overloading.delay.current - dt, 0));
        end;
    end;
end

function Overloading:setOverloadingDelay(delay)
    self.overloading.delay.current = delay;
end

function Overloading:getOverloadingDelayIsActive()
    return self.overloading.delay.unloadStartTime > 0;
end

function Overloading:getAllowDelayEffects()
    if self:getUnitCapacity(self.overloading.fillUnitIndex) == 0 then
        return self.overloading.didOverload;
    end;
    return self.overloading.delay.current > 0 and self:getOverloadingDelayIsActive();
end

function Overloading:getAllowDelayedEffects()
    if self:getUnitCapacity(self.overloading.fillUnitIndex) == 0 then
        return self.overloading.didOverload;
    end;
    return (self.overloading.delay.current > 0 and not self:getOverloadingDelayIsActive()) or self:getAllowDelayedUnloading();
end

function Overloading:getAllowDelayedUnloading()
    return self.overloading.delay.current == self.overloading.delay.time;
end