--
-- baleWrapperRC
-- Class for all Remote Controlled Bale Wrappers
--
-- @author  Manuel Leithner
-- @date  12/09/12
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
--
--
-- @edit  PeterJ
-- @date  20/10/2017
--
--

--source("dataS/scripts/vehicles/specializations/BaleWrapperStateEvent.lua");
baleWrapperRC = {};

baleWrapperRC.STATE_NONE                          = 0;
baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER        = 1;
baleWrapperRC.STATE_MOVING_GRABBER_TO_WORK        = 2;
baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE         = 3;
baleWrapperRC.STATE_WRAPPER_FINSIHED              = 4;
baleWrapperRC.STATE_WRAPPER_DROPPING_BALE         = 5;
baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM    = 6;

baleWrapperRC.STATE_NUM_BITS = 3;

baleWrapperRC.CHANGE_GRAB_BALE                    = 1;
baleWrapperRC.CHANGE_DROP_BALE_AT_GRABBER         = 2;
baleWrapperRC.CHANGE_WRAPPING_START               = 3;
baleWrapperRC.CHANGE_WRAPPING_BALE_FINSIHED       = 4;
baleWrapperRC.CHANGE_WRAPPER_START_DROP_BALE      = 5;
baleWrapperRC.CHANGE_WRAPPER_BALE_DROPPED         = 6;
baleWrapperRC.CHANGE_WRAPPER_PLATFORM_RESET       = 7;
baleWrapperRC.CHANGE_BUTTON_EMPTY                 = 8;

function baleWrapperRC.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(AnimatedVehicle, specializations);
end;

