--
-- AILexion600Combine
-- Specialization for ai combines
--
-- @author  Stefan Geiger
-- @date  10/01/09
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

AILexion600Combine = {};

function AILexion600Combine.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(Hirable, specializations) and SpecializationUtil.hasSpecialization(Lexion600Combine2, specializations);
end;

function AILexion600Combine:load(xmlFile)

    self.startAIThreshing = SpecializationUtil.callSpecializationsFunction("startAIThreshing");
    self.stopAIThreshing = SpecializationUtil.callSpecializationsFunction("stopAIThreshing");
    self.onTrafficCollisionTrigger = AILexion600Combine.onTrafficCollisionTrigger;
    self.onCutterTrafficCollisionTrigger = AILexion600Combine.onCutterTrafficCollisionTrigger;
    self.onTrailerTrigger = AILexion600Combine.onTrailerTrigger;

    self.isAIThreshing = false;
    self.aiTreshingDirectionNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTreshingDirectionNode#index"));
    if self.aiTreshingDirectionNode == nil then
        self.aiTreshingDirectionNode = self.components[1].node;
    end;

    self.lookAheadDistance = 10;
    self.turnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnTimeout"), 200);
    self.turnTimeoutLong = self.turnTimeout*10;
    self.turnTimer = self.turnTimeout;
    self.turnEndDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnEndDistance"), 4);

    self.waitForTurnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForTurnTime"), 1500);
    self.waitForTurnTime = 0;


    self.sideWatchDirOffset = -8;
    self.sideWatchDirSize = 8;

    self.frontAreaSize = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.frontAreaSize#value"), 1.5);

    self.waitingForDischarge = false;
    self.waitForDischargeTime = 0;
    self.waitForDischargeTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForDischargeTime"), 8000);

    self.turnStage = 0;



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

    self.aiTrailerTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTrailerTrigger#index"));
    self.aiTurnThreshWidthScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTurnThreshWidthScale#value"), 0.9);

    self.isTrailerInRange = false;

    self.trafficCollisionIgnoreList = {};
    for k,v in pairs(self.components) do
        self.trafficCollisionIgnoreList[v.node] = true;
    end;
    self.numCollidingVehicles = 0;
    self.numCutterCollidingVehicles = {};

    self.driveBackTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.driveBackTimeout"), 800);
    self.driveBackTime = 0;
    self.driveBackAfterDischarge = false;

    self.aiLastGrainTankFruitType = self.currentGrainTankFruitType;

    self.dtSum = 0;

    local aiMotorSound  = getXMLString(xmlFile, "vehicle.aiMotorSound#file");
    if aiMotorSound  ~= nil and aiMotorSound  ~= "" then
        aiMotorSound  = Utils.getFilename(aiMotorSound, self.baseDirectory);
        self.aiMotorSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#pitchOffset"), 0);
        self.aiMotorSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#radius"), 50);
        self.aiMotorSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#innerRadius"), 10);
        self.aiMotorSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#volume"), 1);
        self.aiMotorSound = createAudioSource("aiMotorSound", aiMotorSound, self.aiMotorSoundRadius, self.aiMotorSoundInnerRadius, self.aiMotorSoundVolume, 0);
        link(self.components[1].node, self.aiMotorSound);
        setVisibility(self.aiMotorSound, false);
    end;

    local aiThreshingSound  = getXMLString(xmlFile, "vehicle.aiTreshingSound#file");
    if aiThreshingSound  ~= nil and aiThreshingSound  ~= "" then
        aiThreshingSound  = Utils.getFilename(aiThreshingSound, self.baseDirectory);
        self.aiThreshingSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#pitchOffset"), 0);
        self.aiThreshingSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#radius"), 50);
        self.aiThreshingSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#innerRadius"), 10);
        self.aiThreshingSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#volume"), 1);
        self.aiThreshingSound = createAudioSource("aiThreshingSound", aiThreshingSound, self.aiThreshingSoundRadius, self.aiThreshingSoundInnerRadius, self.aiThreshingSoundVolume, 0);
        link(self.components[1].node, self.aiThreshingSound);
        setVisibility(self.aiThreshingSound, false);
    end;

    self.turnStage1Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnForwardTimeout"), 20000);
    self.turnStage2Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnBackwardTimeout"), 20000);
    self.turnStage4Timeout = 3000;

    self.waitingForWeather = false;

    self.aiRescueTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#timeout"), 1500);
    self.aiRescueTimer = self.aiRescueTimeout;
    self.aiRescueForce = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#force"), 60);
    self.aiRescueSpeedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#speedThreshold"), 0.0001);
    self.aiRescueNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRescue#index"));
    if self.aiRescueNode == nil then
        self.aiRescueNode = self.components[1].node;
    end;

    --self.debugDirection = loadI3DFile("data/debugDirection.i3d");
    --link(self.aiTreshingDirectionNode, self.debugDirection);

    --self.debugPosition = loadI3DFile("data/debugPosition.i3d");
    --link(self.aiTreshingDirectionNode, self.debugPosition);

