--[[
---------------------------------------------------------------------------
Remote Train Control Program for Windows

© Copyright 2021 by Mark DiVecchio

	This file is part of Remote Train Control.

	Remote Train Control is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	Remote Train Control is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Remote Train Control.  If not, see <http://www.gnu.org/licenses/>.

 Home Page : http://www.silogic.com/trains/RTC_Running.html
---------------------------------------------------------------------------]]
-- Semicolons are not required in Lua code
title = "Stop Distance Test"
require([[defines]]);
require([[functions]]);

--[[
When using this module, all variables (including functions!) must be declared through 
a regular assignment (even assigning nil will do) in a strict scope before being used 
anywhere or assigned to inside a nested scope.
https://github.com/lua-stdlib/strict
]]
local _ENV = require 'std.strict' (_G)	-- strict.lua will detect undeclared global variables
--
local TagCount = 0;
-- Counter Labels
Counter01Label = "tags()";
Counter03Label = "Block Speed Limit";
local C3 = COUNTER03;
Counter04Label = "Target Distance";
local C4 = COUNTER04;
Counter05Label = "Stopping Distance";
local C5 = COUNTER05;
Counter06Label = "Stop Delay";
local C6 = COUNTER06;

local Tags = {};		-- table to hold all of the tags detected in sequence
local Layout4;
local StartedFlag = false;
local MyEngineNo, MyTIUNo;
local BeepOn = false;
local MyDirection = WB;

local SoundOn = true;
local SoundOff = false;
local EngineSoundSave;

local StationStop = false;
local BlockStop = true;