function baleWrapperRC:load(savegame)
	self.getIsFoldAllowed = Utils.overwrittenFunction(self.getIsFoldAllowed, baleWrapperRC.getIsFoldAllowed);
    self.allowsGrabbingBale = baleWrapperRC.allowsGrabbingBale;
    self.pickupWrapperBale = baleWrapperRC.pickupWrapperBale;
    self.getWrapperBaleType = baleWrapperRC.getWrapperBaleType;
    self.updateWrappingState = baleWrapperRC.updateWrappingState;
    self.dropBaleFromWrapper = baleWrapperRC.dropBaleFromWrapper;
    self.moveBaleToWrapper = baleWrapperRC.moveBaleToWrapper;
    self.doStateChange = baleWrapperRC.doStateChange;
    self.updateWrapNodes = baleWrapperRC.updateWrapNodes;
    self.playMoveToWrapper = baleWrapperRC.playMoveToWrapper;
    for _,baleCategory in pairs({"roundBaleWrapper", "squareBaleWrapper"}) do
        self[baleCategory] = {};
        self[baleCategory].animations = {};
        for _,animType in pairs({"moveToWrapper", "wrapBale", "dropFromWrapper", "resetAfterDrop"}) do
            local key = string.format("vehicle.wrapper.%s.animations.%s", baleCategory, animType);
            self[baleCategory].animations[animType] = {};
            self[baleCategory].animations[animType].animName = getXMLString(self.xmlFile, key .. "#animName");
            self[baleCategory].animations[animType].animSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, key .. "#animSpeed"), 1);
            self[baleCategory].animations[animType].reverseAfterMove = Utils.getNoNil(getXMLBool(self.xmlFile, key .. "#reverseAfterMove"), true);
        end;
        self[baleCategory].allowedBaleTypes = {};
        local i=0;
        while true do
            local key = string.format("vehicle.wrapper.%s.baleTypes.baleType(%d)", baleCategory, i);
            if not hasXMLProperty(self.xmlFile, key) then
                break;
            end;
            local wrapperBaleFilename = Utils.getFilename(getXMLString(self.xmlFile, key.."#wrapperBaleFilename"), self.baseDirectory);
            local fillTypeStr = getXMLString(self.xmlFile, key.."#fillType");
            if wrapperBaleFilename ~= nil and fillTypeStr ~= nil then
                local fillType = FillUtil.fillTypeNameToInt[fillTypeStr];
                if fillType == nil then
                    print("Warning: invalid fillType '"..tostring(fillTypeStr).."' for '"..tostring(key).."' given!");
                    break;
                end;
                self[baleCategory].allowedBaleTypes[fillType] = {};
                local minBaleDiameter = getXMLFloat(self.xmlFile, key.."#minBaleDiameter");
                local maxBaleDiameter = getXMLFloat(self.xmlFile, key.."#maxBaleDiameter");
                local minBaleWidth = getXMLFloat(self.xmlFile, key.."#minBaleWidth");
                local maxBaleWidth = getXMLFloat(self.xmlFile, key.."#maxBaleWidth");
                if minBaleDiameter ~= nil and maxBaleDiameter ~= nil and minBaleWidth ~= nil and maxBaleWidth ~= nil then
                    table.insert(self[baleCategory].allowedBaleTypes[fillType], {fillType=fillType, wrapperBaleFilename=wrapperBaleFilename, minBaleDiameter=minBaleDiameter, maxBaleDiameter=maxBaleDiameter, minBaleWidth=minBaleWidth, maxBaleWidth=maxBaleWidth});
                else
                    local minBaleHeight = getXMLFloat(self.xmlFile, key.."#minBaleHeight");
                    local maxBaleHeight = getXMLFloat(self.xmlFile, key.."#maxBaleHeight");
                    local minBaleLength = getXMLFloat(self.xmlFile, key.."#minBaleLength");
                    local maxBaleLength = getXMLFloat(self.xmlFile, key.."#maxBaleLength");
                    if minBaleWidth ~= nil and maxBaleWidth ~= nil and minBaleHeight ~= nil and maxBaleHeight ~= nil and minBaleLength ~= nil and maxBaleLength ~= nil then
                        table.insert(self[baleCategory].allowedBaleTypes[fillType], {fillType=fillType, wrapperBaleFilename=wrapperBaleFilename, minBaleWidth=minBaleWidth, maxBaleWidth=maxBaleWidth, minBaleHeight=minBaleHeight, maxBaleHeight=maxBaleHeight, minBaleLength=minBaleLength, maxBaleLength=maxBaleLength});
                    end;
                end;
            end;
            i=i+1;
        end;
        local key = string.format("vehicle.wrapper.%s", baleCategory);
        self[baleCategory].baleNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#baleIndex"));
        self[baleCategory].wrapperNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#wrapperIndex"));
        self[baleCategory].wrapperRotAxis = Utils.getNoNil(getXMLInt(self.xmlFile, key.."#wrapperRotAxis"), 2);
        local wrappingAnimCurve = AnimCurve:new(linearInterpolatorN);
        local i = 0;
        while true do
            local keyI = string.format("%s.wrapperAnimation.key(%d)", key, i);
            local t = getXMLFloat(self.xmlFile, keyI.."#time");
            local baleX, baleY, baleZ = Utils.getVectorFromString(getXMLString(self.xmlFile, keyI.."#baleRot"));
            if baleX == nil or baleY == nil or baleZ == nil then
                break;
            end;
            baleX = math.rad(Utils.getNoNil(baleX, 0));
            baleY = math.rad(Utils.getNoNil(baleY, 0));
            baleZ = math.rad(Utils.getNoNil(baleZ, 0));
            local wrapperX, wrapperY, wrapperZ = Utils.getVectorFromString(getXMLString(self.xmlFile, keyI.."#wrapperRot"));
            wrapperX = math.rad(Utils.getNoNil(wrapperX, 0));
            wrapperY = math.rad(Utils.getNoNil(wrapperY, 0));
            wrapperZ = math.rad(Utils.getNoNil(wrapperZ, 0));
            wrappingAnimCurve:addKeyframe({ v={baleX, baleY, baleZ, wrapperX, wrapperY, wrapperZ}, time = t});
            i = i +1;
        end;
        self[baleCategory].animCurve = wrappingAnimCurve;
        self[baleCategory].animTime = Utils.getNoNil(getXMLFloat(self.xmlFile, key .. "#wrappingTime"), 5) * 1000;
        self[baleCategory].currentTime = 0;
        self[baleCategory].currentBale = nil;
        self[baleCategory].wrapAnimNodes = {};
        local i = 0;
        while true do
            local wrapAnimNodeKey = string.format("vehicle.wrapper.%s.wrapAnimNodes.wrapAnimNode(%d)", baleCategory, i);
            if not hasXMLProperty(self.xmlFile, wrapAnimNodeKey) then
                break;
            end;
            local nodeId = Utils.indexToObject(self.components, getXMLString(self.xmlFile, wrapAnimNodeKey.."#index"));
            if nodeId ~= nil then
                local animCurve = AnimCurve:new(linearInterpolatorN);
                local keyI = 0;
                local useWrapperRot = false;
                while true do
                    local key = string.format(wrapAnimNodeKey..".key(%d)", keyI);
                    local wrapperRot = getXMLFloat(self.xmlFile, key.."#wrapperRot");
                    local wrapperTime = getXMLFloat(self.xmlFile, key.."#wrapperTime");
                    if wrapperRot == nil and wrapperTime == nil then
                        break;
                    end;
                    useWrapperRot = wrapperRot ~= nil;
                    local x, y, z = Utils.getVectorFromString(getXMLString(self.xmlFile, key.."#trans"));
                    local rx, ry, rz = Utils.getVectorFromString(getXMLString(self.xmlFile, key.."#rot"));
                    local sx, sy, sz = Utils.getVectorFromString(getXMLString(self.xmlFile, key.."#scale"));
                    x = Utils.getNoNil(x, 0);
                    y = Utils.getNoNil(y, 0);
                    z = Utils.getNoNil(z, 0);
                    rx = math.rad(Utils.getNoNil(rx, 0));
                    ry = math.rad(Utils.getNoNil(ry, 0));
                    rz = math.rad(Utils.getNoNil(rz, 0));
                    sx = Utils.getNoNil(sx, 1);
                    sy = Utils.getNoNil(sy, 1);
                    sz = Utils.getNoNil(sz, 1);
                    if wrapperRot ~= nil then
                        animCurve:addKeyframe({ v={x, y, z, rx, ry, rz, sx,sy,sz}, time = math.rad(wrapperRot)});
                    else
                        animCurve:addKeyframe({ v={x, y, z, rx, ry, rz, sx,sy,sz}, time = wrapperTime});
                    end;
                    keyI = keyI +1;
                end;
                if keyI > 0 then
                    local repeatWrapperRot = Utils.getNoNil(getXMLBool(self.xmlFile, wrapAnimNodeKey.."#repeatWrapperRot"), false);
                    local normalizeRotationOnBaleDrop = Utils.getNoNil(getXMLInt(self.xmlFile, wrapAnimNodeKey.."#normalizeRotationOnBaleDrop"), 0);
                    table.insert(self[baleCategory].wrapAnimNodes,
                        {nodeId = nodeId, animCurve = animCurve, repeatWrapperRot = repeatWrapperRot, normalizeRotationOnBaleDrop = normalizeRotationOnBaleDrop, useWrapperRot = useWrapperRot});
                end;
            end;
            i = i + 1;
        end;
        self[baleCategory].wrapNodes = {};
        local i = 0;
        while true do
            local wrapNodeKey = string.format("vehicle.wrapper.%s.wrapNodes.wrapNode(%d)", baleCategory, i);
            if not hasXMLProperty(self.xmlFile, wrapNodeKey) then
                break;
            end;
            local nodeId = Utils.indexToObject(self.components, getXMLString(self.xmlFile, wrapNodeKey.."#index"));
            local wrapVisibility = Utils.getNoNil(getXMLBool(self.xmlFile, wrapNodeKey.."#wrapVisibility"), false);
            local emptyVisibility = Utils.getNoNil(getXMLBool(self.xmlFile, wrapNodeKey.."#emptyVisibility"), false);
            if nodeId ~= nil and (wrapVisibility or emptyVisibility) then
                local maxWrapperRot = getXMLFloat(self.xmlFile, wrapNodeKey.."#maxWrapperRot");
                if maxWrapperRot == nil then
                    maxWrapperRot = math.huge;
                else
                    maxWrapperRot = math.rad(maxWrapperRot);
                end;
                table.insert(self[baleCategory].wrapNodes, {nodeId=nodeId, wrapVisibility=wrapVisibility, emptyVisibility=emptyVisibility, maxWrapperRot=maxWrapperRot});
            end;
            i = i + 1;
        end;
        local defaultText = baleCategory == "roundBaleWrapper" and "action_unloadRoundBale" or "action_unloadBaler";
        self[baleCategory].unloadBaleText = Utils.getNoNil(getXMLString(self.xmlFile, key .. "#unloadBaleText"), defaultText);
    end;
    self.currentWrapper = {};
    self.currentWrapperFoldMinLimit = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.wrapper#foldMinLimit"), 0);
    self.currentWrapperFoldMaxLimit = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.wrapper#foldMaxLimit"), 1);
    self.showInvalidBaleWarning = false;
    --
    self.currentWrapper = self.roundBaleWrapper;
    self:updateWrapNodes(false, true, 0);
    self.currentWrapper = self.squareBaleWrapper;
    self:updateWrapNodes(false, true, 0);
    self.baleGrabber = {};
    self.baleGrabber.grabNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.baleGrabber#index"));
    self.baleGrabber.nearestDistance = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.baleGrabber#nearestDistance"), 3.0);
    self.baleGrabber.origTrans = { getTranslation(self.baleGrabber.grabNode) };
    if self.isClient then
        self.currentWrapperSound = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.wrapperSound", nil, self.baseDirectory);
        self.currentWrapperStartSound = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.wrapperStartSound", nil, self.baseDirectory);
        self.currentWrapperStopSound = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.wrapperStopSound", nil, self.baseDirectory);
    end;
    self.baleToLoad = nil;
    self.baleToMount = nil;
    self.baleWrapperState = baleWrapperRC.STATE_NONE;
    self.grabberIsMoving = false;
    self.hasBaleWrapper = true;
    if savegame ~= nil and not savegame.resetVehicles then
        local filename = getXMLString(savegame.xmlFile, savegame.key.."#baleFileName");
        if filename ~= nil then
            filename = Utils.convertFromNetworkFilename(filename);
            local wrapperTime = Utils.getNoNil(getXMLFloat(savegame.xmlFile, savegame.key.."#wrapperTime"), 0);
            local baleValueScale = Utils.getNoNil(getXMLFloat(savegame.xmlFile, savegame.key.."#baleValueScale"), 1);
            local fillLevel = getXMLFloat(savegame.xmlFile, savegame.key.."#fillLevel");
            local translation = {0,0,0};
            local rotation={0,0,0};
            self.baleToLoad = {filename=filename, translation=translation, rotation=rotation, fillLevel=fillLevel, wrapperTime=wrapperTime, baleValueScale=baleValueScale};
        end;
    end;
