-- This Source Code Form is subject to the terms of the bCDDL, v. 1.1.
-- If a copy of the bCDDL was not distributed with this
-- file, You can obtain one at http://beamng.com/bCDDL-1.1.txt

-- shiftlogic mod begins with "--mod"

local M = {}

local max = math.max
local min = math.min
local abs = math.abs
local fsign = fsign

local constants = {rpmToAV = 0.104719755, avToRPM = 9.549296596425384}

local newDesiredGearIndex = 0
local gearbox = nil
local engine = nil
local torqueConverter = nil

local sharedFunctions = nil
local gearboxAvailableLogic = nil
local gearboxLogic = nil

M.gearboxHandling = nil
M.timer = nil
M.timerConstants = nil
M.inputValues = nil
M.shiftPreventionData = nil
M.shiftBehavior = nil
M.smoothedValues = nil

M.currentGearIndex = 0
M.maxGearIndex = 0
M.minGearIndex = 0
M.throttle = 0
M.brake = 0
M.clutchRatio = 1
M.shiftingAggression = 0
M.isArcadeSwitched = false
M.isSportModeActive = false

M.smoothedAvgAVInput = 0
M.rpm = 0
M.idleRPM = 0
M.maxRPM = 0

M.engineThrottle = 0
M.engineLoad = 0
M.engineTorque = 0
M.flywheelTorque = 0
M.gearboxTorque = 0

M.ignition = true
M.isEngineRunning = 0

M.oilTemp = 0
M.waterTemp = 0
M.checkEngine = false

M.energyStorages = {}

local automaticHandling = {
  availableModes = {"P", "R", "N", "D", "S", "1", "2", "3", "M"},
  hShifterModeLookup = {[-1] = "R", [0] = "N", "P", "D", "3", "2", "1", "S"},
  forwardModes = {["D"] = true, ["S"] = true, ["1"] = true, ["2"] = true, ["3"] = true, ["M"] = true},
  availableModeLookup = {},
  existingModeLookup = {},
  modeIndexLookup = {},
  modes = {},
  mode = nil,
  modeIndex = 0,
  maxAllowedGearIndex = 0,
  minAllowedGearIndex = 0,
  autoDownShiftInM = true,
  autoDownShiftMinGear = 1, --mod
  amtIdleThrottle = 0.01, --mod AMT upshift throttle cut
  amtRevMatchThrottle = 0.50, --mod AMT downshift rev match
  amtRevMatchMinThrottle = 0.50, --mod AMT downshift rev match
  amtRevMatchMaxThrottle = 1, --mod AMT downshift rev match cap
  defaultForwardMode = "D",
  throttleCoefWhileShifting = 1
}

local torqueConverterHandling = {
  lockupAV = 0,
  lockupRange = 0,
  lockupMinGear = 0,
  lockupIdleCoef = 0, --mod modern converter lockup idle speed
  lockupAMTCreep = 0, --mod AMT idle creep
  lockupAMTBrakeThreshold = 0.01, --mod brake disable idle creep
  lockupAMTRevMargin = 0, --mod AMT rev match margin
  lockupFullyMinGear = 0, --mod overdrive lockup
  hasLockup = false
}

local function getGearName()
  local modePrefix = ""
  if automaticHandling.mode == "S" then
    modePrefix = "S"
  elseif string.sub(automaticHandling.mode, 1, 1) == "M" then
    modePrefix = "M"
  end
  return modePrefix ~= "" and modePrefix .. tostring(gearbox.gearIndex) or automaticHandling.mode
end