-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Control";
Tab02Label = "Support";
Tab03Label = "Not Used";

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
	print(string.format("setup() Started at %.2f", RunTime()));

	MyEngineNo = Engine;
	-- Instrumented Layout 4
	Layout4 = require([[Layout4]]);		-- All of the details about the Layout
	-- an undirected weighted double vertex graph
	MyTIUNo = Layout4.MASTER_TIU;
	Title(title .. " Engine# " .. MyEngineNo, MyEngineNo);
	-- Westbound train
	MyDirection = WB;
	--
	math.randomseed(os.time());	-- random seed
	--
	--print("Showing the Layout Window");
	--ShowLayout();
	--
	local Enum = tonumber(InputBox(title, "Enter Direction " .. WB .. "=" .. DirectionNames[WB] .. " or " .. EB .. "=" .. DirectionNames[EB], MyDirection));
	if (Enum ~= nil) then	-- if the result is a number, 1 or 2, then use it, otherwise, ignore it
		if (Enum == WB) then
			MyDirection = Enum;
			end;
		if (Enum == EB) then
			MyDirection = Enum;
			end;
		end;
	print("Setup : Direction = " .. DirectionNames[MyDirection]);
	--
	-- must ask the dispatcher
	print("Select Starting Block Number");
	for iBlock = 1, LAST_BLOCK do
		local Name = BlockName[iBlock];
		if (Name ~= nil and iBlock <= 100) then
			print(Name .. "(" .. iBlock .. ")");
			end;
		end;
	--
	-- read Starting Block Number
	--
	local CurrentBlockNo;
	local response = InputQuery(title, "Enter Starting Block Number (0 for none)", ALIQUIPPA_BLOCK);
	if (response == false) then
		return Stop("Canceled");
		end;
	local Enum = tonumber(response);
	if (Enum ~= nil and Enum ~= 0) then	-- if the result is a block number, then use it, otherwise, ignore it
		CurrentBlockNo = Enum;
		print("Setup : Starting Block = " .. BlockName[Enum]);
		end
	if (LayoutShowing()) then
		-- create and register the cab signals
		print("Setup : Creating Cab Signals");
		CreateCab(Hd3Light, Hd3Light);	-- create light signals - (home, distant) -- create as 3 head signals so that they accept all possible aspects
		print("Setup : Registering the Cab Signals #", NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], " and #", NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
		RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
										-- associate the Home cab signal with next signal and
										-- the Distant cab signal with second signal.
										-- Signals must be created by the "Layout4 Signal Master.lua"
										-- script which should be run in a different PC window
		print("Setup : Reflecting signals from Layout window into the Cab Display");
		end;
	--
	print("Set all switches to Normal postion");
	local k, v;
	-- set all switches to NORMAL position
	for k,v in pairs(Switches) do
		print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
		Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
		Sleep(0.5);
		end
	if (Debug() > 3) then	-- double toggle all switches
		-- set all switches to REVERSE
		for k,v in pairs(Switches) do
			print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
			Switch(REVERSE, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
			Sleep(0.5);
			end;
		-- set all switches to NORMAL
		for k,v in pairs(Switches) do
			print("k = " .. k .. " TIU = " .. v[TIU_NUMBER] .. " AIU = " .. v[AIU_NUMBER] .. " Channel = " .. v[CHAN_NUMBER]);
			Switch(NORMAL, v[CHAN_NUMBER], v[AIU_NUMBER], v[TIU_NUMBER], 0);
			Sleep(0.5);
			end;
		end;

	-- set Lionel-Ives (Balloon) switch to REVERSE (loop)
		Switch(REVERSE, Switches[BALLOON][CHAN_NUMBER], Switches[BALLOON][AIU_NUMBER], Switches[BALLOON][TIU_NUMBER], 0)	
		
	if (MyDirection == EB) then 
		print("Eastbound : Set passing and station track switches to Reverse postion");
		Switch(REVERSE, Switches[ALIQUIPPA_WEST][CHAN_NUMBER], Switches[ALIQUIPPA_WEST][AIU_NUMBER], Switches[ALIQUIPPA_WEST][TIU_NUMBER], 0)	
		Sleep(0.5);
		Switch(REVERSE, Switches[ALIQUIPPA_EAST][CHAN_NUMBER], Switches[ALIQUIPPA_EAST][AIU_NUMBER], Switches[ALIQUIPPA_EAST][TIU_NUMBER], 0)
		Sleep(0.5);
		Switch(REVERSE, Switches[COLLEGE_WEST][CHAN_NUMBER], Switches[COLLEGE_WEST][AIU_NUMBER], Switches[COLLEGE_WEST][TIU_NUMBER], 0)
		Sleep(0.5);
		Switch(REVERSE, Switches[COLLEGE_EAST][CHAN_NUMBER], Switches[COLLEGE_EAST][AIU_NUMBER], Switches[COLLEGE_EAST][TIU_NUMBER], 0)
		Sleep(0.5);
		Switch(REVERSE, Switches[STRUTHERS_WEST][CHAN_NUMBER], Switches[STRUTHERS_WEST][AIU_NUMBER], Switches[STRUTHERS_WEST][TIU_NUMBER], 0)
		Sleep(0.5);
		Switch(REVERSE, Switches[STRUTHERS_EAST][CHAN_NUMBER], Switches[STRUTHERS_EAST][AIU_NUMBER], Switches[STRUTHERS_EAST][TIU_NUMBER], 0)
		Sleep(0.5);
		end;
	
	-- start the engine
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, 0, MyEngineNo, MyTIUNo);
	print("setup() : Set accel/decel rates to " .. Layout4.AccDecRate);
	if (StartedFlag == false) then
		EngineStartUp(MyEngineNo, MyTIUNo);
		print("Starting up the Engine");
		StartedFlag = true;
		Sleep(20);
		end;
	SetDirection(FORWARD, 0, MyEngineNo, MyTIUNo);

	-- read up current sound volume
	EngineSoundSave = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo);	-- get Volume
	--
	-- Set 20 Smph
	local BlockSpeedLimit = 20;
	SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);
	SetCounter(C3, BlockSpeedLimit)
	--[[---------------------]]
	StartedFlag = true;
	print(string.format("setup() Complete at %.2f", RunTime()));
	return true;		-- false=setup failed, true=setup succeeded
	end;
	
	--[[---------------------------------------------------------------------------------------]]