end;

function baleWrapperRC:delete()
    if self.isServer then
        local baleId;
        if self.currentWrapper.currentBale ~= nil then
            baleId = self.currentWrapper.currentBale;
        end;
        if self.baleGrabber.currentBale ~= nil then
            baleId = self.baleGrabber.currentBale;
        end;
        if baleId ~= nil then
            local bale = networkGetObject(baleId);
            if bale ~= nil then
                bale:unmount();
            end;
        end;
    end;
    if self.isClient then
        SoundUtil.deleteSample(self.currentWrapperSound);
        SoundUtil.deleteSample(self.currentWrapperStartSound);
        SoundUtil.deleteSample(self.currentWrapperStopSound);
    end;
end;

function baleWrapperRC:readStream(streamId, connection)
    if connection:getIsServer() then
        local wrapperState = streamReadUIntN(streamId, baleWrapperRC.STATE_NUM_BITS);
        if wrapperState >= baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER then
            local baleServerId;
            if wrapperState ~= baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM then
                baleServerId = readNetworkNodeObjectId(streamId);
            end;
            if wrapperState == baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER then
                self:doStateChange(baleWrapperRC.CHANGE_GRAB_BALE, baleServerId);
                AnimatedVehicle.updateAnimations(self, 99999999);
            elseif wrapperState == baleWrapperRC.STATE_MOVING_GRABBER_TO_WORK then
                self.baleGrabber.currentBale = baleServerId;
                self:doStateChange(baleWrapperRC.CHANGE_DROP_BALE_AT_GRABBER);
                AnimatedVehicle.updateAnimations(self, 99999999);
            elseif wrapperState ~= baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM then
                local attachNode = self.currentWrapper.baleNode;
                self.baleToMount = {serverId=baleServerId, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
                self:updateWrapNodes(true, false, 0);
                self.currentWrapper.currentBale = baleServerId;
                if wrapperState == baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE then
                    local wrapperTime = streamReadFloat32(streamId);
                    self.currentWrapper.currentTime = wrapperTime;
                    self:updateWrappingState(self.currentWrapper.currentTime / self.currentWrapper.animTime, true);
                else
                    self.currentWrapper.currentTime = self.currentWrapper.animTime;
                    self:updateWrappingState(1, true);
                    self:doStateChange(baleWrapperRC.CHANGE_WRAPPING_BALE_FINSIHED);
                    AnimatedVehicle.updateAnimations(self, 99999999);
                    if wrapperState >= baleWrapperRC.STATE_WRAPPER_DROPPING_BALE then
                        self:doStateChange(baleWrapperRC.CHANGE_WRAPPER_START_DROP_BALE);
                        AnimatedVehicle.updateAnimations(self, 99999999);
                    end;
                end;
            else
                self.baleWrapperState = baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM;
            end;
        end;
    end;
end;

function baleWrapperRC:writeStream(streamId, connection)
    if not connection:getIsServer() then
        local wrapperState = self.baleWrapperState;
        streamWriteUIntN(streamId, wrapperState, baleWrapperRC.STATE_NUM_BITS);
        if wrapperState >= baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER and wrapperState ~= baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM then
            if wrapperState == baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER then
                writeNetworkNodeObjectId(streamId, self.baleGrabber.currentBale);
            else
                writeNetworkNodeObjectId(streamId, self.currentWrapper.currentBale);
            end;
        end;
        if wrapperState == baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE then
            streamWriteFloat32(streamId, self.currentWrapper.currentTime);
        end;
    end;
end;

function baleWrapperRC:mouseEvent(posX, posY, isDown, isUp, button)
end;

function baleWrapperRC:keyEvent(unicode, sym, modifier, isDown)
end;

function baleWrapperRC:update(dt)
    if self.firstTimeRun then
        if self.baleToLoad ~= nil then
            local v = self.baleToLoad;
            self.baleToLoad = nil;
            local baleObject = Bale:new(self.isServer, self.isClient);
            local x,y,z = unpack(v.translation);
            local rx,ry,rz = unpack(v.rotation);
            baleObject:load(v.filename, x,y,z,rx,ry,rz, v.fillLevel);
            if baleObject.nodeId ~= nil and baleObject.nodeId ~= 0 then
                self:playMoveToWrapper(baleObject);
                baleObject.baleValueScale = v.baleValueScale;
                local wrapperState = math.min(v.wrapperTime / self.currentWrapper.animTime, 1);
                baleObject:setWrappingState(wrapperState);
                baleObject:mount(self, self.currentWrapper.baleNode, x,y,z, rx,ry,rz)
                baleObject:register();
                self:doStateChange(baleWrapperRC.CHANGE_WRAPPING_START);
                self.currentWrapper.currentBale = networkGetObjectId(baleObject);
                self.currentWrapper.currentTime = v.wrapperTime;
                self:updateWrappingState(self.currentWrapper.currentTime / self.currentWrapper.animTime);
            end;
        end;
        if self.baleToMount ~= nil then
            local bale = networkGetObject(self.baleToMount.serverId);
            if bale ~= nil then
                local x,y,z = unpack(self.baleToMount.trans);
                local rx,ry,rz = unpack(self.baleToMount.rot);
                bale:mount(self, self.baleToMount.linkNode, x,y,z, rx,ry,rz);
                self.baleToMount = nil;
                if self.baleWrapperState == baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER then
                    self:playMoveToWrapper(bale)
                end;
            end;
        end;
    end;
    if self.turnOnVehicle.isTurnedOn then
        if self:getIsActiveForInput() then
            if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA3) then
                if self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_FINSIHED then
                    g_client:getServerConnection():sendEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_BUTTON_EMPTY));
                end;
            end;
        end;
        if self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE then
            if self.isClient and self:getIsActiveForSound() then
                if not self.currentWrapperSound.isPlaying then
                    if self.currentWrapperStartSound.sample == nil or not isSamplePlaying(self.currentWrapperStartSound.sample) or getSamplePlayOffset(self.currentWrapperStartSound.sample) >= self.currentWrapperStartSound.duration-1.8*dt then
                        SoundUtil.playSample(self.currentWrapperSound, 0, 0);
                    end;
                end;
            end;
            self.currentWrapper.currentTime = self.currentWrapper.currentTime + dt;
            self:updateWrappingState(self.currentWrapper.currentTime / self.currentWrapper.animTime);
        end;
	else
        if self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE or self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_FINSIHED  then
			self:setIsTurnedOn(true);
		end;
    end;
