--
-- EasyFlow
-- This is the specialization for EasyFlow
-- It reacts both to steerable and attachable events
--
-- @author  Stefan Geiger
-- @date  04/12/08
--
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
--
--
-- @coAuthor Milan Lekše [milan1702976]
-- @date  18/02/13
--
--
-- Copyright (C) www.FS-UK.com, Confidential, All Rights Reserved.

EasyFlow = {};

function EasyFlow.prerequisitesPresent(specializations)
    return true;
end;

function EasyFlow:load(xmlFile)

    self.onStartReel = SpecializationUtil.callSpecializationsFunction("onStartReel");
    self.onStopReel = SpecializationUtil.callSpecializationsFunction("onStopReel");
    --self.setFruitType = SpecializationUtil.callSpecializationsFunction("setFruitType");
    self.getCombine = EasyFlow.getCombine;
    self.applyInitialAnimation = Utils.overwrittenFunction(self.applyInitialAnimation, EasyFlow.applyInitialAnimation);
    self.getDirectionSnapAngle = Utils.overwrittenFunction(self.getDirectionSnapAngle, EasyFlow.getDirectionSnapAngle);

    self.reelNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.reel#index"));
    self.reelSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.reel#speed"), 0.003);

    self.reelSpeedScale = 1;

    self.rollNodes = {};

    local rollNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.roll#index"));
    if rollNode ~= nil then
        local speed = 0.003*3;
        table.insert(self.rollNodes, {node=rollNode, speed=speed});
    end;
    local i = 0;
    while true do
        local key = string.format("vehicle.rolls.roll(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local rollNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
        local speed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#speed"), 0.003);
        if rollNode ~= nil then
            table.insert(self.rollNodes, {node=rollNode, speed=speed});
        end;
        i = i + 1;
    end;

    local indexSpikesStr = getXMLString(xmlFile, "vehicle.reelspikes#index");
    self.spikesCount = getXMLInt(xmlFile, "vehicle.reelspikes#count");
    self.spikesRootNode = Utils.indexToObject(self.components, indexSpikesStr);

    self.sideArm = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.sidearms#index"));
    self.sideArmMovable = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.sidearms#movable"), false);

    self.cutterStartAnimation = getXMLString(xmlFile, "vehicle.cutterStartAnimation#name");
    self.cutterStartAnimationSpeedScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.cutterStartAnimation#speedScale"), 1);
    self.cutterStartAnimationInitialIsStarted = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.cutterStartAnimation#initialIsStarted"), false);

    self.cutterAllowCuttingWhileRaised = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.cutterAllowCuttingWhileRaised"), false);

    self.cutterSpeedRotatingParts = {};
    local i=0;
    while true do
        local baseName = string.format("vehicle.cutterSpeedRotatingParts.cutterSpeedRotatingPart(%d)", i);
        local index = getXMLString(xmlFile, baseName.. "#index");
        if index == nil then
            break;
        end;
        local node = Utils.indexToObject(self.components, index);
        if node ~= nil then
            local entry = {};
            entry.node = node;
            entry.rotationSpeedScale = getXMLFloat(xmlFile, baseName.."#rotationSpeedScale");
            if entry.rotationSpeedScale == nil then
                entry.rotationSpeedScale = 1.0/Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#radius"), 1);
            end;

            entry.foldMinLimit = Utils.getNoNil(getXMLFloat(xmlFile, baseName .. "#foldMinLimit"), 0);
            entry.foldMaxLimit = Utils.getNoNil(getXMLFloat(xmlFile, baseName .. "#foldMaxLimit"), 1);

            table.insert(self.cutterSpeedRotatingParts, entry);
        end;
        i = i+1;
    end;

    self.threshingParticleSystems = {};
    local psName = "vehicle.threshingParticleSystem";
    Utils.loadParticleSystem(xmlFile, self.threshingParticleSystems, psName, self.components, false, nil, self.baseDirectory)

    self.fruitExtraObjects = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.fruitExtraObjects.fruitExtraObject(%d)", i);
        local t = getXMLString(xmlFile, key.."#fruitType");
        local index = getXMLString(xmlFile, key.."#index");
        if t==nil or index==nil then
            break;
        end;

        local node = Utils.indexToObject(self.components, index);
        if node ~= nil then
            if self.currentExtraObject == nil then
                self.currentExtraObject = node;
                setVisibility(node, true);
            else
                setVisibility(node, false);
            end;
            self.fruitExtraObjects[t] = node;
        end;
        i = i +1;
    end;

    self.cutterThreshingUVScrollParts = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.cutterThreshingUVScrollParts.cutterThreshingUVScrollPart(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index"));
        local speed = Utils.getVectorNFromString(getXMLString(xmlFile, key.."#speed"), 2);
        if node ~= nil and speed then
            table.insert(self.cutterThreshingUVScrollParts, {node=node, speed=speed});
        end;
        i = i +1;
    end;

    self.preferedCombineSize = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.preferedCombineSize"), 1);

    self.fruitTypes = {};
    local fruitTypes = getXMLString(xmlFile, "vehicle.fruitTypes#fruitTypes");
    if fruitTypes ~= nil then
        local types = Utils.splitString(" ", fruitTypes);
        for k,v in pairs(types) do
            local desc = FruitUtil.fruitTypes[v];
            if desc ~= nil then
                self.fruitTypes[desc.index] = true;
            end;
        end;
    end;

    self.convertedFruits = {};
    local i = 0;
    while true do
        local key = string.format("vehicle.convertedFruits.convertedFruit(%d)", i);
        if not hasXMLProperty(xmlFile, key) then
            break;
        end;
        local inputType = getXMLString(xmlFile, key .. "#input");
        local outputType = getXMLString(xmlFile, key .. "#output");

        if inputType ~= nil and outputType ~= nil then
            local inputDesc = FruitUtil.fruitTypes[inputType];
            local outputDesc = FruitUtil.fruitTypes[outputType];
            if inputDesc ~= nil and outputDesc ~= nil then
                self.convertedFruits[inputDesc.index] = outputDesc.index;
            end;
        end;

        i = i + 1;
    end;


    self.cutterMovingDirection = Utils.sign(Utils.getNoNil(getXMLInt(xmlFile, "vehicle.cutterMovingDirection"), -1));

    self.currentInputFruitType = FruitUtil.FRUITTYPE_UNKNOWN;

    self.reelStarted = false;

    self.forceLowSpeed = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.cutterSpeedLimit#forceLow"), false);
    self.speedLimitLow = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.cutterSpeedLimit#low"), 12);
    self.speedLimit = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.cutterSpeedLimit#normal"), 17.5);

    self.speedViolationMaxTime = 50;
    self.speedViolationTimer = self.speedViolationMaxTime;


    self.lastCutterArea = 0;
    self.lastCutterAreaBiggerZero = self.lastCutterArea > 0;

    self.cutterGroundFlag = self:getNextDirtyFlag();

    self.aiLeftCheck = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiLeftCheck#index"));
    self.aiRightCheck = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRightCheck#index"));

    self.dtSum = 0;
end;

function EasyFlow:applyInitialAnimation(superFunc)
    if self.cutterStartAnimation ~= nil and self.playAnimation ~= nil and self.cutterStartAnimationInitialIsStarted then
        self:playAnimation(self.cutterStartAnimation, -self.cutterStartAnimationSpeedScale, nil, true);
        AnimatedVehicle.updateAnimations(self, 99999999);
    end
    if superFunc ~= nil then
        superFunc(self);
    end
end

function EasyFlow:delete()
    Utils.deleteParticleSystem(self.threshingParticleSystems);
end;

function EasyFlow:readStream(streamId, timestamp, connection)
    self.lastCutterAreaBiggerZero = streamReadBool(streamId);
end;

function EasyFlow:writeStream(streamId, connection, dirtyMask)
    streamWriteBool(streamId, self.lastCutterAreaBiggerZero);
end;

function EasyFlow:readUpdateStream(streamId, timestamp, connection)
    if connection:getIsServer() then
        self.lastCutterAreaBiggerZero = streamReadBool(streamId);
    end;
end;

function EasyFlow:writeUpdateStream(streamId, connection, dirtyMask)
    if not connection:getIsServer() then
        streamWriteBool(streamId, self.lastCutterAreaBiggerZero);
    end;
end;

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

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

function EasyFlow:update(dt)

    Utils.setEmittingState(self.threshingParticleSystems, (self.reelStarted and self.lastCutterAreaBiggerZero));

    if self.reelStarted then

        for _, rollNode in pairs(self.rollNodes) do
            rotate(rollNode.node, -dt*rollNode.speed, 0, 0);
        end;

        if self.reelNode ~= nil then
            rotate(self.reelNode, -dt*self.reelSpeed*self.reelSpeedScale, 0, 0);

            if self.sideArmMovable then
                --:TODO: move arm downwards
            end;

            --correct spikes, so that they always look down
            local atx, aty, atz = getRotation(self.reelNode);

            for i=1, self.spikesCount do
                local spike = getChildAt(self.spikesRootNode, i-1);
                --local tx, ty, tz = getRotation(spike);
                setRotation(spike, -atx, aty, atz);
            end;
        end;

    end;
end;

function EasyFlow.updateAIMovement(self, dt)

    if not self:getIsAIThreshingAllowed() then
        self:stopAIThreshing();
        return;
    end;

    if not self.isControlled then
        if g_currentMission.environment.needsLights then
            self:setLightsVisibility(true);
        else
            self:setLightsVisibility(false);
        end;
    end;

    local allowedToDrive = true;
    if self.grainTankCapacity == 0 then
        if not self.pipeStateIsUnloading[self.currentPipeState] then
            allowedToDrive = false;
        end
        if not self.isPipeUnloading and (self.lastArea > 0 or self.lastLostGrainTankFillLevel > 0) then
            -- there is some fruit to unload, but there is no trailer. Stop and wait for a trailer
            self.waitingForTrailerToUnload = true;
        end;
    else
        if self.grainTankFillLevel >= self.grainTankCapacity then
            allowedToDrive = false;
        end
    end

    if self.waitingForTrailerToUnload then
        if self.lastValidGrainTankFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
            local trailer = self:findTrailerToUnload(self.lastValidGrainTankFruitType);
            if trailer ~= nil then
                -- there is a trailer to unload. Continue working
                self.waitingForTrailerToUnload = false;
            end;
        else
            -- we did not cut anything yet. We shouldn't have ended in this state. Just continue working
            self.waitingForTrailerToUnload = false;
        end;
    end;

    if (self.grainTankFillLevel >= self.grainTankCapacity and self.grainTankCapacity > 0) or self.waitingForTrailerToUnload or self.waitingForDischarge then
        allowedToDrive = false;
    end;
    for _,v in pairs(self.numCollidingVehicles) do
        if v > 0 then
            allowedToDrive = false;
            break;
        end;
    end;
    if self.turnStage > 0 then
        if self.waitForTurnTime > self.time or (self.pipeIsUnloading and self.turnStage < 3) then
            allowedToDrive = false;
        end;
    end;
    if not self:getIsThreshingAllowed(true) then
        allowedToDrive = false;
        self:setIsThreshing(false);
        self.waitingForWeather = true;
    else
        if self.waitingForWeather then
            if self.turnStage == 0 then
                self.driveBackTime = self.time + self.driveBackTimeout;
            end;
            self:startThreshing();
            self.waitingForWeather = false;
        end;
    end;
    if not allowedToDrive then
        --local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
        --local lx, lz = 0, 1; --AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ);
        --AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, false, moveForwards, lx, lz)
        AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, false, moveForwards, nil, nil)
        return;
    end;

    local speedLevel = 2;

    local leftMarker = self.aiLeftMarker;
    local rightMarker = self.aiRightMarker;
    local hasFruitPreparer = false;
    local fruitType = self.lastValidInputFruitType;
    if self.fruitPreparerFruitType ~= nil and self.fruitPreparerFruitType == fruitType then
        hasFruitPreparer = true;
    end
    for cutter,implement in pairs(self.attachedCutters) do
        if cutter.aiLeftMarker ~= nil and leftMarker == nil then
            leftMarker = cutter.aiLeftMarker;
        end;
        if cutter.aiRightMarker ~= nil and rightMarker == nil then
            rightMarker = cutter.aiRightMarker;
        end;
        if Cutter.getUseLowSpeedLimit(cutter) then
            speedLevel = 1;
        end;
    end;

    if leftMarker == nil or rightMarker == nil then
       self:stopAIThreshing();
        return;
    end;

    if self.driveBackTime >= self.time then
        local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
        local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ);
        AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, true, false, lx, lz, speedLevel, 1)
        return;
    end;

    local hasArea = true;
    if self.lastArea < 1 then
        local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
        local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ;
        local lInX,  lInY,  lInZ = getWorldTranslation(leftMarker);
        local rInX,  rInY,  rInZ = getWorldTranslation(rightMarker);

        local heightX = lInX + dirX * self.frontAreaSize;
        local heightZ = lInZ + dirZ * self.frontAreaSize;

        -- local area = Utils.getFruitArea(fruitType, lInX, lInZ, rInX, rInZ, heightX, heightZ, hasFruitPreparer);
        local area = EasyFlow.getFruitWindrowArea(fruitType, lInX, lInZ, rInX, rInZ, heightX, heightZ);
        if area < 1 then
            hasArea = false;
        end;
    end;
    if hasArea then
		self.turnTimer = self.turnTimeout;
    else
        self.turnTimer = self.turnTimer - dt;
    end;


    local newTargetX, newTargetY, newTargetZ;

    local moveForwards = true;
    local updateWheels = true;
	self.moveForwards = false

    if self.turnTimer < 0 or (self.turnStage > 0 or self.turnStage < 0) then
        if self.turnStage > 0 then
            local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
            local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ;
            local myDirX, myDirY, myDirZ = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1);

            newTargetX = self.aiThreshingTargetX;
            newTargetY = y;
            newTargetZ = self.aiThreshingTargetZ;
			
            if self.turnStage == 1 then
                self.turnStageTimer = self.turnStageTimer - dt;
                if self.lastSpeed < self.aiRescueSpeedThreshold then
                    self.aiRescueTimer = self.aiRescueTimer - dt;
                else
                    self.aiRescueTimer = self.aiRescueTimeout;
                end;
                if myDirX*dirX + myDirZ*dirZ > self.turnStage1AngleCosThreshold or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then
				self.turnStage = 2;
                    moveForwards = false;
                    if self.turnStageTimer < 0 or self.aiRescueTimer < 0 then

                        self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX;
                        self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ;

                        newTargetX = self.aiThreshingTargetBeforeTurnX;
                        newTargetZ = self.aiThreshingTargetBeforeTurnZ;

                        moveForwards = false;
                        self.turnStage = 4;
                        self.turnStageTimer = self.turnStage4Timeout;
                    else
                        self.turnStageTimer = self.turnStage2Timeout;
                    end;
                    self.aiRescueTimer = self.aiRescueTimeout;
                end;
            elseif self.turnStage == 2 then
                self.turnStageTimer = self.turnStageTimer - dt;
                if self.lastSpeed < self.aiRescueSpeedThreshold then
                    self.aiRescueTimer = self.aiRescueTimer - dt;
                else
                    self.aiRescueTimer = self.aiRescueTimeout;
                end;
                if (myDirX*dirX + myDirZ*dirZ > self.turnStage2AngleCosThreshold or self.turnStageTimer < 0 or self.aiRescueTimer < 0) or self.skipTurnStage2 == true then
                    AICombine.switchToTurnStage3(self);
                else
                    moveForwards = false;
                end;
            elseif self.turnStage == 3 then
                --[[if Utils.vector2Length(x-newTargetX, z-newTargetZ) < self.turnEndDistance then
                    self.turnTimer = self.turnTimeoutLong;
                    self.turnStage = 0;
                    --print("turning done");
                end;]]
                if self.lastSpeed < self.aiRescueSpeedThreshold then
                    self.aiRescueTimer = self.aiRescueTimer - dt;
                else
                    self.aiRescueTimer = self.aiRescueTimeout;
                end;
                local dx, dz = x-newTargetX, z-newTargetZ;
                local dot = dx*dirX + dz*dirZ;
                if -dot < self.turnEndDistance then
                    self.turnTimer = self.turnTimeoutLong;
                    self.turnStage = 0;
                elseif self.aiRescueTimer < 0 then
                    self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX;
                    self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ;

                    newTargetX = self.aiThreshingTargetBeforeTurnX;
                    newTargetZ = self.aiThreshingTargetBeforeTurnZ;

                    moveForwards = false;
                    self.turnStage = 4;
                    self.turnStageTimer = self.turnStage4Timeout;
                end;
            elseif self.turnStage == 4 then
			
                self.turnStageTimer = self.turnStageTimer - dt;
                if self.lastSpeed < self.aiRescueSpeedThreshold then
                    self.aiRescueTimer = self.aiRescueTimer - dt;
                else
                    self.aiRescueTimer = self.aiRescueTimeout;
                end;
                if self.aiRescueTimer < 0 then
                    self.aiRescueTimer = self.aiRescueTimeout;
                    local x,y,z = localDirectionToWorld(self.aiRescueNode, 0, 0, -1);
                    local scale = self.aiRescueForce/Utils.vector2Length(x,z);
                    addForce(self.aiRescueNode, x*scale, 0, z*scale, 0, 0, 0, true);
                end;
                if self.turnStageTimer < 0 then
                    self.aiRescueTimer = self.aiRescueTimeout;
                    self.turnStageTimer = self.turnStage1Timeout;
                    self.turnStage = 1;

                    newTargetX = self.aiThreshingTargetBeforeSaveX;
                    newTargetZ = self.aiThreshingTargetBeforeSaveZ;
                else
                    local dirX, dirZ = -dirX, -dirZ;
                    -- just drive along direction
                    local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ;
                    local dx, dz = x-targetX, z-targetZ;
                    local dot = dx*dirX + dz*dirZ;

                    local projTargetX = targetX +dirX*dot;
                    local projTargetZ = targetZ +dirZ*dot;

                    newTargetX = projTargetX-dirX*self.lookAheadDistance;
                    newTargetZ = projTargetZ-dirZ*self.lookAheadDistance;
                    moveForwards = false;
                end;
            end;
        elseif fruitType == FruitUtil.FRUITTYPE_UNKNOWN then
			self:stopAIThreshing();
            return;
        else
            -- turn
            local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
            local lx,ly,lz = nil, nil, nil
            local rx,ry,rz = nil, nil, nil
			
            local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ;
            local sideX, sideZ = -dirZ, dirX;
            local lInX,  lInY,  lInZ = getWorldTranslation(leftMarker);
            local rInX,  rInY,  rInZ = getWorldTranslation(rightMarker);
            local threshWidth = Utils.vector2Length(lInX-rInX, lInZ-rInZ);
			local leftFruit = 0
			local rightFruit = 0
            local turnLeft = true;
			local mythreshWidth = 0.51
			local mySideWatchDirOffset = -1.01
			local mySideWatchDirSize = 2.01
			local doBreak = false

			for transXDouble=0,30 do
				local transX = transXDouble * 0.5
				for transZ=-6,14 do
					setTranslation(self.aiLeftCheck, transX*-1,-0.5,transZ)
					setTranslation(self.aiRightCheck, transX*1,-0.5,transZ)
					
					lx,ly,lz = getWorldTranslation(self.aiLeftCheck);
					rx,ry,rz = getWorldTranslation(self.aiRightCheck);
				
					lWidthX = lx - sideX*0.5*mythreshWidth + dirX * mySideWatchDirOffset
					lWidthZ = lz - sideZ*0.5*mythreshWidth + dirZ * mySideWatchDirOffset
					lStartX = lWidthX - sideX*0.7*mythreshWidth
					lStartZ = lWidthZ - sideZ*0.7*mythreshWidth
					lHeightX = lStartX + dirX* mySideWatchDirSize
					lHeightZ = lStartZ + dirZ* mySideWatchDirSize
					
					rWidthX = rx + sideX*0.5*mythreshWidth + dirX * mySideWatchDirOffset
					rWidthZ = rz + sideZ*0.5*mythreshWidth + dirZ * mySideWatchDirOffset
					rStartX = rWidthX + sideX*0.7*mythreshWidth
					rStartZ = rWidthZ + sideZ*0.7*mythreshWidth
					rHeightX = rStartX + dirX* mySideWatchDirSize
					rHeightZ = rStartZ + dirZ* mySideWatchDirSize
					
					testLeftFruit = EasyFlow.getFruitWindrowArea(fruitType, lStartX, lStartZ, lWidthX, lWidthZ, lHeightX, lHeightZ);
					testRightFruit = EasyFlow.getFruitWindrowArea(fruitType, rStartX, rStartZ, rWidthX, rWidthZ, rHeightX, rHeightZ);
					
					if (testLeftFruit > 5 and testLeftFruit > leftFruit) or (testRightFruit > 5 and testRightFruit > rightFruit) then
						if transZ < 6 then
							self.moveForwards = true
							if transX < 1 then 
								self.turnTimer = self.turnTimeout;
							end
						end
						if transX > 5 then
							self.skipTurnStage2 = true
						else
							self.skipTurnStage2 = false
						end
						
						leftFruit = testLeftFruit
						rightFruit = testRightFruit
						
						if transX < 1 then
							setTranslation(self.aiLeftCheck, (transX+threshWidth)*-1,-0.5,transZ)
							setTranslation(self.aiRightCheck, (transX+threshWidth)*1,-0.5,transZ)
							lx,ly,lz = getWorldTranslation(self.aiLeftCheck);
							rx,ry,rz = getWorldTranslation(self.aiRightCheck);
						else
							setTranslation(self.aiLeftCheck, (transX+threshWidth*0.6)*-1,-0.5,transZ)
							setTranslation(self.aiRightCheck, (transX+threshWidth*0.6)*1,-0.5,transZ)
							lx,ly,lz = getWorldTranslation(self.aiLeftCheck);
							rx,ry,rz = getWorldTranslation(self.aiRightCheck);
						end
						doBreak = true
						break
					end
				end
				if doBreak == true then
					break
				end
			end
			
			if self.moveForwards == true then
				if self.turnTimer > 0 then
					self.turnStage = 0
					self:setAIImplementsMoveDown(true);
				else
					self.turnStage = -1
					self:setAIImplementsMoveDown(false);
				end
				-- do not turn jet
				local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
				local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ;
				-- just drive along direction
				local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ;
				local dx, dz = x-targetX, z-targetZ;
				local dot = dx*dirX + dz*dirZ;

				local projTargetX = targetX +dirX*dot;
				local projTargetZ = targetZ +dirZ*dot;

				--print("old target: "..targetX.." ".. targetZ .. " distOnDir " .. dot.." proj: "..projTargetX.." "..projTargetZ);

				newTargetX = projTargetX+self.aiThreshingDirectionX*self.lookAheadDistance;
				newTargetY = y;
				newTargetZ = projTargetZ+self.aiThreshingDirectionZ*self.lookAheadDistance;
				--print(distOnDir.." target: "..newTargetX.." ".. newTargetZ);
			else
				if leftFruit > 0 or rightFruit > 0 then
					if leftFruit > rightFruit then
						turnLeft = true;
					else
						turnLeft = false;
					end
				else	
					self:stopAIThreshing();
					return;
				end;
				-- turn to where more fruit is to cut
				local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ;
				--local dx, dz = x-targetX, z-targetZ;
				--local dot = dx*dirX + dz*dirZ;
				--local x, z = targetX + dirX*dot, targetZ + dirZ*dot;
				--threshWidth = threshWidth*self.aiTurnThreshWidthScale;
				local markerSideOffset;
				if turnLeft then
					markerSideOffset, _, _ = worldToLocal(self.aiTreshingDirectionNode, lx,ly,lz);
				else
					markerSideOffset, _, _ = worldToLocal(self.aiTreshingDirectionNode, rx,ry,rz);
				end
				
				local myAiTurnThreshWidthMaxDifference = 0.6
				local areaOverlap = math.min(threshWidth*(1-self.aiTurnThreshWidthScale), myAiTurnThreshWidthMaxDifference);
				if markerSideOffset > 0 then
					markerSideOffset = math.max(markerSideOffset - areaOverlap, 0.01);
				else
					markerSideOffset = math.min(markerSideOffset + areaOverlap, -0.01);
				end

				local x,z = Utils.projectOnLine(x, z, targetX, targetZ, dirX, dirZ)
				newTargetX = x-sideX*markerSideOffset;
				newTargetY = y;
				newTargetZ = z-sideZ*markerSideOffset;

				self.aiThreshingDirectionX = -dirX;
				self.aiThreshingDirectionZ = -dirZ;
				self.turnStage = 1;
				self.aiRescueTimer = self.aiRescueTimeout;
				self.turnStageTimer = self.turnStage1Timeout;

				self.aiThreshingTargetBeforeTurnX = self.aiThreshingTargetX;
				self.aiThreshingTargetBeforeTurnZ = self.aiThreshingTargetZ;

				self.waitForTurnTime = self.time + self.waitForTurnTimeout;
				self:setAIImplementsMoveDown(false);
				-- do not thresh while turning
				self.allowsThreshing = false;
				updateWheels = false
				if turnLeft then
					--print("turning left ", threshWidth);
				else
					--print("turning right ", threshWidth);
				end;
			end
        end;
    else
        local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
        local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ;
        -- just drive along direction
        local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ;
        local dx, dz = x-targetX, z-targetZ;
        local dot = dx*dirX + dz*dirZ;

        local projTargetX = targetX +dirX*dot;
        local projTargetZ = targetZ +dirZ*dot;

        --print("old target: "..targetX.." ".. targetZ .. " distOnDir " .. dot.." proj: "..projTargetX.." "..projTargetZ);

        newTargetX = projTargetX+self.aiThreshingDirectionX*self.lookAheadDistance;
        newTargetY = y;
        newTargetZ = projTargetZ+self.aiThreshingDirectionZ*self.lookAheadDistance;
        --print(distOnDir.." target: "..newTargetX.." ".. newTargetZ);
    end;

    if updateWheels then
        local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, newTargetX, newTargetY, newTargetZ);

        if self.turnStage == 2 and math.abs(lx) < 0.1 then
            AICombine.switchToTurnStage3(self);
            moveForwards = true;
        end;

        AIVehicleUtil.driveInDirection(self, dt, 25, 0.5, 0.5, 20, true, moveForwards, lx, lz, speedLevel, 0.9);

        --local maxAngle = 0.785398163; --45?;
        local maxlx = 0.7071067; --math.sin(maxAngle);
        local colDirX = lx;
        local colDirZ = lz;

        if colDirX > maxlx then
            colDirX = maxlx;
            colDirZ = 0.7071067; --math.cos(maxAngle);
        elseif colDirX < -maxlx then
            colDirX = -maxlx;
            colDirZ = 0.7071067; --math.cos(maxAngle);
        end;

        for triggerId,_ in pairs(self.numCollidingVehicles) do
            AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, triggerId, colDirX, colDirZ);
        end;
    end;

    self.aiThreshingTargetX = newTargetX;
    self.aiThreshingTargetZ = newTargetZ;