function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
-- This function is called each time a tag programmed with 32 bytes of data is detected (TAGDATABLOCK4)
-- TagPacket contains 8 characters (4 hex digits) of UID and 32 characters
-- of block 4 data from the programmed tag.

local CurrentBlockNo, NextStationNo;

CurrentBlockNo = nil;
NextStationNo = nil;

BumpCounter(COUNTER01);	
local PacketLen = #TagPacket;

if (Debug() >= 8) then print("Packet Length = ", PacketLen); end;
if (Debug() >= 7) then print(TagPacket); end;

-- index the table Tags[] by tag ID, value is the tag packet itself
Tags[string.sub(TagPacket, 1, 8)] = string.sub(TagPacket, 9, 40);	-- remove checksum and EOP
TagCount = TagCount + 1;	-- count the number of tags
if (EngineNo > 0) then	-- it's an engine
	if (EngineNo == MyEngineNo) then
		if (BeepOn) then Beep(ASTERISK); end;
		-- next line shows a different way to quote a string using brackets
		printc(clGreen, string.format([[tag(%.2f) : Detector %d Reader %d Eng#%d %s %s #%s %s]],
			RunTime(), Detector, Reader, EngineNo, GetRailroadName(Railroad), GetEngineName(CarModel), CarNumber, GetTagLocation(TagLocation)));
		else
		-- ignore engines that are not me
		return true;	-- true=continue to process tags, false=stop processing tags
		end;
	-- follow on to process the detected engine
	else	-- it's a car
	if (CarModel == CABOOSE) then
		if (BeepOn) then Beep(EXCLAMATION); end;
		printc(clBlue, string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
			RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));
		-- ignore all Caboose cars
		return true;	-- true=continue to process tags, false=stop processing tags			
		else
		-- ignore all non-Caboose cars
		return true;	-- true=continue to process tags, false=stop processing tags
		end;		
	end

	-- The complete packet received is in TagPacket 
	-- (all of the fields have already been extracted into the first parameters to tag()
	--   First 8 digits are the 4 byte Tag UID in hexidecimal
	--   Next 32 digits are the 16 bytes of Block 4 Information read from the Tag in hexidecimal
	if (Debug() >= 6) then print(string.format("Packet  %s  %s", string.sub(TagPacket, 1, 8),
		string.sub(TagPacket,9,40) )); end
		
	-- Only Engine tags get to this point	

	if (TagLocation ~= TAG_ON_FRONT_TRUCK) then
		-- ignore rear truck tags
		return true;	-- true=continue to process tags, false=stop processing tags
		end;
		
	-- at this point, we have an engine front truck tag
		
	-- Tag 3-0
	if (Detector == 3 and Reader == 0) then
		--
		if (MyDirection == WB) then
			CurrentBlockNo = MCKEES_ROCKS_BLOCK;
			NextStationNo = MCKEES_ROCKS;
		else	-- (MyDirection == EB)
			CurrentBlockNo = LIONEL_IVES_BLOCK;
			NextStationNo = nil;
		end;

	-- Tag 5-1
	elseif (Detector == 5 and Reader == 1) then

		if (MyDirection == WB) then
			CurrentBlockNo = J_L_THRU_TRACK;
			NextStationNo = nil;
		else	-- (MyDirection == EB)
			CurrentBlockNo = MCKEES_ROCKS_BLOCK;
			NextStationNo = MCKEES_ROCKS;
		end;

	-- Tag 1-0
	elseif (Detector == 1 and Reader == 0) then

		if (MyDirection == WB) then
			CurrentBlockNo = ALIQUIPPA_BLOCK;
			if (math.random(1,2) ~= 1) then
				NextStationNo = ALIQUIPPA;
			else
				NextStationNo = WESTALIQUIPPA;
			end;			
		else	-- (MyDirection == EB)
			CurrentBlockNo = J_L_PASSING_TRACK;
			NextStationNo = nil;
		end;
		
	-- Tag 6-0
	elseif (Detector == 6 and Reader == 0) then

		if (MyDirection == WB) then
			CurrentBlockNo = MONACA_BLOCK;
			NextStationNo = nil;
			-- entering tunnel, reduce volume
			if (SoundOff == false) then
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 0, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 1, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 1/2), 2, MyEngineNo, MyTIUNo);
				end;			
		else	-- (MyDirection == EB)
			CurrentBlockNo = ALIQUIPPA_BLOCK;
			if (math.random(1,2) ~= 1) then
				NextStationNo = ALIQUIPPA;
			else
				NextStationNo = WESTALIQUIPPA;
			end;			
			-- leaving tunnel, increase volume
			if (SoundOff == false) then
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 0, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 1, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, EngineSoundSave, 2, MyEngineNo, MyTIUNo);
				end;
		end;
		
	-- Tag 6-1
	elseif (Detector == 6 and Reader == 1) then

		if (MyDirection == WB) then
			CurrentBlockNo = BEAVER_FALLS_BLOCK;
			NextStationNo = nil;
			-- leaving tunnel, increase volume
			if (SoundOff == false) then
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 0, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 1, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, EngineSoundSave, 2, MyEngineNo, MyTIUNo);
				end;
		else	-- (MyDirection == EB)
			CurrentBlockNo = MONACA_BLOCK;
			NextStationNo = nil;
			-- entering tunnel, reduce volume
			if (SoundOff == false) then
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 5/6), 0, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 4/6), 1, MyEngineNo, MyTIUNo);
				SetVolume(MASTER_VOLUME, math.floor(EngineSoundSave * 1/2), 2, MyEngineNo, MyTIUNo);
				end;			
		end;

	-- Tag 4-1
	elseif (Detector == 4 and Reader == 1) then

		if (MyDirection == WB) then
			CurrentBlockNo = COLLEGE_THRU_TRACK;
			NextStationNo = nil;
		else	-- (MyDirection == EB)
			CurrentBlockNo = BEAVER_FALLS_BLOCK;
			NextStationNo = nil;
		end;

	-- Tag 2-1
	elseif (Detector == 2 and Reader == 1) then

		if (MyDirection == WB) then
			CurrentBlockNo = NEW_CASTLE_BLOCK;
			NextStationNo = NEWCASTLE;
		else	-- (MyDirection == EB)
			CurrentBlockNo = COLLEGE_STATION_TRACK;
			NextStationNo = COLLEGE;
		end;

	-- Tag 2-0
	elseif (Detector == 2 and Reader == 0) then

		if (MyDirection == WB) then
			CurrentBlockNo = STRUTHERS_THRU_TRACK;
			NextStationNo = STRUTHERS;
		else	-- (MyDirection == EB)
			CurrentBlockNo = NEW_CASTLE_BLOCK;
			NextStationNo = NEWCASTLE;
		end;

	-- Tag 4-0
	elseif (Detector == 4 and Reader == 0) then

		if (MyDirection == WB) then
			CurrentBlockNo = YOUNGSTOWN_BLOCK;
			NextStationNo = YOUNGSTOWN;
		else	-- (MyDirection == EB)
			CurrentBlockNo = STRUTHERS_STATION_TRACK;
			NextStationNo = STRUTHERS;
		end;

	-- Tag 5-0
	elseif (Detector == 5 and Reader == 0) then

		if (MyDirection == WB) then
			CurrentBlockNo = LIONEL_IVES_BLOCK;
			NextStationNo = nil;
		else	-- (MyDirection == EB)
			CurrentBlockNo = YOUNGSTOWN_BLOCK;
			NextStationNo = YOUNGSTOWN;
		end;

		end;
	if (LayoutShowing()) then
		RegisterCabSignals(NextSignal[MyDirection][CurrentBlockNo][HOMESIGNAL], NextSignal[MyDirection][CurrentBlockNo][DISTANTSIGNAL]);
		end;

	if (CurrentBlockNo ~= nil) then
		-- Adjust speed for the current block
		local BlockSpeedLimit = BlockSpeed[MyDirection][CurrentBlockNo];
		SetCounter(C3, BlockSpeedLimit);
		SetSpeed(BlockSpeedLimit, 0, MyEngineNo, MyTIUNo);	
		end;
	if (BlockStop and CurrentBlockNo ~= nil) then
		-- stop in the block after appropriate delay
		local StopDelay = StopAfterDelay(CurrentBlockNo, MyEngineNo, MyDirection, BlockSpeedLimit, BlockSpeedLimit);
		-- restart engine after appropriate delay + 10 seconds
		print(string.format("Set speed to %d after %.3f seconds", BlockSpeedLimit, StopDelay + 10))
		SetSpeed(BlockSpeedLimit, StopDelay + 10, MyEngineNo, MyTIUNo);
		end;
	if (StationStop and NextStationNo ~= nil) then
		-- stop at station after appropriate delay
		local StationStopDelay = StationStopAfterDelay(NextStationNo, MyEngineNo, MyDirection, BlockSpeedLimit, BlockSpeedLimit);
		-- restart engine after appropriate delay + 20 seconds for passengers to get off
		print(string.format("Set speed to %d after %.3f seconds", BlockSpeedLimit, StationStopDelay + 20))
		SetSpeed(BlockSpeedLimit, StationStopDelay + 20, MyEngineNo, MyTIUNo);
		end;
	
	return true;	-- true=continue to process tags, false=stop processing tags	
	end;
