CrabSteering = {
    STEERING_STATE_NONE = 0,
    STEERING_STATE_FIXED = 1,
    STEERING_STATE_LEFT = 2,
    STEERING_STATE_RIGHT = 3,
    STEERING_SEND_NUM_BITS = 2,
    prerequisitesPresent = function (specializations)
        return SpecializationUtil.hasSpecialization(Drivable, specializations)
    end
}
CrabSteering.load = function (self, xmlFile)
    self.setCrabSteering = SpecializationUtil.callSpecializationsFunction("setCrabSteering")
    self.updateSteeringAngle = CrabSteering.updateSteeringAngle
    self.updateArticulatedAxisRotation = CrabSteering.updateArticulatedAxisRotation
    self.crabSteering = {
        stateCurrent = CrabSteering.STEERING_STATE_NONE,
        stateTarget = CrabSteering.STEERING_STATE_NONE,
        steeringOffset = math.rad(getXMLFloat(xmlFile, "vehicle.crabSteering#steeringOffset")),
        applySteeringOffsetToFrontWheels = Utils.getNoNil(getXMLBool(xmlFile, "vehicle.crabSteering#applySteeringOffsetToFrontWheels"), false),
        steeringTarget = 0,
        distFromCompJointToCenterOfBackWheels = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.crabSteering#distFromCompJointToCenterOfBackWheels"), 1),
        frontIndices = {
            Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.crabSteering.frontWheels#indices"))
        },
        backIndices = {
            Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.crabSteering.backWheels#indices"))
        }
    }

    for i, wheel in pairs(self.wheels) do
        wheel.rotMaxBackUp = wheel.rotMax
        wheel.rotMinBackUp = wheel.rotMin
    end

    self.crabSteering.safetyBars = {}
    local i = 0

    while true do
        local animationName = getXMLString(xmlFile, string.format("vehicle.crabSteering.safetyBar(%d)#animationName", i))

        if animationName == nil then
            break
        end

        local state = CrabSteering.STEERING_STATE_NONE
        local stateName = getXMLString(xmlFile, string.format("vehicle.crabSteering.safetyBar(%d)#state", i))

        if stateName == "left" then
            state = CrabSteering.STEERING_STATE_LEFT
        elseif stateName == "right" then
            state = CrabSteering.STEERING_STATE_RIGHT
        end

        table.insert(self.crabSteering.safetyBars, {
            animationName = animationName,
            state = state
        })

        i = i + 1
    end

    self.crabSteering.animation = {}
    local name = getXMLString(xmlFile, "vehicle.crabSteering.animation#name")

    if self.animations ~= nil and self.animations[name] ~= nil then
        self.crabSteering.animation.name = name
        self.crabSteering.animation.left = getXMLFloat(xmlFile, "vehicle.crabSteering.animation#left")
        self.crabSteering.animation.middle = getXMLFloat(xmlFile, "vehicle.crabSteering.animation#middle")
        self.crabSteering.animation.right = getXMLFloat(xmlFile, "vehicle.crabSteering.animation#right")

        self.playAnimation(self, self.crabSteering.animation.name, 1, nil, true)
        self.setAnimationStopTime(self, self.crabSteering.animation.name, 1)
        AnimatedVehicle.updateAnimationByName(self, self.crabSteering.animation.name, 9999)

        local currentTime = self.getAnimationTime(self, self.crabSteering.animation.name)
        local stopAnimTime = self.crabSteering.animation.middle
        local speed = 1

        if stopAnimTime < currentTime then
            speed = -1
        end

        self.playAnimation(self, self.crabSteering.animation.name, speed, currentTime, true)
        self.setAnimationStopTime(self, self.crabSteering.animation.name, stopAnimTime)
        AnimatedVehicle.updateAnimationByName(self, self.crabSteering.animation.name, 9999)
    end

    self.crabSteering.lastRotatedTimeNotZero = 0

    return 