end;

function AILexion600Combine:delete()
    --self:stopAIThreshing();
    if self.aiTrailerTrigger ~= nil then
        removeTrigger(self.aiTrailerTrigger);
    end;

    if self.aiTrafficCollisionTrigger ~= nil then
        removeTrigger(self.aiTrafficCollisionTrigger);
    end;

    for cutter,implement in pairs(self.attachedCutters) do
        AILexion600Combine.removeCutterTrigger(self, cutter);
    end;
end;

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

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

function AILexion600Combine:update(dt)
    if self:getIsActiveForInput() then
        if not g_currentMission.disableCombineAI and g_currentMission.allowSteerableMoving then
			if self.treshingMaschineActive or self.waitingForWeather then
				if InputBinding.hasEvent(InputBinding.TOGGLE_AI) then
					if self.isAIThreshing then
						self:stopAIThreshing();
					else
						self:startAIThreshing();
					end;
				end;
			end;
        end;
    end;

    if self.isAIThreshing then
        if self.isBroken then
            self:stopAIThreshing();
        end;
		--if not self.treshingMaschineActive then
		--	self:stopAIThreshing();
		--end;
		if self.transportModeActive then
			self:stopAIThreshing();
		end;

        self.dtSum = self.dtSum + dt;
        if self.dtSum > 20 then
            AILexion600Combine.updateAIMovement(self, self.dtSum);
            self.dtSum = 0;
        end;

        if self.grainTankFillLevel > 0 and (self.grainTankFillLevel >= self.grainTankCapacity*0.8 or self.isTrailerInRange) then
            self:openPipe();
            if self.isTrailerInRange then
                self.waitForDischargeTime = self.time + self.waitForDischargeTimeout;
            end;
            if self.grainTankFillLevel >= self.grainTankCapacity then
                self.driveBackAfterDischarge = true;
                self.waitingForDischarge = true;
                self.waitForDischargeTime = self.time + self.waitForDischargeTimeout;
            end;
        else
            -- no trailer in range and not full
            if (self.waitingForDischarge and self.grainTankFillLevel <= 0) or self.waitForDischargeTime <= self.time then
                self.waitingForDischarge = false;
                if self.driveBackAfterDischarge then
                    self.driveBackTime = self.time + self.driveBackTimeout;
                    self.driveBackAfterDischarge = false;
                end;
                self:closePipe();
                if Cutter.allowThreshing(true) then
					self.cutterActive = true;
                end;
            end;
        end;
        self.isTrailerInRange = false;
    else
        self.dtSum = 0;
    end;
end;

