-- by modelleicher


fakeBox = {};

function fakeBox.prerequisitesPresent(specializations)
	print("prerequisitesPresent is active");
    return true;
end;

-- new in FS19, you have to register yourself to functions otherwise they aren't called.
-- also, everything has the "on" prefix now (onLoad, onUpdate etc.)

-- registerEventListeners registeres listeners to base functions
-- its like appending to a function. It gets called when the base function is called.
-- its basically the same as it was previously with all the base-functions such as update and load and draw.
-- just that you have to register for them now it doesn't call them automcatically
function fakeBox.registerEventListeners(vehicleType)
	print("registerEventListeners called");
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", fakeBox);
	SpecializationUtil.registerEventListener(vehicleType, "onUpdate", fakeBox);
	SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", fakeBox); -- this one is used to add the actionEvents
end;

-- actionEvent stuffs.. (this one is called each time the vehicle is entered)
function fakeBox.onRegisterActionEvents(self, isActiveForInput)
	local spec = self.spec_fakeBox;
	spec.actionEvents = {}; -- needs this. Farmcon Example didn't have this. Doesn't work without this though.. 
	self:clearActionEventsTable(spec.actionEvents); -- not sure if we need to clear the table now that we just created it. I suppose you could create the table in onLoad, then it makes more sense
	
	-- add the actionEvents if vehicle is ready to have Inputs
	if self:getIsActiveForInput(true) then      
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SHIFT_UP, self, fakeBox.SHIFT_UP, false, true, false, true, nil);
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SHIFT_DOWN, self, fakeBox.SHIFT_DOWN, false, true, false, true, nil);
		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.RANGE_UP, self, fakeBox.RANGE_UP, false, true, false, true, nil);
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.RANGE_DOWN, self, fakeBox.RANGE_DOWN, true, true, false, true, nil);
		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.AXIS_CLUTCH, self, fakeBox.actionEventClutch, false, false, true, true)
		
		-- direct gear buttons
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_1, self, fakeBox.SELECT_GEAR_1, true, true, false, true, nil);		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_2, self, fakeBox.SELECT_GEAR_2, true, true, false, true, nil);		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_3, self, fakeBox.SELECT_GEAR_3, true, true, false, true, nil);		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_4, self, fakeBox.SELECT_GEAR_4, true, true, false, true, nil);		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_5, self, fakeBox.SELECT_GEAR_5, true, true, false, true, nil);		
		local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.SELECT_GEAR_6, self, fakeBox.SELECT_GEAR_6, true, true, false, true, nil);		
	end;
	
--_, eventId = g_inputBinding:registerActionEvent(InputAction.AXIS_ROTATE_HANDTOOL, self, self.onInputRotate, false, false, true, true)	

end;