end
CrabSteering.delete = function (self)
    return 
end
CrabSteering.readStream = function (self, streamId, connection)
    self.crabSteering.stateTarget = streamReadUIntN(streamId, CrabSteering.STEERING_SEND_NUM_BITS)

    return 
end
CrabSteering.writeStream = function (self, streamId, connection)
    streamWriteUIntN(streamId, self.crabSteering.stateTarget, CrabSteering.STEERING_SEND_NUM_BITS)

    return 
end
CrabSteering.mouseEvent = function (self, posX, posY, isDown, isUp, button)
    return 
end
CrabSteering.keyEvent = function (self, unicode, sym, modifier, isDown)
    return 
end
CrabSteering.update = function (self, dt)
    if self.getIsActive(self) then
        if self.getIsActiveForInput(self, false) and not self.hasInputConflictWithSelection(self) then
            local state = self.crabSteering.stateTarget

            if InputBinding.hasEvent(InputBinding.SET_CRABSTEERING_LEFT) then
                state = CrabSteering.STEERING_STATE_LEFT
            elseif InputBinding.hasEvent(InputBinding.SET_CRABSTEERING_RIGHT) then
                state = CrabSteering.STEERING_STATE_RIGHT
            elseif InputBinding.hasEvent(InputBinding.SET_CRABSTEERING_NONE) then
                if state ~= CrabSteering.STEERING_STATE_LEFT and state ~= CrabSteering.STEERING_STATE_RIGHT and state == CrabSteering.STEERING_STATE_NONE then
                    state = CrabSteering.STEERING_STATE_FIXED
                else
                    state = CrabSteering.STEERING_STATE_NONE
                end
            elseif InputBinding.hasEvent(InputBinding.TOGGLE_CRABSTEERING) then
                state = state + 1

                if 3 < state then
                    state = 0
                end
            end

            if state ~= self.crabSteering.stateTarget then
                self.setCrabSteering(self, state)
            end
        end

        if self.isServer and self.crabSteering.stateTarget ~= self.crabSteering.stateCurrent then
            self.crabSteering.stateCurrent = -1
            local allSet = true

            if self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT or self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT then
                for i, index in pairs(self.crabSteering.backIndices) do
                    allSet = allSet and self.wheels[index].steeringAngle == self.crabSteering.steeringTarget
                end
            end

            if self.articulatedAxis ~= nil then
                allSet = allSet and math.abs(self.crabSteering.steeringTarget - self.articulatedAxis.curRot) < math.rad(0.5)
            end

            if allSet then
                self.crabSteering.stateCurrent = self.crabSteering.stateTarget
            end
        end
    end

    return 
end
CrabSteering.draw = function (self)
    if self.getIsActive(self) and self.getIsActiveForInput(self, false) and not self.hasInputConflictWithSelection(self) then
        local text = ""

        if self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_NONE then
            text = g_i18n:getText("SET_CRABSTEERING_NONE")
        elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_FIXED then
            text = g_i18n:getText("SET_CRABSTEERING_FIXED")
        elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT then
            text = g_i18n:getText("SET_CRABSTEERING_LEFT")
        elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT then
            text = g_i18n:getText("SET_CRABSTEERING_RIGHT")
        end

        g_currentMission:addHelpButtonText(string.format(g_i18n:getText("CRABSTEERING_HELPTEXT"), text), InputBinding.TOGGLE_CRABSTEERING)
    end

    return 
end
CrabSteering.onLeave = function (self)
    return 