function AILexion600Combine:draw()

    if not g_currentMission.disableCombineAI then
        if self.numAttachedCutters > 0 then
			if self.treshingMaschineActive then
				if self.isAIThreshing then
					g_currentMission:addHelpButtonText(g_i18n:getText("DismissEmployee"), InputBinding.TOGGLE_AI);
				else
					g_currentMission:addHelpButtonText(g_i18n:getText("HireEmployee"), InputBinding.TOGGLE_AI);
				end;
			elseif self.waitingForWeather then
				if self.isAIThreshing then
					g_currentMission:addHelpButtonText(g_i18n:getText("DismissEmployee"), InputBinding.TOGGLE_AI);
				end;
			end;
        end;
    end;
end;

function AILexion600Combine:startAIThreshing()
    self:hire();
    if not self.isAIThreshing then
		if self.transportModeActive then
			self.transportModeActive = false;
		end;
        self.isAIThreshing = true;
        self.turnTimer = self.turnTimeoutLong;
        self.turnStage = 0;

        local x,y,z = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1);
        local length = Utils.vector2Length(x,z);
        self.aiThreshingDirectionX = x/length;
        self.aiThreshingDirectionZ = z/length;

        local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode);
        self.aiThreshingTargetX = x;
        self.aiThreshingTargetZ = z;

		self.speedDisplayScale = 0.9;
		self.gasMode = 3;
        self.waitingForDischarge = false;

        if not self.isThreshing then
			self:startThreshing();
			self.cutterActive = true;
		else
			self.cutterActive = true;
		end;
		

        if self.aiTrailerTrigger ~= nil then
            addTrigger(self.aiTrailerTrigger, "onTrailerTrigger", self);
        end;

        self.numCollidingVehicles = 0;
        if self.aiTrafficCollisionTrigger ~= nil then
            addTrigger(self.aiTrafficCollisionTrigger, "onTrafficCollisionTrigger", self);
        end;
        for cutter,implement in pairs(self.attachedCutters) do
            AILexion600Combine.addCutterTrigger(self, cutter);
        end;
        self.isTrailerInRange = false;

        self.checkSpeedLimit = false;

        setVisibility(self.aiMotorSound, true);
        setVisibility(self.aiThreshingSound, true);

        self.waitingForWeather = false;

    end;
end;

function AILexion600Combine:stopAIThreshing()
    self:dismiss();
    if self.isAIThreshing then
        --restore allowsThreshing flag
        self.allowsThreshing = true;
        self.speedDisplayScale = 0.5;
        if self.isThreshing then
			self.cutterActive = false;
        end;

        self.motor:setSpeedLevel(0, false);
        self.motor.maxRpmOverride = nil;
		self.gasMode = 1;

        WheelsUtil.updateWheelsPhysics(self, 0, self.lastSpeed, 0, false, self.requiredDriveMode);

        if self.aiTrailerTrigger ~= nil then
            removeTrigger(self.aiTrailerTrigger);
        end;

        if self.aiTrafficCollisionTrigger ~= nil then
            removeTrigger(self.aiTrafficCollisionTrigger);
        end;

        for cutter,implement in pairs(self.attachedCutters) do
            AILexion600Combine.removeCutterTrigger(self, cutter);
        end;

        self.isAIThreshing = false;

        if not self:getIsActive() then
            Lexion600Combine.onDeactivate(self);
        end;

        self.checkSpeedLimit = true;

        setVisibility(self.aiMotorSound, false);
        setVisibility(self.aiThreshingSound, false);

        self.waitingForWeather = false;

    end;
end;