--[[---------------------------------------------------------------------------------------]]
function cleanup(Engine, TIU)
	-- this function is called once when the user presses the [STOP] button or the Stop() function is called
	print("All Newly Detected Tags:");
	local k, v;
	for k,v in  pairs(Tags) do		-- dump out the Tags table
		--                           . . . . . . . . . . . . . . . .
		-- print format:  6619044A  88080101030108011B02202031353536
		print(k .. "  " .. v);
		end;
	SetSpeed(0, 0, MyEngineNo, MyTIUNo);
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, StopDelay, MyEngineNo, MyTIUNo);
	print("Shutdown in Progress.....");
	--
	if (StartedFlag) then
		print("Shutting down the Engine");
		EngineShutdown(MyEngineNo, MyTIUNo);
		StartedFlag = false;
		Sleep(10);
		end;

	print(string.format("cleanup() Complete at %.2f", RunTime()));
	return true;		-- false=cleanup failed, true=cleanup succeeded
end
--[[---------------------------------------------------------------------------------------]]

function ReloadButton()
	print("Reloading layout4.lua");
	package.loaded["layout4"] = nil;	-- unload
	Layout4 = require([[layout4]]);	-- All of the details about the Layout
	return true;
end
Function11Name, Function11Label = ReloadButton, "Reload";
--[[---------------------------------------------------------------------------------------]]