end
CrabSteering.setCrabSteering = function (self, state, noEventSend)
    SetCrabSteeringEvent.sendEvent(self, state, noEventSend)

    self.crabSteering.stateTarget = state
    self.crabSteering.steeringTarget = 0

    if self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT then
        self.crabSteering.steeringTarget = self.crabSteering.steeringOffset
    elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT then
        self.crabSteering.steeringTarget = -self.crabSteering.steeringOffset
    end

    for i, bar in pairs(self.crabSteering.safetyBars) do
        local animTime = self.getAnimationTime(self, bar.animationName)

        if bar.state == state and animTime < 1 then
            self.playAnimation(self, bar.animationName, 1, nil, true)
        elseif 0 < animTime then
            self.playAnimation(self, bar.animationName, -1, nil, true)
        end
    end

    for i, wheelIndex in pairs(self.crabSteering.backIndices) do
        if self.wheels[wheelIndex] ~= nil then
            self.wheels[wheelIndex].crabSteeringTargetReached = false
        end
    end

    if self.animations ~= nil and self.crabSteering.animation.name ~= nil then
        local targetTime = self.crabSteering.animation.middle
        local currentTime = self.getAnimationTime(self, self.crabSteering.animation.name)

        if self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT then
            targetTime = self.crabSteering.animation.left
        elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT then
            targetTime = self.crabSteering.animation.right
        end

        local speed = 1

        if targetTime < currentTime then
            speed = -1
        end

        self.playAnimation(self, self.crabSteering.animation.name, speed, currentTime, true)
        self.setAnimationStopTime(self, self.crabSteering.animation.name, targetTime)
    end

    return 
end
CrabSteering.startAIThreshing = function (self, noEventSend)
    self.crabSteering.stateTarget = CrabSteering.STEERING_STATE_NONE

    return 
end
CrabSteering.stopAIThreshing = function (self, noEventSend)
    return 
end
CrabSteering.setAIImplementsMoveDown = function (self, moveDown)
    return 
end
CrabSteering.updateSteeringAngle = function (self, wheel, dt, steeringAngle)
    local adjustBackWheel = false
    local isBackWheel = false
    local isFrontWheel = false

    if self.crabSteering ~= nil then
        if self.crabSteering.backIndices ~= nil then
            for i, wheelIndex in pairs(self.crabSteering.backIndices) do
                if self.wheels[wheelIndex] == wheel then
                    adjustBackWheel = true
                    isBackWheel = true

                    break
                end
            end

            adjustBackWheel = adjustBackWheel and (self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT or self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT)
        end

        if self.crabSteering.frontIndices ~= nil then
            for i, wheelIndex in pairs(self.crabSteering.frontIndices) do
                if self.wheels[wheelIndex] == wheel then
                    isFrontWheel = true

                    break
                end
            end
        end
    end

    local targetAngle = self.crabSteering.steeringTarget

    if wheel.steeringOffset == nil then
        wheel.steeringOffset = 0
    end

    if isBackWheel or (isFrontWheel and self.crabSteering.applySteeringOffsetToFrontWheels) then
        local rotScale = math.min((self.lastSpeed*self.speedRotScale + self.speedRotScaleOffset)/1, 1)
        local delta = dt*0.001*rotScale

        if self.rotatedTime ~= 0 then
            self.crabSteering.lastRotatedTimeNotZero = self.rotatedTime
        end

        local rotSpeed = wheel.rotSpeed

        if self.crabSteering.lastRotatedTimeNotZero <= 0 and wheel.rotSpeedNeg ~= nil then
            rotSpeed = wheel.rotSpeedNeg
        end

        if targetAngle < wheel.steeringOffset then
            wheel.steeringOffset = math.max(targetAngle, wheel.steeringOffset - math.abs(delta*rotSpeed))
        elseif wheel.steeringOffset < targetAngle then
            wheel.steeringOffset = math.min(targetAngle, wheel.steeringOffset + math.abs(delta*rotSpeed))
        end

        local steeringTarget = steeringAngle

        if isBackWheel then
            if self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT or self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT then
                steeringTarget = wheel.steeringOffset
            elseif self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_FIXED and not SpecializationUtil.hasSpecialization(ArticulatedAxis, self.specializations) then
                steeringTarget = wheel.steeringOffset
            end
        end

        if isFrontWheel then
            steeringTarget = wheel.steeringOffset + steeringAngle

            if 0 < self.rotatedTime then
                steeringTarget = math.max(steeringAngle, steeringTarget)
            elseif self.rotatedTime < 0 then
                steeringTarget = math.min(steeringAngle, steeringTarget)
            end
        end

        steeringTarget = math.max(wheel.rotMin, math.min(wheel.rotMax, steeringTarget))

        if steeringTarget < wheel.steeringAngle then
            wheel.steeringAngle = math.max(steeringTarget, wheel.steeringAngle - math.abs(delta*rotSpeed))
        elseif wheel.steeringAngle < steeringTarget then
            wheel.steeringAngle = math.min(steeringTarget, wheel.steeringAngle + math.abs(delta*rotSpeed))
        end

        return wheel.steeringAngle
    end

    return steeringAngle