end;

function baleWrapperRC:updateTick(dt)
    if self.turnOnVehicle.isTurnedOn then
		--self.showInvalidBaleWarning = false;
        if self:allowsGrabbingBale() then
            if self.baleGrabber.grabNode ~= nil and self.baleGrabber.currentBale == nil then
				local nearestBale, nearestBaleType = baleWrapperRC.getBaleInRange(self, self.baleGrabber.grabNode, self.baleGrabber.nearestDistance);
                if nearestBale ~= nil then
                    if nearestBaleType == nil then
                        if self.lastDroppedBale ~= nearestBale then
                           -- self.showInvalidBaleWarning = true;
                        end;
                    elseif self.isServer then
                        self:pickupWrapperBale(nearestBale, nearestBaleType);
                    end;
                end;
            end;
        end;
        if self.isServer then
            if self.baleWrapperState ~= baleWrapperRC.STATE_NONE then
                if self.baleWrapperState == baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER then
                    if not self:getIsAnimationPlaying(self.currentWrapper.animations["moveToWrapper"].animName) then
                        g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_DROP_BALE_AT_GRABBER), true, nil, self);
                    end;
                elseif self.baleWrapperState == baleWrapperRC.STATE_MOVING_GRABBER_TO_WORK then
                    if not self:getIsAnimationPlaying(self.currentWrapper.animations["moveToWrapper"].animName) then
                        local bale = networkGetObject(self.currentWrapper.currentBale);
                        if bale ~= nil and not bale.supportsWrapping then
                            g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPER_START_DROP_BALE), true, nil, self);
                        else
                            g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPING_START), true, nil, self);
                        end;
                    end;
                elseif self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_DROPPING_BALE then
                    if not self:getIsAnimationPlaying(self.currentWrapper.animations["dropFromWrapper"].animName) then
                        g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPER_BALE_DROPPED), true, nil, self);
                    end;
                elseif self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM then
                    if not self:getIsAnimationPlaying(self.currentWrapper.animations["resetAfterDrop"].animName) then
                        g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPER_PLATFORM_RESET), true, nil, self);
                    end;
                end;
            end;
        end;
    end;