local function getGearPosition()
  return (automaticHandling.modeIndex - 1) / (#automaticHandling.modes - 1), automaticHandling.modeIndex
end

local function applyGearboxModeRestrictions()
  local manualModeIndex
  if string.sub(automaticHandling.mode, 1, 1) == "M" then
    manualModeIndex = string.sub(automaticHandling.mode, 2)
  end
  local maxGearIndex = gearbox.maxGearIndex
  local minGearIndex = gearbox.minGearIndex
  if automaticHandling.mode == "1" then
    maxGearIndex = 1
    minGearIndex = 1
  elseif automaticHandling.mode == "2" then
    maxGearIndex = 2
    minGearIndex = 2
  elseif automaticHandling.mode == "3" then
    maxGearIndex = 3
    minGearIndex = 1
  elseif manualModeIndex then
    maxGearIndex = manualModeIndex
    minGearIndex = manualModeIndex
  end

  automaticHandling.maxGearIndex = maxGearIndex
  automaticHandling.minGearIndex = minGearIndex
end

local function applyGearboxMode()
  local autoIndex = automaticHandling.modeIndexLookup[automaticHandling.mode]
  if autoIndex then
    automaticHandling.modeIndex = min(max(autoIndex, 1), #automaticHandling.modes)
    automaticHandling.mode = automaticHandling.modes[automaticHandling.modeIndex]
  end

  if automaticHandling.mode == "P" then
    gearbox:setGearIndex(0)
    gearbox:setMode("park")
  elseif automaticHandling.mode == "N" then
    gearbox:setGearIndex(0)
    gearbox:setMode("neutral")
  else
    gearbox:setMode("drive")
    if automaticHandling.mode == "R" and gearbox.gearIndex > -1 then
      gearbox:setGearIndex(-1)
    elseif automaticHandling.mode ~= "R" and gearbox.gearIndex < 1 then
      gearbox:setGearIndex(1)
    end
  end

  M.isSportModeActive = automaticHandling.mode == "S"
end

local function gearboxBehaviorChanged(behavior)
  gearboxLogic = gearboxAvailableLogic[behavior]
  M.updateGearboxGFX = gearboxLogic.inGear
  M.shiftUp = gearboxLogic.shiftUp
  M.shiftDown = gearboxLogic.shiftDown
  M.shiftToGearIndex = gearboxLogic.shiftToGearIndex

  if behavior == "arcade" then
    if gearbox.gearIndex > 0 then
      automaticHandling.mode = automaticHandling.defaultForwardMode
      if automaticHandling.mode == "M1" then
        automaticHandling.mode = "M" .. tostring(max(gearbox.gearIndex, 1))
      end
    elseif gearbox.gearIndex < 0 then
      automaticHandling.mode = "R"
    else
      automaticHandling.mode = "N"
    end
  end

  applyGearboxMode()
end

local function setDefaultForwardMode(mode)
  --todo directly set the active mode as well if we are in forward
  automaticHandling.defaultForwardMode = mode
  if automaticHandling.mode == "D" or automaticHandling.mode == "S" or automaticHandling.mode == "1" or automaticHandling.mode == "2" or string.sub(automaticHandling.mode, 1, 1) == "M" then
    if mode == "M1" then --we just shifted into M1
      automaticHandling.mode = "M" .. tostring(max(gearbox.gearIndex, 1))
    else
      automaticHandling.mode = mode
    end
    applyGearboxMode()
    applyGearboxModeRestrictions()
  end
end

local function shiftUp()
  if automaticHandling.mode == "N" then
    M.timer.gearChangeDelayTimer = M.timerConstants.gearChangeDelay
  end

  local previousMode = automaticHandling.mode
  automaticHandling.modeIndex = min(automaticHandling.modeIndex + 1, #automaticHandling.modes)
  automaticHandling.mode = automaticHandling.modes[automaticHandling.modeIndex]

  if automaticHandling.mode == "M1" then --we just shifted into M1
    automaticHandling.mode = "M" .. tostring(max(gearbox.gearIndex, 1))
  end

  if M.gearboxHandling.gearboxSafety then
    local gearRatio = 0
    if string.find(automaticHandling.mode, "M") then
      local gearIndex = tonumber(string.sub(automaticHandling.mode, 2))
      gearRatio = gearbox.gearRatios[gearIndex]
    end
    if tonumber(automaticHandling.mode) then
      local gearIndex = tonumber(automaticHandling.mode)
      gearRatio = gearbox.gearRatios[gearIndex]
    end
    if gearbox.outputAV1 * gearRatio > engine.maxAV then
      automaticHandling.mode = previousMode
    end
  end

  if automaticHandling.mode == "D" or automaticHandling.mode == "R" or automaticHandling.mode == "S" then
    local gearIndex = 1
    local tmpEngineAV = gearbox.outputAV1 * gearbox.gearRatios[gearIndex]

    while tmpEngineAV >= engine.maxAV * 0.9 do
      gearIndex = gearIndex + sign(gearIndex)
      tmpEngineAV = gearbox.outputAV1 * (gearbox.gearRatios[gearIndex] or 0)
    end
    gearbox:setGearIndex(gearIndex, 0)
  end

  applyGearboxMode()
  applyGearboxModeRestrictions()
end

local function shiftDown()
  if automaticHandling.mode == "N" then
    M.timer.gearChangeDelayTimer = M.timerConstants.gearChangeDelay
  end

  local previousMode = automaticHandling.mode
  automaticHandling.modeIndex = max(automaticHandling.modeIndex - 1, 1)
  automaticHandling.mode = automaticHandling.modes[automaticHandling.modeIndex]

  if previousMode == "M1" and electrics.values.wheelspeed > 2 and M.gearboxHandling.gearboxSafety then
    --we just tried to downshift past M1, something that is irritating while racing, so we disallow this shift unless we are really slow
    automaticHandling.mode = previousMode
  end

  if M.gearboxHandling.gearboxSafety then
    local gearRatio = 0
    if string.find(automaticHandling.mode, "M") then
      local gearIndex = tonumber(string.sub(automaticHandling.mode, 2))
      gearRatio = gearbox.gearRatios[gearIndex]
    end
    if tonumber(automaticHandling.mode) then
      local gearIndex = tonumber(automaticHandling.mode)
      gearRatio = gearbox.gearRatios[gearIndex]
    end
    if gearbox.outputAV1 * gearRatio > engine.maxAV then
      automaticHandling.mode = previousMode
    end
  end

  if automaticHandling.mode == "D" or automaticHandling.mode == "R" or automaticHandling.mode == "S" then
    local gearIndex = 1
    local tmpEngineAV = gearbox.outputAV1 * gearbox.gearRatios[gearIndex]

    while tmpEngineAV >= engine.maxAV * 0.9 do
      gearIndex = gearIndex + sign(gearIndex)
      tmpEngineAV = gearbox.outputAV1 * (gearbox.gearRatios[gearIndex] or 0)
    end
    gearbox:setGearIndex(gearIndex, 0)
  end

  applyGearboxMode()
  applyGearboxModeRestrictions()
end

local function shiftToGearIndex(index)
  local desiredMode = automaticHandling.hShifterModeLookup[index]
  if not desiredMode or not automaticHandling.existingModeLookup[desiredMode] then
    if desiredMode and not automaticHandling.existingModeLookup[desiredMode] then
      guihooks.message({txt = "vehicle.drivetrain.cannotShiftAuto", context = {mode = desiredMode}}, 2, "vehicle.shiftLogic.cannotShift")
    end
    desiredMode = "N"
  end
  automaticHandling.mode = desiredMode

  if automaticHandling.mode == "D" or automaticHandling.mode == "R" or automaticHandling.mode == "S" or automaticHandling.mode == "M1" then
    local gearIndex = 1
    local tmpEngineAV = gearbox.outputAV1 * gearbox.gearRatios[gearIndex]

    while tmpEngineAV >= engine.maxAV * 0.9 do
      gearIndex = gearIndex + sign(gearIndex)
      tmpEngineAV = gearbox.outputAV1 * (gearbox.gearRatios[gearIndex] or 0)
    end
    gearbox:setGearIndex(gearIndex, 0)
    if automaticHandling.mode == "M1" then --we just shifted into M1
      automaticHandling.mode = "M" .. tostring(max(gearbox.gearIndex, 1))
    end
  end

  applyGearboxMode()
  applyGearboxModeRestrictions()
end

local function updateExposedData()
  M.rpm = engine and (engine.outputAV1 * constants.avToRPM) or 0
  M.smoothedAvgAVInput = sharedFunctions.updateAvgAVSingleDevice("gearbox")
  M.waterTemp = (engine and engine.thermals) and (engine.thermals.coolantTemperature or engine.thermals.oilTemperature) or 0
  M.oilTemp = (engine and engine.thermals) and engine.thermals.oilTemperature or 0
  M.checkEngine = engine and engine.isDisabled or false
  M.ignition = electrics.values.ignitionLevel > 1
  M.engineThrottle = (engine and engine.isDisabled) and 0 or M.throttle
  M.engineLoad = engine and (engine.isDisabled and 0 or engine.instantEngineLoad) or 0
  M.running = engine and not engine.isDisabled or false
  M.engineTorque = engine and engine.combustionTorque or 0
  M.flywheelTorque = engine and engine.outputTorque1 or 0
  M.gearboxTorque = gearbox and gearbox.outputTorque1 or 0
  M.isEngineRunning = (engine and engine.starterMaxAV) and ((engine.outputAV1 > engine.starterMaxAV * 0.8) and 1 or 0) or 1
  M.isShifting = gearbox and gearbox.isShifting or false
  M.minGearIndex = gearbox.minGearIndex
  M.maxGearIndex = gearbox.maxGearIndex
  M.shiftingAggression = M.smoothedValues.drivingAggression
end

local function updateInGearArcade(dt)
  M.throttle = M.inputValues.throttle
  M.brake = M.inputValues.brake
  M.isArcadeSwitched = false

  local gearIndex = gearbox.gearIndex
  local gearboxInputAV = gearbox.inputAV
  local gearboxOutputAV = gearbox.outputAV1 --mod output lockup
  local engineAV = engine.outputAV1

  -- driving backwards? - only with automatic shift - for obvious reasons ;)
  if (gearIndex < 0 and M.smoothedValues.avgAV <= 0.8) or (gearIndex <= 0 and M.smoothedValues.avgAV < -1) then
    M.throttle, M.brake = M.brake, M.throttle
    M.isArcadeSwitched = true
  end

  --Arcade mode gets a "rev limiter" in case the engine does not have one
  if engineAV > engine.maxAV and not engine.hasRevLimiter then
    local throttleAdjust = min(max((engineAV - engine.maxAV * 1.02) / (engine.maxAV * 0.03), 0), 1)
    M.throttle = min(max(M.throttle - throttleAdjust, 0), 1)
  end

  --mod modern AT revmatch
  local modernATRevMatchTimeModifier = min(automaticHandling.minGearChangeTime,automaticHandling.sportGearChangeTime) / (min(max(automaticHandling.gearChangeTimeRange * (M.smoothedValues.drivingAggression - 0.5) * 2 + automaticHandling.maxGearChangeTime, automaticHandling.minGearChangeTime), automaticHandling.maxGearChangeTime) +1e-30)
  local modernATRevMatchRPMModifier = gearboxInputAV / engine.maxAV
  if torqueConverterHandling.lockupIdleCoef > 0 and gearbox.isShiftingDown and gearbox.gearIndex >= 1 then
	M.throttle = max(M.throttle,automaticHandling.modernATRevMatchThrottle * modernATRevMatchTimeModifier * modernATRevMatchRPMModifier)
  end

  if M.timer.gearChangeDelayTimer <= 0 and automaticHandling.mode ~= "N" then
    local tmpEngineAV = gearboxInputAV
    local relEngineAV = gearboxInputAV / gearbox.gearRatio

    sharedFunctions.selectShiftPoints(gearIndex)

    local wheelSlipCanShiftDown = M.shiftPreventionData.wheelSlipShiftDown or M.brake <= 0
    while tmpEngineAV < M.shiftBehavior.shiftDownAV and abs(gearIndex) > 1 and wheelSlipCanShiftDown and abs(M.throttle - M.smoothedValues.throttle) < M.smoothedValues.throttleUpShiftThreshold do
      gearIndex = gearIndex - sign(gearIndex)
      tmpEngineAV = relEngineAV * (gearbox.gearRatios[gearIndex] or 0)
      if tmpEngineAV > engine.maxAV * 0.95 then
        tmpEngineAV = relEngineAV / (gearbox.gearRatios[gearIndex] or 0)
        gearIndex = gearIndex + sign(gearIndex)
        break
      end
	  
      sharedFunctions.selectShiftPoints(gearIndex)
    end

    local wheelSlipCanShiftUp = M.shiftPreventionData.wheelSlipShiftUp or M.throttle <= 0
    --shift up?
    local isRevLimitReached = engine.revLimiterActive and not (engine.isTempRevLimiterActive or false)
    if (tmpEngineAV >= M.shiftBehavior.shiftUpAV or isRevLimitReached) and (M.brake <= 0 or engineAV >= engine.maxAV) and wheelSlipCanShiftUp and abs(M.throttle - M.smoothedValues.throttle) < M.smoothedValues.throttleUpShiftThreshold and gearIndex < gearbox.maxGearIndex and gearIndex > gearbox.minGearIndex then
      gearIndex = gearIndex + sign(gearIndex)
      tmpEngineAV = relEngineAV * (gearbox.gearRatios[gearIndex] or 0)
      if tmpEngineAV < engine.idleAV * 1.1 then
        gearIndex = gearIndex - sign(gearIndex)
      else
        sharedFunctions.selectShiftPoints(gearIndex)
      end
	  
    end
  end

  if torqueConverterHandling.hasTorqueConverter then
    local lockupTarget = 0
	local revmatchmodifier = clamp((M.smoothedValues.drivingAggression - 0.2) / 0.3, 0, 1) --mod revmatch throttle 0.2 min 0.5 max
    if torqueConverterHandling.hasLockup then
      if torqueConverterHandling.lockupAMTRevMargin > 0 and gearbox.isShifting or (engine.ignitionCoef < 1 or (engine.idleAVStartOffset > 1 and M.throttle <= 0)) then
	    lockupTarget = 0
		if (engine.ignitionCoef < 1 or (engine.idleAVStartOffset > 1 and M.throttle <= 0)) then
		  M.throttle = M.throttle
		elseif engineAV < gearboxOutputAV * gearbox.gearRatios[gearIndex] * (1 - torqueConverterHandling.lockupAMTRevMargin) then
		  M.throttle = min(max(automaticHandling.amtRevMatchThrottle * revmatchmodifier + automaticHandling.amtRevMatchMinThrottle * (1 - revmatchmodifier),M.throttle),automaticHandling.amtRevMatchMaxThrottle)
		elseif engineAV > gearboxOutputAV * gearbox.gearRatios[gearIndex] * (1 + torqueConverterHandling.lockupAMTRevMargin) and engineAV > engine.idleAV * (1 + torqueConverterHandling.lockupAMTRevMargin) then
		  M.throttle = min(automaticHandling.amtIdleThrottle,M.throttle)
		  if (M.smoothedValues.drivingAggression > 0.75 and abs(gearIndex) > 1) and not (isSportMode or isManualMode) then
		    engine:cutIgnition(automaticHandling.sportGearChangeTime * 0.5)
		  end
		else
		  M.throttle = clamp(M.throttle,0,automaticHandling.amtRevMatchMaxThrottle)
		end
	  elseif torqueConverterHandling.lockupAMTRevMargin > 0 and gearIndex >= torqueConverterHandling.lockupFullyMinGear and gearboxInputAV > engine.idleAV * (1 + torqueConverterHandling.lockupAMTRevMargin) then
	    lockupTarget = 1
      elseif torqueConverterHandling.lockupAMTRevMargin > 0 then
	    lockupTarget = max(clamp((max(engineAV,gearboxInputAV) - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1),clamp(torqueConverterHandling.lockupAMTCreep - max(M.brake,input.parkingbrake) * torqueConverterHandling.lockupAMTCreep / torqueConverterHandling.lockupAMTBrakeThreshold,0,torqueConverterHandling.lockupAMTCreep))
		if gearboxInputAV < -1 then
		  M.brake = max(M.brake, torqueConverterHandling.lockupAMTBrakeThreshold)
		  lockupTarget = clamp((max(engineAV,gearboxInputAV) - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
		end
	  elseif torqueConverterHandling.lockupIdleCoef > 0 and gearboxInputAV > engine.idleAV * torqueConverterHandling.lockupIdleCoef and M.brake <= 0.25 then
	    lockupTarget = clamp((gearboxOutputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
	  elseif torqueConverterHandling.lockupFullyMinGear > 0 and gearIndex >= torqueConverterHandling.lockupFullyMinGear and gearboxInputAV > engine.idleAV and M.brake <= 0.25 then
	    lockupTarget = 1
      elseif torqueConverterHandling.lockupMinGear > 0 and gearIndex >= torqueConverterHandling.lockupMinGear and M.brake <= 0.2 and (not gearbox.isShifting or isSportMode) then
        lockupTarget = clamp((gearboxInputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
      elseif torqueConverterHandling.lockupMinGear > 0 and gearIndex >= torqueConverterHandling.lockupMinGear and M.brake <= 0.2 and gearbox.isShifting then
        lockupTarget = clamp(clamp(0.5 - M.smoothedValues.drivingAggression,0,0.5) * ((gearboxInputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange), 0, 0.5)
	  end
	end
	--mod lockup logic
    electrics.values.lockupClutchRatio = torqueConverterHandling.lockupSmoother:getUncapped(lockupTarget, dt)
  end

  -- neutral gear handling
  if abs(gearbox.gearIndex) <= 1 and M.timer.neutralSelectionDelayTimer <= 0 then
    if automaticHandling.mode ~= "P" and abs(M.smoothedValues.avgAV) < M.gearboxHandling.arcadeAutoBrakeAVThreshold and M.throttle <= 0 then
      M.brake = max(M.brake, M.gearboxHandling.arcadeAutoBrakeAmount)
    end

    if automaticHandling.mode ~= "N" and abs(M.smoothedValues.avgAV) < M.gearboxHandling.arcadeAutoBrakeAVThreshold and M.smoothedValues.throttle <= 0 then
      gearIndex = 0
      automaticHandling.mode = "N"
      applyGearboxMode()
    else
      if M.smoothedValues.throttleInput > 0 and M.inputValues.throttle > 0 and M.smoothedValues.brakeInput <= 0 and M.smoothedValues.avgAV > -1 and gearIndex < 1 then
        gearIndex = 1
        M.timer.neutralSelectionDelayTimer = M.timerConstants.neutralSelectionDelay
        automaticHandling.mode = automaticHandling.defaultForwardMode
        applyGearboxMode()
      end

      if M.smoothedValues.brakeInput > 0.1 and M.inputValues.brake > 0 and M.smoothedValues.throttleInput <= 0 and M.smoothedValues.avgAV <= 0.15 and gearIndex > -1 then
        gearIndex = -1
        M.timer.neutralSelectionDelayTimer = M.timerConstants.neutralSelectionDelay
        automaticHandling.mode = "R"
        applyGearboxMode()
      end
    end

    if electrics.values.ignitionLevel ~= 2 and automaticHandling.mode ~= "P" then
      gearIndex = 0
      M.timer.neutralSelectionDelayTimer = M.timerConstants.neutralSelectionDelay
      automaticHandling.mode = "P"
      applyGearboxMode()
    end
  end

  M.throttle = automaticHandling.mode ~= "N" and M.throttle or 0
  M.throttle = gearbox.isShiftingUp and min(M.throttle, automaticHandling.throttleCoefWhileShifting) or M.throttle

  if gearbox.gearIndex ~= gearIndex then
    newDesiredGearIndex = gearIndex
    M.updateGearboxGFX = gearboxLogic.whileShifting
  end

  M.currentGearIndex = gearIndex
  updateExposedData()
end

local function updateWhileShiftingArcade()
  M.throttle = M.inputValues.throttle
  M.brake = M.inputValues.brake
  M.isArcadeSwitched = false

  -- old -> wait -> new -> in gear update
  local gearIndex = gearbox.gearIndex
  if (gearIndex < 0 and M.smoothedValues.avgAV <= 0.15) or (gearIndex <= 0 and M.smoothedValues.avgAV < -1) then
    M.throttle, M.brake = M.brake, M.throttle
    M.isArcadeSwitched = true
  end

  local gearChangeTime = min(max(automaticHandling.gearChangeTimeRange * (M.smoothedValues.drivingAggression - 0.5) * 2 + automaticHandling.maxGearChangeTime, automaticHandling.minGearChangeTime), automaticHandling.maxGearChangeTime)
  local autoMode = string.sub(automaticHandling.mode, 1, 1)
  if (autoMode == "S" or autoMode == "M") then
    if abs(newDesiredGearIndex) > 1 and abs(newDesiredGearIndex) > abs(gearbox.gearIndex) and M.throttle > 0 then
      engine:cutIgnition(automaticHandling.sportGearChangeTime * 0.5)
      gearChangeTime = automaticHandling.sportGearChangeTime
    elseif abs(newDesiredGearIndex) > 0 and abs(newDesiredGearIndex) < abs(gearbox.gearIndex) then
      M.throttle = 1
    end
  end
  gearbox:setGearIndex(newDesiredGearIndex, gearChangeTime)
  newDesiredGearIndex = 0
  M.timer.gearChangeDelayTimer = M.timerConstants.gearChangeDelay
  M.updateGearboxGFX = gearboxLogic.inGear
  updateExposedData()
end

local function updateInGear(dt)
  M.throttle = M.inputValues.throttle
  M.brake = M.inputValues.brake
  M.isArcadeSwitched = false

  local gearIndex = gearbox.gearIndex
  local gearboxInputAV = gearbox.inputAV
  local engineAV = engine.outputAV1 --mod AMT clutch use this
  local gearboxOutputAV = gearbox.outputAV1 --mod output lockup
  
  if M.timer.gearChangeDelayTimer <= 0 and automaticHandling.mode ~= "N" then
    local tmpEngineAV = gearboxInputAV
    local relEngineAV = gearboxInputAV / gearbox.gearRatio

    sharedFunctions.selectShiftPoints(gearIndex)

    local wheelSlipCanShiftDown = M.shiftPreventionData.wheelSlipShiftDown or M.brake <= 0
    --shift down?
    while tmpEngineAV < M.shiftBehavior.shiftDownAV and abs(gearIndex) > 1 and wheelSlipCanShiftDown and abs(M.throttle - M.smoothedValues.throttle) < M.smoothedValues.throttleUpShiftThreshold do
      gearIndex = gearIndex - sign(gearIndex)
      tmpEngineAV = relEngineAV * (gearbox.gearRatios[gearIndex] or 0)
      if tmpEngineAV > engine.maxAV then
        gearIndex = gearIndex + sign(gearIndex)
        break
      end
	  
      sharedFunctions.selectShiftPoints(gearIndex)
    end

    local wheelSlipCanShiftUp = M.shiftPreventionData.wheelSlipShiftUp or M.throttle <= 0
    --shift up?
    local isRevLimitReached = engine.revLimiterActive and not (engine.isTempRevLimiterActive or false)
    if (tmpEngineAV >= M.shiftBehavior.shiftUpAV or isRevLimitReached) and (M.brake <= 0 or engineAV >= engine.maxAV) and wheelSlipCanShiftUp and abs(M.throttle - M.smoothedValues.throttle) < M.smoothedValues.throttleUpShiftThreshold and gearIndex < gearbox.maxGearIndex and gearIndex > gearbox.minGearIndex then
      gearIndex = gearIndex + sign(gearIndex)
      tmpEngineAV = relEngineAV * (gearbox.gearRatios[gearIndex] or 0)
      if tmpEngineAV < engine.idleAV then
        gearIndex = gearIndex - sign(gearIndex)
      end
	  
      sharedFunctions.selectShiftPoints(gearIndex)
    end
  end

  local isManualMode = string.sub(automaticHandling.mode, 1, 1) == "M"
  --enforce things like L and M modes
  gearIndex = min(max(gearIndex, automaticHandling.minGearIndex), automaticHandling.maxGearIndex)
  if isManualMode and gearIndex > automaticHandling.autoDownShiftMinGear and gearboxInputAV < engine.idleAV and M.shiftPreventionData.wheelSlipShiftDown and automaticHandling.autoDownShiftInM then
    gearIndex = gearIndex - 1 --mod autoDownShiftMinGear
  end
  --mod modern AT revmatch
  local modernATRevMatchTimeModifier = min(automaticHandling.minGearChangeTime,automaticHandling.sportGearChangeTime) / (min(max(automaticHandling.gearChangeTimeRange * (M.smoothedValues.drivingAggression - 0.5) * 2 + automaticHandling.maxGearChangeTime, automaticHandling.minGearChangeTime), automaticHandling.maxGearChangeTime) +1e-30)
  local modernATRevMatchRPMModifier = gearboxInputAV / engine.maxAV
  if torqueConverterHandling.lockupIdleCoef > 0 and gearbox.isShiftingDown and gearbox.gearIndex >= 1 then
	M.throttle = max(M.throttle,automaticHandling.modernATRevMatchThrottle * modernATRevMatchTimeModifier * modernATRevMatchRPMModifier)
  end

  local isSportMode = automaticHandling.mode == "S"
  if torqueConverterHandling.hasTorqueConverter then
    local lockupTarget = 0
	local revmatchmodifier = clamp((M.smoothedValues.drivingAggression - 0.2) / 0.3, 0, 1) --mod revmatch throttle 0.2 min 0.5 max
    if torqueConverterHandling.hasLockup then
      if torqueConverterHandling.lockupAMTRevMargin > 0 and gearbox.isShifting or (engine.ignitionCoef < 1 or (engine.idleAVStartOffset > 1 and M.throttle <= 0)) then --mod AMT
	    lockupTarget = 0
		if (engine.ignitionCoef < 1 or (engine.idleAVStartOffset > 1 and M.throttle <= 0)) then
		  M.throttle = M.throttle 
		elseif engineAV < gearboxOutputAV * gearbox.gearRatios[gearIndex] * (1 - torqueConverterHandling.lockupAMTRevMargin) then
		  if isManualMode then
            M.throttle = min(max(automaticHandling.amtRevMatchThrottle,M.throttle),automaticHandling.amtRevMatchMaxThrottle)
		  else
		    M.throttle = min(max(automaticHandling.amtRevMatchThrottle * revmatchmodifier + automaticHandling.amtRevMatchMinThrottle * (1 - revmatchmodifier),M.throttle),automaticHandling.amtRevMatchMaxThrottle)
		  end
		elseif engineAV > gearboxOutputAV * gearbox.gearRatios[gearIndex] * (1 + torqueConverterHandling.lockupAMTRevMargin) and engineAV > engine.idleAV * (1 + torqueConverterHandling.lockupAMTRevMargin) then
		  M.throttle = min(automaticHandling.amtIdleThrottle,M.throttle)
		  if (M.smoothedValues.drivingAggression > 0.75 and abs(gearIndex) > 1) and not (isSportMode or isManualMode) then --mod since we already cut ignition in Sport/Manual mode
		    engine:cutIgnition(automaticHandling.sportGearChangeTime * 0.5)
		  end
		else
		  M.throttle = clamp(M.throttle,0,automaticHandling.amtRevMatchMaxThrottle)
		end
	  elseif torqueConverterHandling.lockupAMTRevMargin > 0 and gearIndex >= torqueConverterHandling.lockupFullyMinGear and gearboxInputAV > engine.idleAV * (1 + torqueConverterHandling.lockupAMTRevMargin) then
	    lockupTarget = 1
      elseif torqueConverterHandling.lockupAMTRevMargin > 0 then
	    lockupTarget = max(clamp((max(engineAV,gearboxInputAV) - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1),clamp(torqueConverterHandling.lockupAMTCreep - max(M.brake,input.parkingbrake) * torqueConverterHandling.lockupAMTCreep / torqueConverterHandling.lockupAMTBrakeThreshold,0,torqueConverterHandling.lockupAMTCreep))
		if gearboxInputAV < -1 then
		  M.brake = max(M.brake, torqueConverterHandling.lockupAMTBrakeThreshold - input.parkingbrake)
		  lockupTarget = clamp((max(engineAV,gearboxInputAV) - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
		end
	  elseif torqueConverterHandling.lockupIdleCoef > 0 and gearboxInputAV > engine.idleAV * torqueConverterHandling.lockupIdleCoef and M.brake <= 0.25 then --mod modern AT
	    lockupTarget = clamp((gearboxOutputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
	  elseif torqueConverterHandling.lockupFullyMinGear > 0 and gearIndex >= torqueConverterHandling.lockupFullyMinGear and gearboxInputAV > engine.idleAV and M.brake <= 0.25 then --mod classic AT TC fully locked
	    lockupTarget = 1
      elseif torqueConverterHandling.lockupMinGear > 0 and gearIndex >= torqueConverterHandling.lockupMinGear and M.brake <= 0.2 and (not gearbox.isShifting or isSportMode) then --mod classic AT TC locking with throttle
        lockupTarget = clamp((gearboxInputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange, 0, 1)
      elseif torqueConverterHandling.lockupMinGear > 0 and gearIndex >= torqueConverterHandling.lockupMinGear and M.brake <= 0.2 and gearbox.isShifting then --mod classic AT 0.5 TC locking during shifts
        lockupTarget = clamp(clamp(0.5 - M.smoothedValues.drivingAggression,0,0.5) * ((gearboxInputAV - torqueConverterHandling.lockupThrottleRange * clamp(M.throttle / (torqueConverterHandling.lockupDelayMaxThrottle - torqueConverterHandling.lockupDelayMinThrottle) + torqueConverterHandling.lockupDelayMinThrottle / (torqueConverterHandling.lockupDelayMinThrottle - torqueConverterHandling.lockupDelayMaxThrottle),0,1) - torqueConverterHandling.lockupAV) / torqueConverterHandling.lockupRange), 0, 0.5)
	  end
	end
	--mod lockup logic
    electrics.values.lockupClutchRatio = torqueConverterHandling.lockupSmoother:getUncapped(lockupTarget, dt)
  end

  M.throttle = (gearbox.isShiftingUp and not isSportMode) and min(M.throttle, automaticHandling.throttleCoefWhileShifting) or M.throttle

  if gearbox.gearIndex ~= gearIndex then
    newDesiredGearIndex = gearIndex
    M.updateGearboxGFX = gearboxLogic.whileShifting
  end

  M.currentGearIndex = gearIndex
  updateExposedData()

  if isManualMode then
    automaticHandling.mode = "M" .. gearIndex
    automaticHandling.modeIndex = automaticHandling.modeIndexLookup[automaticHandling.mode]
    applyGearboxModeRestrictions()
  end
end

local function updateWhileShifting()
  -- old -> wait -> new -> in gear update
  M.throttle = M.inputValues.throttle
  M.brake = M.inputValues.brake
  M.isArcadeSwitched = false

  local gearChangeTime = min(max(automaticHandling.gearChangeTimeRange * (M.smoothedValues.drivingAggression - 0.5) * 2 + automaticHandling.maxGearChangeTime, automaticHandling.minGearChangeTime), automaticHandling.maxGearChangeTime)
  local autoMode = string.sub(automaticHandling.mode, 1, 1)
  if (autoMode == "S" or autoMode == "M") then
    if abs(newDesiredGearIndex) > 1 and abs(newDesiredGearIndex) > abs(gearbox.gearIndex) and M.throttle > 0 then
      engine:cutIgnition(automaticHandling.sportGearChangeTime * 0.5)
      gearChangeTime = automaticHandling.sportGearChangeTime
    elseif abs(newDesiredGearIndex) > 0 and abs(newDesiredGearIndex) < abs(gearbox.gearIndex) then
      M.throttle = 1
    end
  end
  gearbox:setGearIndex(newDesiredGearIndex, gearChangeTime)
  newDesiredGearIndex = 0
  M.timer.gearChangeDelayTimer = M.timerConstants.gearChangeDelay
  M.updateGearboxGFX = gearboxLogic.inGear
  updateExposedData()
end

local function sendTorqueData()
  if engine then
    engine:sendTorqueData()
  end
end

local function init(jbeamData, sharedFunctionTable)
  sharedFunctions = sharedFunctionTable
  engine = powertrain.getDevice("mainEngine")
  gearbox = powertrain.getDevice("gearbox")
  torqueConverter = powertrain.getDevice("torqueConverter")
  newDesiredGearIndex = 0

  M.currentGearIndex = 0
  M.throttle = 0
  M.brake = 0
  M.clutchRatio = 1

  gearboxAvailableLogic = {
    arcade = {
      inGear = updateInGearArcade,
      whileShifting = updateWhileShiftingArcade,
      shiftUp = sharedFunctions.warnCannotShiftSequential,
      shiftDown = sharedFunctions.warnCannotShiftSequential,
      shiftToGearIndex = sharedFunctions.switchToRealisticBehavior
    },
    realistic = {
      inGear = updateInGear,
      whileShifting = updateWhileShifting,
      shiftUp = shiftUp,
      shiftDown = shiftDown,
      shiftToGearIndex = shiftToGearIndex
    }
  }

  automaticHandling.availableModeLookup = {}
  for _, v in pairs(automaticHandling.availableModes) do
    automaticHandling.availableModeLookup[v] = true
  end

  automaticHandling.modes = {}
  automaticHandling.modeIndexLookup = {}
  local modes = jbeamData.automaticModes or "PRNDS21M"
  local modeCount = #modes
  local modeOffset = 0
  local forwardModes = {}
  for i = 1, modeCount do
    local mode = modes:sub(i, i)
    if automaticHandling.availableModeLookup[mode] then
      if automaticHandling.forwardModes[mode] then
        table.insert(forwardModes, mode)
      end
      if mode ~= "M" then
        automaticHandling.modes[i + modeOffset] = mode
        automaticHandling.modeIndexLookup[mode] = i + modeOffset
        automaticHandling.existingModeLookup[mode] = true
      else
        for j = 1, gearbox.maxGearIndex, 1 do
          local manualMode = "M" .. tostring(j)
          local manualModeIndex = i + j - 1
          automaticHandling.modes[manualModeIndex] = manualMode
          automaticHandling.modeIndexLookup[manualMode] = manualModeIndex
          automaticHandling.existingModeLookup[manualMode] = true
          modeOffset = j - 1
        end
      end
    else
      print("unknown auto mode: " .. mode)
    end
  end

  if torqueConverter then
    torqueConverterHandling.hasTorqueConverter = true
    torqueConverterHandling.lockupAV = (jbeamData.torqueConverterLockupRPM or 0) * constants.rpmToAV
    torqueConverterHandling.lockupRange = (jbeamData.torqueConverterLockupRange or (torqueConverterHandling.lockupAV * 0.2 * constants.avToRPM)) * constants.rpmToAV
    torqueConverterHandling.lockupThrottleRange = (jbeamData.torqueConverterThrottleLockupRange or 0) * constants.rpmToAV
    --mod delay torqueConverterLockup with throttle application
    torqueConverterHandling.lockupDelayMinThrottle = jbeamData.torqueConverterDelayLockupMinThrottle or 0
    --mod no lockup delay below this throttle value
    torqueConverterHandling.lockupDelayMaxThrottle = jbeamData.torqueConverterDelayLockupMaxThrottle or 1
    --mod max lockup delay above this throttle value
    torqueConverterHandling.lockupMinGear = jbeamData.torqueConverterLockupMinGear or 0
    torqueConverterHandling.lockupFullyMinGear = jbeamData.torqueConverterLockupFullyMinGear or 0  --mod overdrive always lock TC
    torqueConverterHandling.hasLockup = torqueConverterHandling.lockupAV > 0
	torqueConverterHandling.lockupIdleCoef = jbeamData.torqueConverterlockupIdleCoef or 0 --mod output speed lock stall prevention
	torqueConverterHandling.lockupAMTRevMargin = jbeamData.torqueConverterAMTRevMargin or 0 --mod AMT rev match margin
	torqueConverterHandling.lockupAMTCreep = jbeamData.torqueConverterAMTCreep or 0 --mod AMT idle creep
	torqueConverterHandling.lockupAMTBrakeThreshold = jbeamData.torqueConverterAMTBrakeThreshold or 0.01 --mod AMT this amount of brake disable idle creep
    local lockupRate = jbeamData.torqueConverterLockupRate or 5
    local lockupInRate = jbeamData.torqueConverterLockupInRate or lockupRate * 2
    local lockupOutRate = jbeamData.torqueConverterLockupOutRate or lockupRate
    torqueConverterHandling.lockupSmoother = newTemporalSmoothing(lockupInRate, lockupOutRate)
  end

  local defaultMode = jbeamData.defaultAutomaticMode or "N"
  local defaultForwardMode = jbeamData.defaultAutomaticForwardMode or forwardModes[1] or "D"
  --if we want M as default mode, we need to actually use M1, as that's the correct mode name, "M" is just how it's called in jbeam
  if defaultMode == "M" then
    defaultMode = "M1"
  end
  if defaultForwardMode == "M" then
    defaultForwardMode = "M1"
  end
  --Check if we do actually support the requested default mode, if not, default to "D" mode
  if not automaticHandling.existingModeLookup[defaultMode] then
    defaultMode = "D"
  end

  automaticHandling.modeIndex = automaticHandling.modeIndexLookup[defaultMode]
  automaticHandling.defaultForwardMode = defaultForwardMode
  automaticHandling.mode = automaticHandling.modes[automaticHandling.modeIndex]
  automaticHandling.maxGearIndex = gearbox.maxGearIndex
  automaticHandling.minGearIndex = gearbox.minGearIndex
  automaticHandling.krawlerGearIndex = jbeamData.krawlerGearIndex or -2 --mod krawler gear
  automaticHandling.crawlerGearIndex = jbeamData.crawlerGearIndex or 2 --mod crawler gear
  automaticHandling.maxGearChangeTime = jbeamData.maxGearChangeTime or 0
  automaticHandling.minGearChangeTime = jbeamData.minGearChangeTime or 0
  automaticHandling.sportGearChangeTime = jbeamData.sportGearChangeTime or 0
  automaticHandling.gearChangeTimeRange = automaticHandling.minGearChangeTime - automaticHandling.maxGearChangeTime
  automaticHandling.autoDownShiftInM = jbeamData.autoDownShiftInM == nil and true or jbeamData.autoDownShiftInM
  automaticHandling.autoDownShiftMinGear = jbeamData.autoDownShiftMinGear or 1 --mod
  automaticHandling.throttleCoefWhileShifting = jbeamData.throttleCoefWhileShifting or 1
  automaticHandling.amtIdleThrottle = jbeamData.amtIdleThrottle or 0.01 --mod AMT upshift throttle cut
  automaticHandling.amtRevMatchThrottle = jbeamData.amtRevMatchThrottle or 0.5 --mod AMT downshift rev match
  automaticHandling.amtRevMatchMinThrottle = jbeamData.amtRevMatchMinThrottle or automaticHandling.amtRevMatchThrottle --mod AMT downshift rev match
  automaticHandling.amtRevMatchMaxThrottle = jbeamData.amtRevMatchMaxThrottle or 1 --mod AMT downshift rev match
  automaticHandling.modernATRevMatchThrottle = jbeamData.modernATRevThrottle or 0 --mod modern AT downshift rev match
  
  M.maxRPM = engine.maxRPM
  M.idleRPM = engine.idleRPM
  M.maxGearIndex = automaticHandling.maxGearIndex
  M.minGearIndex = abs(automaticHandling.minGearIndex)
  M.energyStorages = sharedFunctions.getEnergyStorages({engine})

  applyGearboxMode()
end

local function getState()
  local data = {grb_mde = automaticHandling.mode}

  return tableIsEmpty(data) and nil or data
end

local function setState(data)
  if data.grb_mde then
    automaticHandling.mode = data.grb_mde
    automaticHandling.modeIndex = automaticHandling.modeIndexLookup[automaticHandling.mode]
    applyGearboxMode()
    applyGearboxModeRestrictions()
  end
end

M.init = init

M.gearboxBehaviorChanged = gearboxBehaviorChanged
M.shiftUp = shiftUp
M.shiftDown = shiftDown
M.shiftToGearIndex = shiftToGearIndex
M.updateGearboxGFX = nop
M.getGearName = getGearName
M.getGearPosition = getGearPosition
M.setDefaultForwardMode = setDefaultForwardMode
M.sendTorqueData = sendTorqueData

M.getState = getState
M.setState = setState

return M