end
CrabSteering.updateArticulatedAxisRotation = function (self, steeringAngle, dt)
    local fixedSteering = false
    local curRot = self.articulatedAxis.curRot

    if self.crabSteering ~= nil and (self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_FIXED or self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_LEFT or self.crabSteering.stateTarget == CrabSteering.STEERING_STATE_RIGHT) then
        steeringAngle = self.crabSteering.steeringTarget
    end

    if self.articulatedAxis.curRot < steeringAngle then
        self.articulatedAxis.curRot = math.min(steeringAngle, self.articulatedAxis.curRot + math.abs(self.articulatedAxis.rotSpeed)*dt*0.001)
    elseif steeringAngle < self.articulatedAxis.curRot then
        self.articulatedAxis.curRot = math.max(steeringAngle, self.articulatedAxis.curRot - math.abs(self.articulatedAxis.rotSpeed)*dt*0.001)
    end

    if self.crabSteering ~= nil and self.crabSteering.stateCurrent ~= self.crabSteering.stateTarget then
        local adjustedRotation = false
        local cutterObject = next(self.attachedCutters)

        if cutterObject ~= nil then
            adjustedRotation = cutterObject.isLowered(cutterObject, false)
        end

        if adjustedRotation then
            local alpha = 0
            local count = 0

            for i, wheelIndex in pairs(self.crabSteering.backIndices) do
                alpha = alpha + self.wheels[wheelIndex].steeringAngle
                count = count + 1
            end

            alpha = alpha/count
            alpha = alpha - curRot
            local v = 0
            local count = 0

            for i, wheelIndex in pairs(self.crabSteering.backIndices) do
                local wheel = self.wheels[wheelIndex]
                local axleSpeed = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape)

                if wheel.hasGroundContact then
                    local longSlip, latSlip = getWheelShapeSlip(wheel.node, wheel.wheelShape)
                    local fac = math.min(1, longSlip) - 1
                    v = v + fac*axleSpeed*wheel.radius
                    count = count + 1
                end
            end

            v = v/count
            local h = v*0.001*dt
            local g = math.sin(alpha)*h
            local a = math.cos(alpha)*h
            local ls = self.crabSteering.distFromCompJointToCenterOfBackWheels
            local beta = math.atan2(g, ls - a)
            self.articulatedAxis.curRot = curRot + beta
        else
            local speed = self.getLastSpeed(self)
            local fac = speed/20

            if self.articulatedAxis.curRot < steeringAngle then
                self.articulatedAxis.curRot = math.min(self.crabSteering.steeringTarget, curRot + (self.crabSteering.steeringOffset*fac*dt)/2000)
            elseif steeringAngle < self.articulatedAxis.curRot then
                self.articulatedAxis.curRot = math.max(self.crabSteering.steeringTarget, curRot - (self.crabSteering.steeringOffset*fac*dt)/2000)
            end
        end
    end

    self.articulatedAxis.curRot = math.max(self.articulatedAxis.rotMin, math.min(self.articulatedAxis.rotMax, self.articulatedAxis.curRot))

    return self.articulatedAxis.curRot
end

return 