end;

function baleWrapperRC:draw()
    if self.isClient then
        if self:getIsActiveForInput(true) then
            if self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_FINSIHED then
                local i18n = g_i18n;
                if self.customEnvironment ~= nil then
                    i18n = _G[self.customEnvironment].g_i18n;
                end;
                g_currentMission:addHelpButtonText(i18n:getText(self.currentWrapper.unloadBaleText), InputBinding.IMPLEMENT_EXTRA3, nil, GS_PRIO_HIGH);
            end;
        end;
        if self.showInvalidBaleWarning then
            g_currentMission:showBlinkingWarning(g_i18n:getText("warning_baleNotSupported"));
        end;
    end;
end;

function baleWrapperRC:getSaveAttributesAndNodes(nodeIdent)
    local attributes = "";
    local baleServerId = self.baleGrabber.currentBale;
    if baleServerId == nil then
        baleServerId = self.currentWrapper.currentBale;
    end;
    if baleServerId ~= nil then
        local bale = networkGetObject(baleServerId);
        if bale ~= nil then
            local fillLevel = bale:getFillLevel();
            local baleValueScale = bale.baleValueScale;
            attributes = 'baleFileName="'..Utils.encodeToHTML(Utils.convertToNetworkFilename(bale.i3dFilename))..'" fillLevel="'..fillLevel..'" wrapperTime="'..tostring(self.currentWrapper.currentTime)..'" baleValueScale="'..baleValueScale..'"';
        end;
    end;
    return attributes, nil;