function BeepOffButton()
	print("Beep Off");
	BeepOn = false;
	return true;
end
Function14Name, Function14Label = BeepOffButton, "Beep Off";
--[[---------------------------------------------------------------------------------------]]

function BeepOnButton()
	print("Beep On");
	BeepOn = true;
	return true;
end
Function15Name, Function15Label = BeepOnButton, "Beep On";
--[[---------------------------------------------------------------------------------------]]

function StopAfterDelay(StoppingBlockNo, EngineNo, direction, LastBlockSpeedLimit, BSL)
-- Calculate the stop delay based on parameters
--
-- LastBlockSpeedLimit is the speed at which the block was entered
-- BlockSpeedLimit is the speed to which the engine is being slowed before it is asked to stop
-- LastBlockSpeedLimit and BlockSpeedLimit may be equal
-- Either one may be greater than the other
--
-- Returns the stop delay in seconds and the target distance in inches
local Target_Station;
local Target_Distance;
local StopDelay;

local BlockSpeedLimit = BSL or LastBlockSpeedLimit;

Target_Station = BlockName[StoppingBlockNo];
if (EngineNo == 7 or EngineNo == 13) then
	Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, PASSENGER);	--Passenger StoppingDistance;
	else
	Target_Distance = Layout4.STOPBlock(StoppingBlockNo, direction, FREIGHT);	--Freight StoppingDistance;
	end