-- direct gear selection functions 
function fakeBox:SELECT_GEAR_1(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 1;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;
end;
function fakeBox:SELECT_GEAR_2(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 2;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;	
end;
function fakeBox:SELECT_GEAR_3(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 3;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;	
end;
function fakeBox:SELECT_GEAR_4(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 4;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;	
end;
function fakeBox:SELECT_GEAR_5(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 5;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;	
end;
function fakeBox:SELECT_GEAR_6(actionName, inputValue)
	if self.spec_fakeBox.clutchPercent < 0.4 then
		self.spec_fakeBox.newGear = 6;
	end;
	if self.spec_fakeBox.fullManualMode and inputValue == 0 then
		self.spec_fakeBox.neutral = true;
	end;	
end;

-- the functions called by the action events
function fakeBox:SHIFT_UP()
	local spec = self.spec_fakeBox;
	if spec.clutchPercent < 0.4 then
		spec.newGear = math.min(spec.newGear +1, spec.numberOfGears);
	end;
end;

function fakeBox:SHIFT_DOWN()
	local spec = self.spec_fakeBox;
	if spec.clutchPercent < 0.4 then
		spec.newGear = math.max(spec.newGear -1, 1);
	end;
end;

function fakeBox:RANGE_UP()
	local spec = self.spec_fakeBox;
	if spec.clutchPercent < 0.4 or spec.rangesPowershift then
		spec.newRange = math.min(spec.newRange +1, spec.numberOfRanges);
	end;
end;

function fakeBox:RANGE_DOWN(actionName, inputValue, callbackState, isAnalog, test1, test2, test3, test4, test5)
	local spec = self.spec_fakeBox;
	if spec.clutchPercent < 0.4 or spec.rangesPowershift then
		spec.newRange = math.max(spec.newRange -1, 1);
	end;
	
end;

-- Clutch Pedal Action Input 
function fakeBox:actionEventClutch(actionName, inputValue, callbackState, isAnalog)
	--print(tostring(actionName));
	--print(tostring(inputValue));
	--print(tostring(callbackState));
	--print(tostring(isAnalog));
	self.spec_fakeBox.clutchPercent = 1 - inputValue;
end;



local oldUpdateWheelsPhysics = WheelsUtil.updateWheelsPhysics;
function fakeBox.prependUpdateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, stopAndGoBraking)
	if not self.hasFakeBox then
		oldUpdateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, stopAndGoBraking);
		--print("old");
	else
		-- since the engine is always running, acceleration is never 0.. So in idle lets say we accelerate by 5%.
		if acceleration == 0 then
			acceleration = 0.05;
		end;
		-- if we are in neutral, don't accelerate 
		if self.spec_fakeBox.neutral then
			acceleration = 0;
		end;
		
		-- also, acceleration if clutch is present is dependent on clutch engagement percentage
		oldUpdateWheelsPhysics(self, dt, currentSpeed, acceleration*self.spec_fakeBox.clutchPercent, doHandbrake, stopAndGoBraking); -- set acceleration dependent on clutch so we don't speed up if clutch is fully pressed
	end;
end;
	
--[[function fakeBox.newUpdateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, stopAndGoBraking)
	if not self.hasFakeBox then
		oldUpdateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, stopAndGoBraking);
		print("old");
	else

	--print("function WheelsUtil.updateWheelsPhysics("..tostring(self)..", "..tostring(dt)..", "..tostring(currentSpeed)..", "..tostring(acceleration)..", "..tostring(doHandbrake)..", "..tostring(stopAndGoBraking))

		local acceleratorPedal = 0
		local brakePedal = 0

		local reverserDirection = 1
		if self.spec_drivable ~= nil then
			reverserDirection = self.spec_drivable.reverserDirection
			acceleration = acceleration * reverserDirection
		end

		local motor = self.spec_motorized.motor

		local absCurrentSpeed = math.abs(currentSpeed)
		local accSign = MathUtil.sign(acceleration)

		self.nextMovingDirection = Utils.getNoNil(self.nextMovingDirection, 0)

		local automaticBrake = false

		if math.abs(acceleration) < 0.001 then
			automaticBrake = true

			-- Non-stop&go only allows change of direction if the vehicle speed is smaller than 1km/h or the direction has already changed (e.g. because the brakes are not hard enough)
			if stopAndGoBraking or currentSpeed * self.nextMovingDirection < 0.0003 then
				self.nextMovingDirection = 0
			end
		else
			-- Disable the known moving direction if the vehicle is driving more than 5km/h (0.0014 * 3600 =  5.04km/h) in the opposite direction
			if self.nextMovingDirection * currentSpeed < -0.0014 then
				self.nextMovingDirection = 0
			end

			-- Continue accelerating if we want to go in the same direction
			-- or if the vehicle is only moving slowly in the wrong direction (0.0003 * 3600 = 1.08 km/h) and we are allowed to change direction
			if accSign == self.nextMovingDirection or (currentSpeed * accSign > -0.0003 and (stopAndGoBraking or self.nextMovingDirection == 0)) then
				acceleratorPedal = acceleration
				brakePedal = 0
				self.nextMovingDirection = accSign
			else
				acceleratorPedal = 0
				brakePedal = math.abs(acceleration)
				if stopAndGoBraking then
					self.nextMovingDirection = accSign
				end
			end
		end

		if automaticBrake then
			acceleratorPedal = 0
		end

		acceleratorPedal = motor:updateGear(acceleratorPedal, dt)

		if motor.gear == 0 and motor.targetGear ~= 0 then
			-- brake automatically if the vehicle is rolling backwards while shifting
			if currentSpeed * MathUtil.sign(motor.targetGear) < 0 then
				automaticBrake = true
			end
		end

		if automaticBrake then
			local isSlow = absCurrentSpeed < motor.lowBrakeForceSpeedLimit
			local isArticulatedSteering = self.spec_articulatedAxis ~= nil and self.spec_articulatedAxis.componentJoint ~= nil and math.abs(self.rotatedTime) > 0.01

			if (isSlow or doHandbrake) and not isArticulatedSteering then
				brakePedal = 1
			else
				-- interpolate between lowBrakeForce and 1 if speed is below 3.6 km/h
				local factor = math.min(absCurrentSpeed / 0.001, 1)
				brakePedal = MathUtil.lerp(1, motor.lowBrakeForceScale, factor)
			end
		end

		-- ToDo: move to Lights ?!
		if self.spec_lights ~= nil then
			if self.setBrakeLightsVisibility ~= nil then
				self:setBrakeLightsVisibility(not automaticBrake and math.abs(brakePedal) > 0)
			end

			if self.setReverseLightsVisibility ~= nil then
				self:setReverseLightsVisibility((currentSpeed < -0.0006 or acceleratorPedal < 0) and reverserDirection == 1)
			end
		end

		acceleratorPedal, brakePedal = WheelsUtil.getSmoothedAcceleratorAndBrakePedals(self, acceleratorPedal, brakePedal, dt)

		
		--active braking if over the speed limit
		--local overSpeedLimit = self:getLastSpeed() - motor:getSpeedLimit()
		--if overSpeedLimit > 0 then
		--	brakePedal = math.max(math.min(overSpeedLimit/5, 1), brakePedal)
		--	acceleratorPedal = math.min(0, acceleratorPedal)
		--end
		-- removed modelleicher

		if next(self.spec_motorized.differentials) ~= nil and self.spec_motorized.motorizedNode ~= nil then

			local absAcceleratorPedal = math.abs(acceleratorPedal)
			local minGearRatio, maxGearRatio = motor:getMinMaxGearRatio()

			local maxSpeed;
			if maxGearRatio >= 0 then
				maxSpeed = motor:getMaximumForwardSpeed()
			else
				maxSpeed = motor:getMaximumBackwardSpeed()
			end

			local acceleratorPedalControlsSpeed = false
			if acceleratorPedalControlsSpeed then
				maxSpeed = maxSpeed * absAcceleratorPedal
				if absAcceleratorPedal > 0.001 then
					absAcceleratorPedal = 1
				end
			end
			maxSpeed = math.min(maxSpeed, motor:getSpeedLimit() / 3.6)
			local maxAcceleration = motor:getAccelerationLimit()
			local minMotorRpm, maxMotorRpm = motor:getRequiredMotorRpmRange()

			local neededPtoTorque = PowerConsumer.getTotalConsumedPtoTorque(self) / motor:getPtoMotorRpmRatio();

			print(string.format("set vehicle props:   accPed=%.1f   speed=%.1f gearRatio=[%.1f %.1f] rpm=[%.1f %.1f]", absAcceleratorPedal, maxSpeed, minGearRatio, maxGearRatio, minMotorRpm, maxMotorRpm))
			controlVehicle(self.spec_motorized.motorizedNode, absAcceleratorPedal, maxSpeed, maxAcceleration, minMotorRpm*math.pi/30, maxMotorRpm*math.pi/30, minGearRatio, maxGearRatio, motor:getMaxClutchTorque(), neededPtoTorque, 800)
		end

		self:brake(brakePedal)
	end;
end;
-]]

WheelsUtil.updateWheelsPhysics = fakeBox.prependUpdateWheelsPhysics;



function fakeBox:onLoad(savegame)
	print("onLoad loaded");
	
	self.hasFakeBox = false;
	
	-- also new in FS19.. Namespaces. So you do not have interference between different lua Scripts.
	-- its basically like using self.specName = {} table to store all your variables for that specialization in
	self.spec_fakeBox = {};  -- creating the table where all the variables are stored in
	local spec = self.spec_fakeBox; -- this looks different to the example on the farmCon video. didn't work the other way though I assume mistake in their presentation
	-- from here on out its not self.spec_fakeBox.zylinderCount for example (altough it should work too) its
	-- spec.zylinderCount instead

	local xmlFile = self.xmlFile;
	
	
	--[[<fakeBox>
		<gears>
			<gear ratio="10" name="1" />
			<gear ratio="25" name="2" />
		</gears>
		
		<ranges>
			<range ratio="0.5" name="I" />
			<range ratio="1" name="II" />
		</ranges>
	
	</fakeBox>]]
	
	
	spec.gears = {};
	local i = 0;
	while true do
		local ratio = getXMLFloat(self.xmlFile, "vehicle.fakeBox.gears.gear("..i..")#ratio")
		local speed = getXMLFloat(self.xmlFile, "vehicle.fakeBox.gears.gear("..i..")#speed")
		
		if ratio == nil and speed ~= nil then
			ratio = 836 / speed;
		end;
		
		local name = getXMLString(self.xmlFile, "vehicle.fakeBox.gears.gear("..i..")#name");
		if ratio ~= nil and name ~= nil then
			spec.gears[i+1] = {ratio = ratio, name = name};
		else
			break;
		end;
		i = i+1;
	end;
	spec.numberOfGears = i;
	
	if i > 0 then
		self.hasFakeBox = true;
	end;
	
	
	if self.hasFakeBox then
	

		spec.ranges = {};
		i = 0;
		while true do
			local ratio = getXMLFloat(self.xmlFile, "vehicle.fakeBox.ranges.range("..i..")#ratio")
			local name = getXMLString(self.xmlFile, "vehicle.fakeBox.ranges.range("..i..")#name");
			if ratio ~= nil and name ~= nil then
				spec.ranges[i+1] = {ratio = ratio, name = name};
			else
				break;
			end;
			i = i+1;
		end;	
		spec.numberOfRanges = i;
		spec.rangesPowershift = getXMLInt(self.xmlFile, "vehicle.fakeBox.ranges#powerShift");
		
		spec.finalRatio = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.fakeBox#finalRatio"), 1);
		
		spec.neutral = true;
		
		
		
		--spec.gearRatios = {10000, 60, 45, 33, 22, 15};
		
		
		local motor = self.spec_motorized.motor;
		
		motor.minForwardGearRatio = spec.gears[1].ratio;
		motor.maxForwardGearRatio = spec.gears[1].ratio;
		
		motor.minBackwardGearRatio = spec.gears[1].ratio;
		motor.maxBackwardGearRatio = spec.gears[1].ratio;
		
		spec.lastGear = 1;
		spec.newGear = 1;
		
		spec.lastRange = 1;
		spec.newRange = 1;

		
		spec.clutchPercent = 1;
		spec.wantedGearRatio = 0;
		
		spec.fullManualMode = true;
	end;
end;

local oldMotorUpdate = VehicleMotor.update;
function fakeBox:newMotorUpdate(dt)
	local vehicle = self.vehicle
	if not vehicle.hasFakeBox then
		oldMotorUpdate(self, dt);
		
	else
		--print("new 2")
		
		if next(vehicle.spec_motorized.differentials) ~= nil and vehicle.spec_motorized.motorizedNode ~= nil then
			-- Only update the physics values if a physics simulation was performed
			if g_physicsDtNonInterpolated > 0.0 then
				local lastMotorRotSpeed = self.motorRotSpeed;
				local lastDiffRotSpeed = self.differentialRotSpeed
				self.motorRotSpeed, self.differentialRotSpeed, self.gearRatio = getMotorRotationSpeed(vehicle.spec_motorized.motorizedNode)

				self.motorAvailableTorque, self.motorAppliedTorque, self.motorExternalTorque = getMotorTorque(vehicle.spec_motorized.motorizedNode)


				local motorRotAcceleration = (self.motorRotSpeed - lastMotorRotSpeed) / (g_physicsDtNonInterpolated*0.001)
				self.motorRotAcceleration = motorRotAcceleration
				self.motorRotAccelerationSmoothed = 0.8 * self.motorRotAccelerationSmoothed + 0.2 * motorRotAcceleration

				local diffRotAcc = (self.differentialRotSpeed - lastDiffRotSpeed) / (g_physicsDtNonInterpolated*0.001)
				self.differentialRotAcceleration = diffRotAcc
				self.differentialRotAccelerationSmoothed = 0.8 * self.differentialRotAccelerationSmoothed + 0.2 * diffRotAcc

				--print(string.format("update rpms: %.2f %.2f acc: %.2f", self.motorRotSpeed*30/math.pi, self.differentialRotSpeed*self.gearRatio*30/math.pi, motorRotAcceleration))
			end

			self.requiredMotorPower = math.huge

		else
			local _, gearRatio = self:getMinMaxGearRatio()
			self.differentialRotSpeed = WheelsUtil.computeDifferentialRotSpeedNonMotor(vehicle)
			self.motorRotSpeed = math.max(self.differentialRotSpeed * gearRatio, 0)
			self.gearRatio = gearRatio
		end

		-- the clamped motor rpm always is higher-equal than the required rpm by the pto
		--local ptoRpm = math.min(PowerConsumer.getMaxPtoRpm(self.vehicle)*self.ptoMotorRpmRatio, self.maxRpm)
		-- smoothing for raise/fall of ptoRpm
		if self.lastPtoRpm == nil then
			self.lastPtoRpm = self.minRpm
		end
		local ptoRpm = PowerConsumer.getMaxPtoRpm(self.vehicle)*self.ptoMotorRpmRatio
		if ptoRpm > self.lastPtoRpm then
			self.lastPtoRpm = math.min(ptoRpm, self.lastPtoRpm + self.maxRpm*dt/2000)
		elseif ptoRpm < self.lastPtoRpm then
			self.lastPtoRpm = math.max(self.minRpm, self.lastPtoRpm - self.maxRpm*dt/1000)
		end
		local ptoRpm = math.min(self.lastPtoRpm, self.maxRpm)

		local clampedMotorRpm = math.max(self.motorRotSpeed*30/math.pi, ptoRpm, self.minRpm)
		
		-- modelleicher 
		-- if clutch is pressed, motor RPM is not dependent on wheel speed anymore.. Instead, calculate motor RPM based on accelerator pedal input 
		if vehicle.spec_fakeBox.clutchPercent < 0.999 or vehicle.spec_fakeBox.neutral then
			local clutchPercent = vehicle.spec_fakeBox.clutchPercent;
			if vehicle.spec_fakeBox.neutral then
				clutchPercent = 0;
			end;	
			local accInput = 0
			if vehicle.getAxisForward ~= nil then
				accInput = vehicle:getAxisForward()
			end;
			local wantedRpm = (self.maxRpm - self.minRpm) * accInput + self.minRpm;
			local currentRpm = self.lastRealMotorRpm;
			if currentRpm < wantedRpm then
				currentRpm = math.min(currentRpm + 2 * dt, wantedRpm);  -- to do, do proper engine rpm increase calculation 
			elseif currentRpm > wantedRpm then
				currentRpm = math.max(currentRpm - 1 * dt, wantedRpm);
			end;
			
			self.lowBrakeForceScale = 0.001;
			
			clampedMotorRpm = (clampedMotorRpm * clutchPercent) + (currentRpm * (1-clutchPercent));
			--renderText(0.1, 0.2, 0.02, "clampedMotorRpm: "..tostring(clampedMotorRpm).." currentRpm: "..tostring(currentRpm).." wantedRpm: "..tostring(wantedRpm).." accInput: "..tostring(accInput));
		else
			self.lowBrakeForceScale = 0.08;
			--clampedMotorRpm = math.max(self.motorRotSpeed*30/math.pi, ptoRpm, self.minRpm)
			
			-- get clutch RPM shut off motor if RPM gets too low , disable "auto clutch" of FS
			local clutchRpm = self:getClutchRotSpeed() *  9.5493;
			if clutchRpm < self.minRpm then
				clampedMotorRpm = clutchRpm;
			end;
			if clutchRpm < 500 then -- to do, add stall rpm variable 
				-- stall the engine 
				vehicle:stopMotor()
			end;
			--renderText(0.1, 0.3, 0.02, "clutchRpm: "..tostring(clutchRpm));
		end;
		

		
		-- end modelleicher 
		--

		self:setLastRpm(clampedMotorRpm)

		self.equalizedMotorRpm = clampedMotorRpm
	end;
end;
VehicleMotor.update = fakeBox.newMotorUpdate;


function fakeBox:onUpdate(dt) 


    if self.hasFakeBox and self:getIsActive() and self.spec_motorized.isMotorStarted then 
	
		local spec = self.spec_fakeBox;

		
		local motor = self.spec_motorized.motor;
		local rpm = motor.lastRealMotorRpm;
		
		local mLoad = self.spec_motorized.actualLoadPercentage;
		
		if spec.lastGear ~= spec.newGear then
			spec.wantedGearRatio = spec.gears[spec.newGear].ratio / spec.ranges[spec.lastRange].ratio / spec.finalRatio;
			spec.lastGear = spec.newGear;
			spec.neutral = false;
		end;		
		
		if spec.lastRange ~= spec.newRange then
			spec.wantedGearRatio = spec.gears[spec.lastGear].ratio / spec.ranges[spec.newRange].ratio / spec.finalRatio;
			spec.lastRange = spec.newRange;
		end;		
		
		
		local actualGearRatio = 0;
		local currentGearRatio = 0;
		

		
		
		-- now get the current theoretical gear ratio based on wheel speed 
		--currentGearRatio = motor.lastRealMotorRpm / wheelSpeed;	
		
		-- wheel rpm rpm = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape)*30/math.pi
		-- 
		-- max speed -> maxRpm * math.pi / (30 * minRatio)
		
		
		-- rpm = 1889
		-- ratio = 16.63
		-- wheelRPM = 129.9
		-- wheelRadius = 0.87
		-- speed = 42kph
		
		-- calculate relative axle speed: 
		local wheelSpeed = 0;
		local numWheels = 0;
		for _, wheel in pairs(self.spec_wheels.wheels) do
	
			local rpm = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape)*30/math.pi
			wheelSpeed = wheelSpeed + (rpm * wheel.radius);
			numWheels = numWheels + 1;
			
		end;		
		wheelSpeed = wheelSpeed / numWheels
		
		-- calculate theoretical gear ratio dependent on current RPM 
		currentGearRatio = motor.lastRealMotorRpm / wheelSpeed;
		
		-- cap that gear ratio at 1000 
		currentGearRatio = math.min(currentGearRatio, 1000);
		
		if spec.clutchPercent < 0.999 then
			-- calculate gear ratio based on clutch percentage between actual gear ratio and wanted gear ratio 
			actualGearRatio = (spec.wantedGearRatio * spec.clutchPercent) + (currentGearRatio * (1-spec.clutchPercent));
		else
			-- if clutch is fully engaged just use wanted gear ratio 
			actualGearRatio = spec.wantedGearRatio;
		end;
		
		--renderText(0.1, 0.1, 0.02, "actualGearRatio: "..tostring(actualGearRatio).." currentGearRatio: "..tostring(currentGearRatio).." wheelSpeed: "..tostring(wheelSpeed).." lastRealMotorRpm: "..tostring(motor.lastRealMotorRpm));
			
		-- finally set the gear ratio values 
		motor.minForwardGearRatio = actualGearRatio;
		motor.maxForwardGearRatio = actualGearRatio;
		
		motor.minBackwardGearRatio = actualGearRatio * 2;
		motor.maxBackwardGearRatio = actualGearRatio * 2;	

		if spec.neutral then
			renderText(0.93, 0.24, 0.02, "Gear: N");		
		else
			renderText(0.93, 0.24, 0.02, "Gear: "..tostring(spec.gears[spec.lastGear].name));	
		end;
		renderText(0.93, 0.26, 0.02, "Range: "..tostring(spec.ranges[spec.lastRange].name));		
		renderText(0.93, 0.28, 0.02, "Clutch: "..tostring(spec.clutchPercent));		
    end; 
end;