end;

function baleWrapperRC:onDeactivate()
    self.showInvalidBaleWarning = false;
end;

function baleWrapperRC:onDeactivateSounds()
    if self.isClient then
        SoundUtil.stopSample(self.currentWrapperStartSound, true);
        SoundUtil.stopSample(self.currentWrapperStopSound, true);
        SoundUtil.stopSample(self.currentWrapperSound, true);
    end;
end;

function baleWrapperRC:allowsGrabbingBale()
    local foldAnimTime = self.foldAnimTime;
    if foldAnimTime ~= nil and (foldAnimTime > self.currentWrapperFoldMaxLimit or foldAnimTime < self.currentWrapperFoldMinLimit) then
        return false;
    end;
    return self.baleWrapperState == baleWrapperRC.STATE_NONE;
end;

function baleWrapperRC:updateWrapNodes(isWrapping, isEmpty, t, wrapperRot)
    if wrapperRot == nil then
        wrapperRot = 0;
    end;
    for _, wrapNode in pairs(self.currentWrapper.wrapNodes) do
        local doShow = true;
        if wrapNode.maxWrapperRot ~= nil then
            doShow = wrapperRot < wrapNode.maxWrapperRot;
        end;
        setVisibility(wrapNode.nodeId, doShow and ((isWrapping and wrapNode.wrapVisibility) or (isEmpty and wrapNode.emptyVisibility)));
    end;
    if isWrapping then
        local wrapperRotRepeat = Utils.sign(wrapperRot) * (wrapperRot % math.pi);
        if wrapperRotRepeat < 0 then
            wrapperRotRepeat = wrapperRotRepeat + math.pi;
        end;
        for _,wrapAnimNode in pairs(self.currentWrapper.wrapAnimNodes) do
            local v;
            if wrapAnimNode.useWrapperRot then
                local rot = wrapperRot;
                if wrapAnimNode.repeatWrapperRot then
                    rot = wrapperRotRepeat;
                end;
                v = wrapAnimNode.animCurve:get(rot);
            else
                v = wrapAnimNode.animCurve:get(t);
            end;
            if v ~= nil then
                setTranslation(wrapAnimNode.nodeId, v[1], v[2], v[3]);
                setRotation(wrapAnimNode.nodeId, v[4], v[5], v[6]);
                setScale(wrapAnimNode.nodeId, v[7], v[8], v[9]);
            end;
        end;
    else
        if not isEmpty then
            for _,wrapAnimNode in pairs(self.currentWrapper.wrapAnimNodes) do
                if wrapAnimNode.normalizeRotationOnBaleDrop ~= 0 then
                    local rot = { getRotation(wrapAnimNode.nodeId) };
                    for i=1,3 do
                        rot[i] = wrapAnimNode.normalizeRotationOnBaleDrop * Utils.sign(rot[i]) * ( rot[i] % (2*math.pi) );
                    end;
                    setRotation(wrapAnimNode.nodeId, rot[1],rot[2],rot[3]);
                end;
            end;
        end;
    end;
end;

function baleWrapperRC:updateWrappingState(t, noEventSend)
    t = math.min(t, 1);
    local wrapperRot = 0;
    if self.currentWrapper.animCurve ~= nil then
        local v = self.currentWrapper.animCurve:get(t);
        if v ~= nil then
            setRotation(self.currentWrapper.baleNode, v[1]%(math.pi*2), v[2]%(math.pi*2), v[3]%(math.pi*2));
            setRotation(self.currentWrapper.wrapperNode, v[4]%(math.pi*2), v[5]%(math.pi*2), v[6]%(math.pi*2));
            wrapperRot = v[3 + self.currentWrapper.wrapperRotAxis];
        elseif self.currentWrapper.animations["wrapBale"].animName ~= nil then
            t = self:getAnimationTime(self.currentWrapper.animations["wrapBale"].animName);
        end;
        if self.currentWrapper.currentBale ~= nil and self.isServer then
            local bale = networkGetObject(self.currentWrapper.currentBale);
            if bale ~= nil then
                bale:setWrappingState(t);
            end;
        end;
    end;
    self:updateWrapNodes(t > 0, false, t, wrapperRot);
    if t == 1 then
        if self.isServer and self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE and not noEventSend then
            g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPING_BALE_FINSIHED), true, nil, self);
        end;
    end;
end;

function baleWrapperRC:moveBaleToWrapper(bale)
    local baleType = self:getWrapperBaleType(bale);
    self:pickupWrapperBale(bale, baleType);
end;

function baleWrapperRC:playMoveToWrapper(bale)
    self.currentWrapper = self.roundBaleWrapper;
    if bale.baleDiameter == nil then
        self.currentWrapper = self.squareBaleWrapper;
    end;
    if self.currentWrapper.animations["moveToWrapper"].animName ~= nil then
        self:playAnimation(self.currentWrapper.animations["moveToWrapper"].animName, self.currentWrapper.animations["moveToWrapper"].animSpeed, nil, true);
    end;