if (Target_Distance == nil) then
	Target_Distance = 0;
	StopDelay = 0;
	return EmergencyStop("SAD() : Target_Distance missing in Layout description");
	end
SetCounter(C4, Target_Distance);
--print(BlockName[StoppingBlockNo] .. " " .. direction .. ", Target Distance = " .. Target_Distance);
--
-- First calculate the distance from entrance of the block until the engine gets down to Intermediate Speed
-- Note: this distance may be zero if the two speeds are the same
local StoppingDistance = Layout4.STOPDistance(EngineNo, BlockSpeedLimit);
-- then calculate the extra distance caused by the engine slowing down or the sufet distance caused
-- by the engine speeding up from the speed of the last block with the speed of this block.
local StepDistance = Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit) - StoppingDistance;
print(string.format("SAD() : LastBlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - BlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - Step Distance = %.3f in",
	LastBlockSpeedLimit, Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit), BlockSpeedLimit, StoppingDistance, StepDistance));
-- Subtract these two values from the target distance and calcuate the Stop Delay
--
-- Equation for Stop Delay calculation
-- Time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr / (12 in/ft * 5280 ft/mile * X Smiles/hr)
StopDelay =  (Target_Distance - StoppingDistance - StepDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
-- if the StopDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decell rate very high and then
-- set the speed to zero.
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
--
print(string.format("SAD() : %s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
	BlockName[StoppingBlockNo], Target_Distance, StoppingDistance, BlockSpeedLimit, StopDelay));
if (StopDelay < 0) then
	StopDelay = 0;
	return EmergencyStop("SAD() : Engine cannot stop in time (Stopping Distance > Target_Distance)");
	end;
-- Stop delay any positive value including 0
if (StopDelay == 0) then
	Rate(8, 8, StopDelay, EngineNo, MyTIUNo);	-- stop as quickly as possible
	end;
LastSentSpeed = SetSpeed(0, StopDelay, EngineNo, MyTIUNo);	-- stop engine;
if (StopDelay == 0) then
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, StopDelay, EngineNo, MyTIUNo);
	end;
print(string.format("SAD() : Set speed to 0 after %.3f seconds", StopDelay))
return StopDelay, Target_Distance;
end;
--[[---------------------------------------------------------------------------------------]]

function StationStopAfterDelay(StoppingStationNo, EngineNo, direction, LastBlockSpeedLimit, BSL)
-- Calculate the stop delay based on parameters
-- Returns the stop delay in seconds and the target distance in inches
local Target_Station;
local Target_Distance;
local StopDelay;

local BlockSpeedLimit = BSL or LastBlockSpeedLimit;

Target_Station = StationNames[StoppingStationNo];
if (EngineNo == 7 or EngineNo == 13) then
	Target_Distance = Layout4.STOPStation(StoppingStationNo, direction, PASSENGER);	--Passenger StoppingDistance;
	else
	Target_Distance = Layout4.STOPStation(StoppingStationNo, direction, FREIGHT);	--Freight StoppingDistance;
	end
if (Target_Distance == nil) then
	Target_Distance = 0;
	StopDelay = 0;
	printc(clRed, "SSAD() : Target_Distance missing in Layout description");
	return StopDelay, Target_Distance;
	end