end;

function EasyFlow.getFruitWindrowArea(fruitId, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ)
    local ids = g_currentMission.fruits[fruitId];
    if ids == nil or ids.windrowId == 0 then
        return 0, 0;
    end
    local id = ids.windrowId;
    local x,z, widthX,widthZ, heightX,heightZ = Utils.getXZWidthAndHeight(id, startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ);
    local ret, _, total = getDensityParallelogram(id, x, z, widthX, widthZ, heightX, heightZ, 0, g_currentMission.numWindrowChannels);
    return ret, total;
end

function EasyFlow:updateTick(dt)
    if self.isServer then
        self.lastCutterArea = 0;
        self.lastCutterAreaBiggerZero = false;
		local combineAI = self:getCombine();
		if combineAI ~= nil then
			if combineAI.aiLeftCheck == nil or combineAI.aiRightCheck == nil then
				combineAI.aiLeftCheck = self.aiLeftCheck
				combineAI.aiRightCheck = self.aiRightCheck
			end
			if combineAI.isAIThreshing then
				if combineAI.isBroken or combineAI.aiLeftCheck == nil or combineAI.aiRightCheck == nil then
					combineAI:stopAIThreshing();
				end;
				combineAI.dtSum = - 1000
				self.dtSum = self.dtSum + dt;
				if self.dtSum > 50 then
					EasyFlow.updateAIMovement(combineAI, self.dtSum);
					self.dtSum = 0;
				end;
			end
		end
    end;
    if self.reelStarted and self.movingDirection == self.cutterMovingDirection and (self.cutterAllowCuttingWhileRaised or self:isLowered(true)) then
        local speedLimit = self.speedLimit;
        if EasyFlow.getUseLowSpeedLimit(self) then
            speedLimit = self.speedLimitLow;
        end

        if self:doCheckSpeedLimit() and self.lastSpeed*3600 > speedLimit then
            self.speedViolationTimer = self.speedViolationTimer - dt;
        else
            self.speedViolationTimer = self.speedViolationMaxTime;
        end
        local oldInputFruitType = self.currentInputFruitType;
        if self.isServer then

            if self.speedViolationTimer > 0 then
                local combine = self:getCombine();
                if combine ~= nil and combine:getIsThreshingAllowed(false) and combine.allowsThreshing then

                    local cuttingAreasSend = {};
                    for _,area in pairs(self.cuttingAreas) do
                        if self:getIsAreaActive(area) then
                            local x,_,z = getWorldTranslation(area.start);
                            local x1,_,z1 = getWorldTranslation(area.width);
                            local x2,_,z2 = getWorldTranslation(area.height);
                            table.insert(cuttingAreasSend, {x,z,x1,z1,x2,z2});
                        end;
                    end;
                    if (table.getn(cuttingAreasSend) > 0) then
                        for fruitType,_ in pairs(self.fruitTypes) do
                            local outputFruitType = fruitType;
                            if self.convertedFruits[fruitType] ~= nil then
                                outputFruitType = self.convertedFruits[fruitType];
                            end;

                            if combine:getIsCutterFruitTypeAllowed(outputFruitType) then

                                local lastCutterArea, realArea = EasyFlowAreaEvent.runLocally(cuttingAreasSend, fruitType);
                                if lastCutterArea > 0 then
                                    self.currentInputFruitType = fruitType;

                                    self.lastCutterArea = lastCutterArea;
                                    self.lastCutterAreaBiggerZero = (self.lastCutterArea > 0);

                                    combine:addCutterArea(self, lastCutterArea, realArea, fruitType, outputFruitType);

                                    g_server:broadcastEvent(EasyFlowAreaEvent:new(cuttingAreasSend, fruitType));

                                    if self.lastCutterAreaBiggerZero ~= self.lastCutterAreaBiggerZeroSent then
                                        self:raiseDirtyFlags(self.cutterGroundFlag);
                                        self.lastCutterAreaBiggerZeroSent = self.lastCutterAreaBiggerZero;
                                    end;
                                    local pixelToSqm = g_currentMission:getFruitPixelsToSqm(); -- 4096px are mapped to 2048m
                                    local sqm = realArea*pixelToSqm;
                                    local ha = sqm/10000;

                                    g_currentMission.missionStats.hectaresThreshedTotal = g_currentMission.missionStats.hectaresThreshedTotal + ha;
                                    g_currentMission.missionStats.hectaresThreshedSession = g_currentMission.missionStats.hectaresThreshedSession + ha;

                                    -- Stop the loop over all fruit types
                                    break;
                                end
                            end
                        end
                    end
                end
            end
        else
            local combine = self:getCombine();
            if combine ~= nil and combine.lastValidInputFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
                self.currentInputFruitType = combine.lastValidInputFruitType;
            end
        end
        if self.currentInputFruitType ~= oldInputFruitType then
            EasyFlow.updateExtraObjects(self);
        end

        if self:isLowered(true) then
            local foldAnimTime = self.foldAnimTime;
            for k,v in pairs(self.cutterSpeedRotatingParts) do
                if foldAnimTime == nil or (foldAnimTime <= v.foldMaxLimit and foldAnimTime >= v.foldMinLimit) then
                    rotate(v.node, v.rotationSpeedScale * self.lastSpeedReal * self.movingDirection * dt, 0, 0);
                end
            end
        end
    else
        self.speedViolationTimer = self.speedViolationMaxTime;
    end