function AILexion600Combine.updateAIMovement(self, dt)

    local allowedToDrive = true;

    if self.grainTankFillLevel >= self.grainTankCapacity or self.waitingForDischarge or self.numCollidingVehicles > 0 then
        allowedToDrive = false;
    end;
    for k,v in pairs(self.numCutterCollidingVehicles) do
        if v > 0 then
            allowedToDrive = false;
            break;
        end;
    end;
    if self.turnStage > 0 then
        if self.waitForTurnTime > self.time or self.lastUnloadingTrailer ~= nil then
            allowedToDrive = false;
        end;
    end;
    if not Cutter.allowThreshing(true) then
        allowedToDrive = false;
		self.cutterActive = false;
		self.gasMode = 1;
		if not self.cutterActive then
			self:stopThreshing();
		end;
        self.waitingForWeather = true;
    else
        if self.waitingForWeather then
            if self.turnStage == 0 then
                self.driveBackTime = self.time + self.driveBackTimeout;
            end;
			self:startThreshing();
			self.cutterActive = true;
            self.waitingForWeather = false;
			self.gasMode = 3;
        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)
        return;
    end;

    local speedLevel = 1;

    local leftMarker = nil;
    local rightMarker = nil;
    local fruitType = self.aiLastGrainTankFruitType;
    for cutter,implement in pairs(self.attachedCutters) do
        if cutter.aiLeftMarker ~= nil then
            if leftMarker == nil then
                leftMarker = cutter.aiLeftMarker;
            end;
        end;
        if cutter.aiRightMarker ~= nil then
            if rightMarker == nil then
                rightMarker = cutter.aiRightMarker;
            end;
        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);
        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;


    if self.turnTimer < 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 > 0.25 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 > 0.85 or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then
                    AILexion600Combine.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 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 turnLeft = true;

            local lWidthX = x - sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset;
            local lWidthZ = z - sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset;
            local lStartX = lWidthX - sideX*0.7*threshWidth;
            local lStartZ = lWidthZ - sideZ*0.7*threshWidth;
            local lHeightX = lStartX + dirX*self.sideWatchDirSize;
            local lHeightZ = lStartZ + dirZ*self.sideWatchDirSize;

            local rWidthX = x + sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset;
            local rWidthZ = z + sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset;
            local rStartX = rWidthX + sideX*0.7*threshWidth;
            local rStartZ = rWidthZ + sideZ*0.7*threshWidth;
            local rHeightX = rStartX + dirX*self.sideWatchDirSize;
            local rHeightZ = rStartZ + dirZ*self.sideWatchDirSize;

            local leftFruit = Utils.getFruitArea(fruitType, lStartX, lStartZ, lWidthX, lWidthZ, lHeightX, lHeightZ);
            local rightFruit = Utils.getFruitArea(fruitType, rStartX, rStartZ, rWidthX, rWidthZ, rHeightX, rHeightZ);
            -- turn to where more fruit is to cut
            if leftFruit > 0 or rightFruit > 0 then
                if leftFruit > rightFruit then
                    turnLeft = true;
                else
                    turnLeft = false;
                end
            else
                self:stopAIThreshing();
                return;
            end;
            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 x,z = Utils.projectOnLine(x, z, targetX, targetZ, dirX, dirZ)
            if turnLeft then
                newTargetX = x-sideX*threshWidth;
                newTargetY = y;
                newTargetZ = z-sideZ*threshWidth;
            else
                newTargetX = x+sideX*threshWidth;
                newTargetY = y;
                newTargetZ = z+sideZ*threshWidth;
            end;
            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;
            for cutter,implement in pairs(self.attachedCutters) do
                local jointDesc = self.attacherJoints[implement.jointDescIndex];
                jointDesc.moveDown = false;
            end;
            -- do not thresh while turning
            self.allowsThreshing = false;
            updateWheels = false;
            if turnLeft then
                --print("turning left ", threshWidth);
            else
                --print("turning right ", threshWidth);
            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
            AILexion600Combine.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;

        if self.aiTrafficCollisionTrigger ~= nil then
            AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, self.aiTrafficCollisionTrigger, colDirX, colDirZ);
        end;
        for k,v in pairs(self.numCutterCollidingVehicles) do
            AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, k, colDirX, colDirZ);
        end;
    end;

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

    if self.grainTankFillLevel > 0 then
        self.aiLastGrainTankFruitType = self.currentGrainTankFruitType;
    end;