--[[ OLD
SetCounter(C4, Target_Distance);
--print(StationNames[StoppingStationNo] .. " " .. direction .. ", Target Distance = " .. Target_Distance);
-- equation for Stop Delay calculation
-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
StoppingDistance = Layout4.STOPDistance(EngineNo, BlockSpeedLimit);
StopDelay =  (Target_Distance - StoppingDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
-- if the StopDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decell rate very high and then
-- set the speed to zero.
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
--]]
-- New
SetCounter(C4, Target_Distance);
--print(BlockName[StoppingBlockNo] .. " " .. direction .. ", Target Distance = " .. Target_Distance);
--
-- First calculate the distance from entrance of the block until the engine gets down to Intermediate Speed
-- Note: this distance may be zero if the two speeds are the same
local StoppingDistance = Layout4.STOPDistance(EngineNo, BlockSpeedLimit);
-- then calculate the extra distance caused by the engine slowing down or the sufet distance caused
-- by the engine speeding up from the speed of the last block with the speed of this block.
local StepDistance = Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit) - StoppingDistance;
print(string.format("SAD() : LastBlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - BlockSpeedLimit = %d Smph - Stopping Distance = %.3f in - Step Distance = %.3f in",
	LastBlockSpeedLimit, Layout4.STOPDistance(EngineNo, LastBlockSpeedLimit), BlockSpeedLimit, StoppingDistance, StepDistance));
-- Subtract these two values from the target distance and calcuate the Stop Delay
--
-- Equation for Stop Delay calculation
-- Time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr / (12 in/ft * 5280 ft/mile * X Smiles/hr)
StopDelay =  (Target_Distance - StoppingDistance - StepDistance) * (48 / (12 * 5280 * BlockSpeedLimit)) * 3600;
-- if the StopDelay value comes out negative, that means we have to stop
-- very quickly. If that happens, set the Decell rate very high and then
-- set the speed to zero.
SetCounter(C5, StoppingDistance);
SetCounter(C6, StopDelay);
--
if (StopDelay < 0) then
	StopDelay = 0;
	printc(clRed, "SSAD() : Engine cannot stop in time (Stopping Distance > Target_Distance)");
	return StopDelay, Target_Distance;
	end;
print(string.format("SSAD() : %s : Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
	StationNames[StoppingStationNo], Target_Distance, StoppingDistance, LastBlockSpeedLimit, StopDelay));
-- Stop delay any positive value including 0
if (StopDelay == 0) then
	Rate(8, 8, 0, MyEngineNo, MyTIUNo);	-- stop as quickly as possible
	end;
SetSpeed(0, StopDelay, MyEngineNo, MyTIUNo);	-- stop engine;
if (StopDelay == 0) then
	Rate(Layout4.AccDecRate, Layout4.AccDecRate, 0, MyEngineNo, MyTIUNo);
	end;
print(string.format("SSAD() : Set speed to 0 after %.3f seconds", StopDelay))
return StopDelay, Target_Distance;
end;
--[[---------------------------------------------------------------------------------------]]
function SoundOffButton(Eng, TIU)
print("Sound Off");
if (SoundOff == false) then
	-- read up current sound volume
	EngineSoundSave = GetVolume(MASTER_VOLUME, MyEngineNo, MyTIUNo);	-- get Volume
	print(" Sound Off - Engine " .. MyEngineNo .. " Level was " .. EngineSoundSave);
	SoundOff = true;
	SetVolume(MASTER_VOLUME, 0, 3, MyEngineNo, MyTIUNo);
	end;
return true;
end
Function19Name, Function19Label = SoundOffButton, "Sound Off";
--[[---------------------------------------------------------------------------------------]]
function SoundOnButton(Eng, TIU)
print("Sound On");
if (SoundOff == true) then
	print(" Sound On - Engine " .. MyEngineNo .. " Level restored to " .. EngineSoundSave);
	SoundOff = false;
	SetVolume(MASTER_VOLUME, EngineSoundSave, 3, MyEngineNo, MyTIUNo);
	end;
return true;
end
Function20Name, Function20Label = SoundOnButton, "Sound On";
--[[---------------------------------------------------------------------------------------]]

function StationStopOnButton()
	print("Station Stop");
	StationStop = true;
	BlockStop = false;
	return true;
end
Function01Name, Function01Label = StationStopOnButton, "Station Stop";
--[[---------------------------------------------------------------------------------------]]

function BlockStopOnButton()
	print("Block Stop");
	StationStop = false;
	BlockStop = true;
	return true;
end
Function02Name, Function02Label = BlockStopOnButton, "Block Stop";
--[[---------------------------------------------------------------------------------------]]