end

function EasyFlow:draw()

end;

function EasyFlow:onDetach()
    if self.deactivateOnDetach then
        EasyFlow.onDeactivate(self);
    end;
end;

function EasyFlow:onLeave()
    if self.deactivateOnLeave then
        EasyFlow.onDeactivate(self);
    end;
end;

function EasyFlow:onDeactivate()
    self:onStopReel();
    Utils.setEmittingState(self.threshingParticleSystems, false);
    self.speedViolationTimer = self.speedViolationMaxTime;
end;

function EasyFlow:setReelSpeedSpace(speedScale)
    self.reelSpeedScale = speedScale;
end;

function EasyFlow:onStartReel()
    self.reelStarted = true;

    for _, part in pairs(self.cutterThreshingUVScrollParts) do
        setShaderParameter(part.node, "uvScrollSpeed", part.speed[1], part.speed[2], 0, 0, false);
    end;

    if self.cutterStartAnimation ~= nil and self.playAnimation ~= nil then
        self:playAnimation(self.cutterStartAnimation, self.cutterStartAnimationSpeedScale, nil, true);
    end
end;

function EasyFlow:onStopReel()
    self.reelStarted = false;
    Utils.setEmittingState(self.threshingParticleSystems, false);
    self.speedViolationTimer = self.speedViolationMaxTime;

    for _, part in pairs(self.cutterThreshingUVScrollParts) do
        setShaderParameter(part.node, "uvScrollSpeed", 0, 0, 0, 0, false);
    end;

    if self.cutterStartAnimation ~= nil and self.playAnimation ~= nil then
        self:playAnimation(self.cutterStartAnimation, -self.cutterStartAnimationSpeedScale, nil, true);
    end