end;

function baleWrapperRC:doStateChange(id, nearestBaleServerId)
    if id == baleWrapperRC.CHANGE_GRAB_BALE then
        local bale = networkGetObject(nearestBaleServerId);
        self.baleGrabber.currentBale = nearestBaleServerId;
        if bale ~= nil then
            local x,y,z = localToLocal(bale.nodeId, getParent(self.baleGrabber.grabNode), 0,0,0);
            setTranslation(self.baleGrabber.grabNode, x,y,z);
            bale:mount(self, self.baleGrabber.grabNode, 0,0,0, 0,0,0);
            self.baleToMount = nil;
            self:playMoveToWrapper(bale)
        else
            self.baleToMount = {serverId=nearestBaleServerId, linkNode=self.baleGrabber.grabNode, trans={0,0,0}, rot={0,0,0} };
        end;
        self.baleWrapperState = baleWrapperRC.STATE_MOVING_BALE_TO_WRAPPER;
    elseif id == baleWrapperRC.CHANGE_DROP_BALE_AT_GRABBER then
        -- drop bale at wrapper
        local attachNode = self.currentWrapper.baleNode;
        local bale = networkGetObject(self.baleGrabber.currentBale);
        if bale ~= nil then
            bale:mount(self, attachNode, 0,0,0, 0,0,0);
            self.baleToMount = nil;
        else
            self.baleToMount = {serverId=self.baleGrabber.currentBale, linkNode=attachNode, trans={0,0,0}, rot={0,0,0} };
        end;
        self:updateWrapNodes(true, false, 0);
        self.currentWrapper.currentBale = self.baleGrabber.currentBale;
        self.baleGrabber.currentBale = nil;
        if self.currentWrapper.animations["moveToWrapper"].animName ~= nil then
            if self.currentWrapper.animations["moveToWrapper"].reverseAfterMove then
                self:playAnimation(self.currentWrapper.animations["moveToWrapper"].animName, -self.currentWrapper.animations["moveToWrapper"].animSpeed, nil, true);
            end;
        end;
        self.baleWrapperState = baleWrapperRC.STATE_MOVING_GRABBER_TO_WORK;
    elseif id == baleWrapperRC.CHANGE_WRAPPING_START then
        self.baleWrapperState = baleWrapperRC.STATE_WRAPPER_WRAPPING_BALE;
        if self.isClient and self:getIsActiveForSound() then
            SoundUtil.playSample(self.currentWrapperStartSound, 1, 0);
        end;
        if self.currentWrapper.animations["wrapBale"].animName ~= nil then
            self:playAnimation(self.currentWrapper.animations["wrapBale"].animName, self.currentWrapper.animations["wrapBale"].animSpeed, nil, true);
        end;
    elseif id == baleWrapperRC.CHANGE_WRAPPING_BALE_FINSIHED then
        if self.isClient then
            SoundUtil.stopSample(self.currentWrapperSound);
            if self.isClient and self:getIsActiveForSound() then
                SoundUtil.playSample(self.currentWrapperStopSound, 1, 0);
            end;
        end;
        self:updateWrappingState(1, true);
        self.baleWrapperState = baleWrapperRC.STATE_WRAPPER_FINSIHED;
    elseif id == baleWrapperRC.CHANGE_WRAPPER_START_DROP_BALE then
        self:updateWrapNodes(false, false, 0);
        if self.currentWrapper.animations["dropFromWrapper"].animName ~= nil then
            self:playAnimation(self.currentWrapper.animations["dropFromWrapper"].animName, self.currentWrapper.animations["dropFromWrapper"].animSpeed, nil, true);
        end;
        self.baleWrapperState = baleWrapperRC.STATE_WRAPPER_DROPPING_BALE;
    elseif id == baleWrapperRC.CHANGE_WRAPPER_BALE_DROPPED then
        local bale = networkGetObject(self.currentWrapper.currentBale);
        if bale ~= nil then
            bale:unmount();
        end;
        self.lastDroppedBale = bale;
        self.currentWrapper.currentBale = nil;
        self.currentWrapper.currentTime = 0;
        if self.currentWrapper.animations["resetAfterDrop"].animName ~= nil then
            self:playAnimation(self.currentWrapper.animations["resetAfterDrop"].animName, self.currentWrapper.animations["resetAfterDrop"].animSpeed, nil, true);
        end;
        self.baleWrapperState = baleWrapperRC.STATE_WRAPPER_RESETTING_PLATFORM;
    elseif id == baleWrapperRC.CHANGE_WRAPPER_PLATFORM_RESET then
        self:updateWrappingState(0);
        self:updateWrapNodes(false, true, 0);
        self.baleWrapperState = baleWrapperRC.STATE_NONE;
		
		self:setIsTurnedOn(false);
    elseif id == baleWrapperRC.CHANGE_BUTTON_EMPTY then
        -- Server only code
        assert(self.isServer);
        if self.baleWrapperState == baleWrapperRC.STATE_WRAPPER_FINSIHED then
            g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_WRAPPER_START_DROP_BALE), true, nil, self);
        end;
    end;