end;

function AILexion600Combine.switchToDirection(self, myDirX, myDirZ)
    self.aiThreshingDirectionX = myDirX;
    self.aiThreshingDirectionZ = myDirZ;
    --print("switch to direction");
end;

function AILexion600Combine.addCutterTrigger(self, cutter)
    if cutter.aiTrafficCollisionTrigger ~= nil then
        addTrigger(cutter.aiTrafficCollisionTrigger, "onCutterTrafficCollisionTrigger", self);
        self.numCutterCollidingVehicles[cutter.aiTrafficCollisionTrigger] = 0;
    end;
    for k,v in pairs(cutter.components) do
        self.trafficCollisionIgnoreList[v.node] = true;
    end;
end;

function AILexion600Combine:removeCutterTrigger(cutter)
    if cutter.aiTrafficCollisionTrigger ~= nil then
        removeTrigger(cutter.aiTrafficCollisionTrigger);
        self.numCutterCollidingVehicles[cutter.aiTrafficCollisionTrigger] = nil;
    end;
    for k,v in pairs(cutter.components) do
        self.trafficCollisionIgnoreList[v.node] = nil;
    end;
end;

function AILexion600Combine:attachImplement(implement)
    local object = implement.object;
    if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then
        if self.isAIThreshing then
            AILexion600Combine.addCutterTrigger(self, object);
        end;
    end;
end;

function AILexion600Combine:detachImplement(implementIndex)
    local object = self.attachedImplements[implementIndex].object;
    if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then
        if self.isAIThreshing then
            AILexion600Combine.removeCutterTrigger(self, object);
        end;
    end;
end;

function AILexion600Combine:onTrafficCollisionTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onLeave then
        if otherId == Player.rootNode then
            if onEnter then
                self.numCollidingVehicles = self.numCollidingVehicles+1;
            elseif onLeave then
                self.numCollidingVehicles = math.max(self.numCollidingVehicles-1, 0);
            end;
        else
            local vehicle = g_currentMission.nodeToVehicle[otherId];
            if vehicle ~= nil and self.trafficCollisionIgnoreList[otherId] == nil then
                if onEnter then
                    self.numCollidingVehicles = self.numCollidingVehicles+1;
                elseif onLeave then
                    self.numCollidingVehicles = math.max(self.numCollidingVehicles-1, 0);
                end;
            end;
        end;
    end;
end;

function AILexion600Combine:onCutterTrafficCollisionTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onLeave then
        if otherId == Player.rootNode then
            if onEnter then
                self.numCutterCollidingVehicles[triggerId] = self.numCutterCollidingVehicles[triggerId]+1;
            elseif onLeave then
                self.numCutterCollidingVehicles[triggerId] = math.max(self.numCutterCollidingVehicles[triggerId]-1, 0);
            end;
        else
            local vehicle = g_currentMission.nodeToVehicle[otherId];
            if vehicle ~= nil and self.trafficCollisionIgnoreList[otherId] == nil then
                if onEnter then
                    self.numCutterCollidingVehicles[triggerId] = self.numCutterCollidingVehicles[triggerId]+1;
                elseif onLeave then
                    self.numCutterCollidingVehicles[triggerId] = math.max(self.numCutterCollidingVehicles[triggerId]-1, 0);
                end;
            end;
        end;
    end;
end;

function AILexion600Combine:onTrailerTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId)
    if onEnter or onStay then
        self.isTrailerInRange = true;
    end;
end;

function AILexion600Combine.switchToTurnStage3(self)
    self.turnStage = 3;
    for cutter,implement in pairs(self.attachedCutters) do
        local jointDesc = self.attacherJoints[implement.jointDescIndex];
        jointDesc.moveDown = true;
    end;
    self.allowsThreshing = true;
    self.aiRescueTimer = self.aiRescueTimeout;
end;