end;

function EasyFlow:getDirectionSnapAngle(superFunc)
    local snapAngle = 0;
    for fruitType,_ in pairs(self.fruitTypes) do
        local desc = FruitUtil.fruitIndexToDesc[fruitType];
        if desc ~= nil then
            snapAngle = math.max(snapAngle, desc.directionSnapAngle);
        end
    end
    return math.max(snapAngle, superFunc(self));
end

function EasyFlow:getCombine()
    if self.getIsThreshingAllowed ~= nil and self.getIsCutterFruitTypeAllowed ~= nil and self.grainTankFillLevel ~= nil then
        return self;
    else
        local c = self.attacherVehicle;
        if c ~= nil and c.getIsThreshingAllowed ~= nil and c.getIsCutterFruitTypeAllowed ~= nil and c.grainTankFillLevel ~= nil then
            return c;
        end
    end;
    return nil;
end

--[[function EasyFlow:resetFruitType()
    self.currentFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.currentInputFruitType = FruitUtil.FRUITTYPE_UNKNOWN;
    self.lastCutterArea = 0;
end;]]

--[[function EasyFlow:setFruitType(fruitType)
    if self.currentInputFruitType ~= fruitType then
        self.currentInputFruitType = fruitType;
        self.currentFruitType = fruitType;
        if self.convertedFruits[fruitType] ~= nil then
            self.currentFruitType = self.convertedFruits[fruitType];
        end;
        self.lastCutterArea = 0;

        EasyFlow.updateExtraObjects(self)
    end;
end;]]

function EasyFlow.getUseLowSpeedLimit(self)
    local combineSize = self.combineSize;
    if combineSize == nil then
        if self.attacherVehicle ~= nil then
            combineSize = self.attacherVehicle.combineSize;
        end
    end

    if self.forceLowSpeed or (combineSize ~= nil and self.preferedCombineSize > combineSize) then
        return true;
    end
    return false;
end;

function EasyFlow.updateExtraObjects(self)
    if self.currentExtraObject ~= nil then
        setVisibility(self.currentExtraObject, false);
        self.currentExtraObject = nil;
    end;
    if self.currentInputFruitType ~= FruitUtil.FRUITTYPE_UNKNOWN then
        local name = FruitUtil.fruitIndexToDesc[self.currentInputFruitType].name;
        local extraObject = self.fruitExtraObjects[name];
        if extraObject ~= nil then
            setVisibility(extraObject, true);
            self.currentExtraObject = extraObject;
        end;
    end;
end;