end;

function baleWrapperRC:setIsUnloadingBale(isUnloadingBale, noEventSend)
    if self.baler ~= nil and self.baler.baleUnloadAnimationName ~= nil then
        if isUnloadingBale then
            if self.baleWrapperUnloadingState ~= baleWrapperRC.UNLOADING_OPENING then
                BaleWrapperSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.baleWrapperUnloadingState = baleWrapperRC.UNLOADING_OPENING;
                self:playAnimation(self.baler.baleUnloadAnimationName, self.baler.baleUnloadAnimationSpeed, nil, true);
            end;
        else
            if self.baleWrapperUnloadingState ~= baleWrapperRC.UNLOADING_CLOSING then
                BaleWrapperSetIsUnloadingBaleEvent.sendEvent(self, isUnloadingBale, noEventSend)
                self.baleWrapperUnloadingState = baleWrapperRC.UNLOADING_CLOSING;
                self:playAnimation(self.baler.baleCloseAnimationName, self.baler.baleCloseAnimationSpeed, nil, true);
            end;
        end;
    end;
end;

function baleWrapperRC:getWrapperBaleType(bale)
    local baleTypes;
    if bale.baleDiameter ~= nil then
        baleTypes = self.roundBaleWrapper.allowedBaleTypes[bale:getFillType()];
    else
        baleTypes = self.squareBaleWrapper.allowedBaleTypes[bale:getFillType()];
    end;
    if baleTypes ~= nil then
        for _, baleType in pairs(baleTypes) do
            if bale.baleDiameter ~= nil and bale.baleWidth ~= nil then
                if bale.baleDiameter >= baleType.minBaleDiameter and bale.baleDiameter <= baleType.maxBaleDiameter and
                   bale.baleWidth >= baleType.minBaleWidth and bale.baleWidth <= baleType.maxBaleWidth
                   then
                    return baleType;
                end;
            elseif bale.baleHeight ~= nil and bale.baleWidth ~= nil and bale.baleLength ~= nil then
                if bale.baleHeight >= baleType.minBaleHeight and bale.baleHeight <= baleType.maxBaleHeight and
                   bale.baleWidth >= baleType.minBaleWidth and bale.baleWidth <= baleType.maxBaleWidth and
                   bale.baleLength >= baleType.minBaleLength and bale.baleLength <= baleType.maxBaleLength
                   then
                    return baleType;
                end;
            end;
        end;
    end;
    return nil;
end;

function baleWrapperRC:pickupWrapperBale(bale, baleType)
    if baleType ~= nil and bale.i3dFilename ~= baleType.wrapperBaleFilename then
        local x,y,z = getWorldTranslation(bale.nodeId);
        local rx,ry,rz = getWorldRotation(bale.nodeId);
        local fillLevel = bale.fillLevel;
        local baleValueScale = bale.baleValueScale;
        bale:delete();
        bale = Bale:new(self.isServer, self.isClient);
        bale:load(baleType.wrapperBaleFilename, x,y,z, rx,ry,rz, fillLevel);
        bale.baleValueScale = baleValueScale;
        bale:register();
    end;
    -- found bale
    g_server:broadcastEvent(BaleWrapperStateEvent:new(self, baleWrapperRC.CHANGE_GRAB_BALE, networkGetObjectId(bale)), true, nil, self);
end;

function baleWrapperRC.getBaleInRange(self, refNode, distance)
    local px, py, pz = getWorldTranslation(refNode);
    local nearestDistance = distance;
    local nearestBale = nil;
    local nearestBaleType;
    for _, item in pairs(g_currentMission.itemsToSave) do
        local bale = item.item;
        if bale:isa(Bale) then
            local vx, vy, vz = getWorldTranslation(bale.nodeId);
            local maxDist;
            if bale.baleDiameter ~= nil then
                maxDist = math.min(bale.baleDiameter, bale.baleWidth);
            else
                maxDist = math.min(bale.baleLength, bale.baleHeight, bale.baleWidth);
            end;
            local _,_,z = localToLocal(bale.nodeId, refNode, 0,0,0);
            if math.abs(z) < nearestDistance and Utils.vector3Length(px-vx, py-vy, pz-vz) < maxDist then
                local foundBaleType;
                if not bale.supportsWrapping or bale.wrappingState < 0.99 then
                    foundBaleType = self:getWrapperBaleType(bale);
                end;
                if foundBaleType ~= nil or nearestBaleType == nil then
                    if foundBaleType ~= nil then
                        nearestDistance = distance;
                    end;
                    nearestBale = bale;
                    nearestBaleType = foundBaleType;
                end;
            end;
        end;
    end;
    return nearestBale, nearestBaleType;
end;

function baleWrapperRC:getIsFoldAllowed(superFunc, onAiTurnOn)
    if self.baleWrapperState ~= baleWrapperRC.STATE_NONE then
        return false;
    end;
    if superFunc ~= nil then
        return superFunc(self, onAiTurnOn);
    end;
    return true;
end;