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

© Copyright 2020 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 = "Run To Station"
require([[defines]]);
require([[functions]]);

-- Counter Labels
Counter01Label = "Engine Starts";
C1 = COUNTER01;
Counter02Label = "State";
C2 = COUNTER02;
Counter03Label = "#Tags";
C3 = COUNTER03;
Counter04Label = "Target Distance";
C4 = COUNTER04;
Counter05Label = "C5";
C5 = COUNTER05;
Counter06Label = "C6";
C6 = COUNTER06;
-- These labels appear on each of the tabs for the Function Buttons
Tab01Label = "Stations";
Tab02Label = "Yards";
Tab03Label = "Control";
-- table to hold all of the tags detected in sequence
Tags = {};
TagCount = 0;
local Layout4;
-- starting time
DTOStart = 0.0;
--
-- Siding entrance test
--
local STOPTIME = 15;
local STARTTIME = 10;
BeepOn = false;
SoundOff = false;
SPEEDINCREMENT = 10;

-- state machine states
IDLE = 99;
DONE = 100;
S0 = 0;
S1 = 1;
S2 = 2;
S3 = 3;
S4 = 4;
S5 = 5;
S6 = 6;
S7 = 7;
S8 = 8;
-- starting state
SM = IDLE;
StartRun = false;
BASESPEED = 15;
MyDirection = WB;
MyDest = 0;

-- Instrumented Layout 4
Layout4 = require([[Layout4]]);		-- All of the details about the Layout

--[[---------------------------------------------------------------------------------------]]
function setup(Engine, TIU)
	print(os.date("setup() %d %b %Y   %X"));
	-- 
	MyTIUNo = TIU;
	MyEngineNo = Engine;
	--
	print("Layout : " .. Layout4.name);
	print("setup() : TIU#" .. MyTIUNo .. " Engine#" .. MyEngineNo .. " Debug " .. Debug());
	print(string.format("Run Time = %.2f Seconds", RunTime()));

	--Resp = InputQuery("Run to Station", "Enter Engine Number", MyEngineNo);
	--if (Resp == false or Resp == nil) then	-- user pressed [Cancel]
	--	return false;	-- false=setup failed, true=setup succeeded
	--	end;
	--Enum = tonumber(Resp);
	--if (Enum ~= nil) then	-- if the result is a number, then use it, otherwise, ignore it
	--	MyEngineNo = Enum;
	--	end
	print([[Run this script with "Dispatcher - RunTo.lua"]]);
	SM = IDLE;
	SetCounter(C2, IDLE);	
	print("Dispatcher send out Clearance Card A to Engine Window #", PCNo());

return true;
end
--[[---------------------------------------------------------------------------------------]]

function tag(Detector, Reader, EngineNo, TagLocation, CarModel, Railroad, CarNumber, TagPacket)
	-- this function is called each time a tag is detected
	if (EngineNo ~= MyEngineNo) then return true; end;
	if (Debug() and BeepOn) then Beep(); end;	-- sound a tone
	-- Line below shows a different way to quote a string using brackets
	if (EngineNo > 0) then	-- it's an engine
		print(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	-- it's a car
		print(string.format([[tag(%.2f) : Detector %d Reader %d Car %s %s #%s]],
			RunTime(), Detector, Reader, GetRailroadName(Railroad), GetCarName(CarModel), CarNumber));			
		end

	-- The complete packet received is in TagPacket 
	-- (some of the most used fields have already been extracted into the first parameters to tag()
	--   First 8 digits are the 4 byte Tag UID in hexadecimal
	--   Next 32 digits are the 16 bytes of Block 4 Information read from the Tag in hexadecimal
	--   Final 4 digits are the checksum and End of Packet character
	if (Debug() > 3) then print(string.format("Packet  %s", string.sub(TagPacket, 1, 40))); 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
	BumpCounter(C3);		-- display the number of tags
	
--[[---------------------------------------------------------------------------------------]]
-- STATE MACHINE
--[[---------------------------------------------------------------------------------------]]	
if (SM == IDLE) then	-- do nothing 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == DONE) then	-- finish up
	SetCounter(C2, IDLE);
	SM = IDLE;	-- go idle
	print("Almost Done...");
	return Stop("DONE");
	end;
	
if (SM == S0) then
	-- run forward until the engine tag is detected over the Detector/Reader
	if (isEngine(EngineNo) and isDetector(Detector, Target_Detector) and isReader(Reader, Target_Reader)) then
		if (isTagFront(TagLocation)) then
			-- set stopping time delay to calculated value
			if (GetDirection(EngineNo, MyTIUNo) == FORWARD) then
				--print("S0 : Tag Detected in Forward");
				-- stop the engine
				-- time(seconds) = d(in) * 48 Smiles/mile * 3600 sec/hr * / (12 in/ft * 5280 ft/mile * X Smiles/hr)
				if (Target_Distance > 0) then
					StopDelay =  (Target_Distance - Layout4.STOPDistance(EngineNo, EngSpeed)) * (48 / (12 * 5280 * EngSpeed)) * 3600;
					else
					StopDelay = 0;
					end
				StopTime = Target_Distance  * (48 / (12 * 5280 * EngSpeed)) * 3600;
				print(string.format("Target Distance = %d in - Stopping Distance = %.3f in - Speed %d Smph = Stop Delay %.3f seconds",
					Target_Distance, Layout4.STOPDistance(EngineNo, EngSpeed), EngSpeed, StopDelay));
				EngSpeed = 0;
				print("S0 : Set speed to " .. EngSpeed .. " after " .. StopDelay .. " seconds")
				Throttle(EngSpeed, StopDelay, EngineNo, MyTIUNo); -- set Speed to EngSpeed
				StopForwardScheduled = true;
				-- stay in S0 to check for rear tag detection
				return true;
				end;
			end;

		if (isTagRear(TagLocation)) then
			if (not StopForwardScheduled) then return Stop("S0 : Engine Front tag not detected"); end
			StopForwardScheduled = false;
							
			-- wait for engine to stop
			StopCount = StopTime * 2;		-- must stop in expected time
			while(1) do
				Spd = GetSpeed(EngineNo, MyTIUNo);
				if (Spd ~= nil and Spd == 0) then
					SetEvent(1, ONSHEET, MyDest, EngineNo, os.time());	-- On Sheet
					break;
					end;
				if (StopCount  == 0) then
					return Stop("S0 : Engine Did not Stop");
					end;
					
				Sleep(1);
				StopCount = StopCount - 1;
				end
					
			SetCounter(C2, IDLE);	-- go to Idle			
			SM = IDLE
			StartRun = false;			
			return true;
			end			
		end;
	-- Stay in S0
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;

	
if (SM == S1) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S2) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S3) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S4) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S5) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S6) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S7) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	end;
	
if (SM == S8) then
	SM = DONE;		-- TBD End the script 
	return true;	-- false=stop calling loop(), true=continue to call loop()
	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
print(os.date("cleanup()  %d %b %Y   %X"));
print("cleanup() : TIU#" .. TIU .. " Engine#" .. MyEngineNo .. " Debug " .. Debug());
StartRun = false;
Throttle(0, 0, MyEngineNo, TIU);
DTOFinish = DTO(MyEngineNo, TIU);
-- wait for dispatch list to empty
if (CountDispatchList() > 0) then
	ClearDispatchList();
	end;
print(string.format("Run Time = %.2f seconds", RunTime()));
print(string.format("Trip Odometer Finish %.2f Smiles", DTOFinish));
ShowTags(MyEngineNo, TIU);		-- dump out the Tags table	
return true;		-- false=cleanup failed, true=cleanup succeeded
end;
--[[---------------------------------------------------------------------------------------]]
function RunToStation(Station, Dir)
-- aim for the this Station
Target_Station = StationNames[Station];
Target_Detector = Layout4.STOPBlock(Station, Dir, DETECTOR);
Target_Reader = Layout4.STOPBlock(Station, Dir, READER);
if (MyEngineNo == 7 or MyEngineNo == 13) then
	Target_Distance = Layout4.STOPBlock(Station, Dir, PASSENGER);	--Passenger StoppingDistance;
	else
	Target_Distance = Layout4.STOPBlock(Station, Dir, FREIGHT);	--Freight StoppingDistance;
	end
SetCounter(C4, Target_Distance);
print(Target_Station .. " Target Distance = " .. Target_Distance);
StartTheEngine();
return true;
end;
--[[---------------------------------------------------------------------------------------]]
function StartTheEngine()
	if (StartRun) then 	
		print(string.format("Run Time = %.2f Seconds", RunTime()));
		SetCounter(C2, S0);	
		SM = S0;		-- go wait for tag	
		return true; 
		end;	-- engine already running
		
	StartRun = true;
	-- start engine
	BumpCounter(C1);
	SetDirection(FORWARD, 0, MyEngineNo, MyTIUNo);
	EngSpeed = math.random(BASESPEED, BASESPEED + 15);
	print("StartTheEngine : Set speed to " .. EngSpeed);
	Throttle(EngSpeed, 0, MyEngineNo, MyTIUNo); -- set Speed to EngSpeed
	BumpCounter(C1);	-- Bump number of engine starts
	-- check that the engine has reached speed
	local StartTime = STARTTIME * 2;
	while (GetSpeed(MyEngineNo, MyTIUNo) ~= EngSpeed) do 
		if (StartTime == 0) then
			return EmergencyStop("StartTheEngine : Engine#" .. MyEngineNo .. " Did not Start");		-- failure
			end
		Sleep(2); 
		StartTime = StartTime - 2;
	end;
	print(string.format("Run Time = %.2f Seconds", RunTime()));
	SetCounter(C2, S0);	
	SM = S0;		-- go wait for tag
end;
--[[---------------------------------------------------------------------------------------]]
--"Destination" Buttons
--[[---------------------------------------------------------------------------------------]]
function TAliquippa()
RunToStation(ALIQUIPPA, MyDirection);
return true;
end;
Function01Name, Function01Label = TAliquippa, Layout4.StationName(ALIQUIPPA);
--[[---------------------------------------------------------------------------------------]]
function TCollege()
RunToStation(COLLEGE, MyDirection);
return true;
end;
Function02Name, Function02Label = TCollege, Layout4.StationName(COLLEGE);
--[[---------------------------------------------------------------------------------------]]
function TStruthers()
RunToStation(STRUTHERS, MyDirection);
return true;
end;
Function03Name, Function03Label = TStruthers, Layout4.StationName(STRUTHERS);
--[[---------------------------------------------------------------------------------------]]
function TMCKEES_ROCKS()
RunToStation(MCKEES_ROCKS, MyDirection);
return true;
end;
Function04Name, Function04Label = TMCKEES_ROCKS, Layout4.StationName(MCKEES_ROCKS);
--[[---------------------------------------------------------------------------------------]]
function TNEWCASTLE()
RunToStation(NEWCASTLE, MyDirection);
return true;
end;
Function05Name, Function05Label = TNEWCASTLE, Layout4.StationName(NEWCASTLE);
--[[---------------------------------------------------------------------------------------]]
function TWEST_ALIQUIPPA()
RunToStation(WEST_ALIQUIPPA, MyDirection);
return true;
end;
Function06Name, Function06Label = TWEST_ALIQUIPPA, Layout4.StationName(WEST_ALIQUIPPA);
--[[---------------------------------------------------------------------------------------]]
function TYOUNGSTOWN()
RunToStation(YOUNGSTOWN, MyDirection);
return true;
end;
Function07Name, Function07Label = TYOUNGSTOWN, Layout4.StationName(YOUNGSTOWN);
--[[---------------------------------------------------------------------------------------]]
--"Yards" & "Distance" Buttons
--[[---------------------------------------------------------------------------------------]]
function TMCKEES_ROCKS_REPAIR_SHOPS_YARD1()
RunToStation(MCKEES_ROCKS_REPAIR_SHOPS_YARD1, MyDirection);
return true;
end;
Function11Name, Function11Label = TMCKEES_ROCKS_REPAIR_SHOPS_YARD1, Layout4.StationName(MCKEES_ROCKS_REPAIR_SHOPS_YARD1);
--[[---------------------------------------------------------------------------------------]]
function TMCKEES_ROCKS_REPAIR_SHOPS_YARD2()
RunToStation(MCKEES_ROCKS_REPAIR_SHOPS_YARD2, MyDirection);
return true;
end;
Function12Name, Function12Label = TMCKEES_ROCKS_REPAIR_SHOPS_YARD2, Layout4.StationName(MCKEES_ROCKS_REPAIR_SHOPS_YARD2);
--[[---------------------------------------------------------------------------------------]]
function TGATEWAY_YARD1()
RunToStation(GATEWAY_YARD1, MyDirection);
return true;
end;
Function13Name, Function13Label = TGATEWAY_YARD1, Layout4.StationName(GATEWAY_YARD1);
--[[---------------------------------------------------------------------------------------]]
function TGATEWAY_YARD2()
RunToStation(GATEWAY_YARD2, MyDirection);
return true;
end;
Function14Name, Function14Label = TGATEWAY_YARD2, Layout4.StationName(GATEWAY_YARD2);
--[[---------------------------------------------------------------------------------------]]
--"Control" Buttons
--[[---------------------------------------------------------------------------------------]]
function SoundOffButton(Eng, TIU)
	print("Sound Off Button()");
	S1 = GetVolume(MASTER_VOLUME, Eng, TIU);	-- get Volume
	print(" Sound Off - Engine " .. Eng .. " Level was " .. S1);
	EngineSoundSave = S1;
	SoundOff = true;
	SetVolume(MASTER_VOLUME, 0, 0, Eng, TIU);
	return true;
end;
Function21Name, Function21Label = SoundOffButton, "Sound Off";
--[[---------------------------------------------------------------------------------------]]
function SoundOnButton(Eng, TIU)
	print("Sound On Button()");
	if (SoundOff) then
		S1 = EngineSoundSave;		
		print(" Sound On - Engine " .. Eng .. " Level restored to " .. S1);
		SoundOff = false;
		SetVolume(MASTER_VOLUME, S1, 0, Eng, TIU);
		end;
	return true;
end;
Function22Name, Function22Label = SoundOnButton, "Sound On";
--[[---------------------------------------------------------------------------------------]]
function ShowTags(Eng, TIU)
	print("Tags detected:");
	--if (#Tags == 0) then
	--	print ("none");
	--	end
	for k,v in  pairs(Tags) do		-- dump out the Tags table
		print(k .. "  " .. v);
		end
	return true;
end;
Function23Name, Function23Label = ShowTags, "Show Tags";
--[[---------------------------------------------------------------------------------------]]
function IncTarget(Eng, TIU)
	Target_Distance = Target_Distance + 1;
	print("Target Distance = " .. Target_Distance);
	SetCounter(C4, Target_Distance);
	return true;
end;
Function26Name, Function26Label = IncTarget, "Target +1";
--[[---------------------------------------------------------------------------------------]]
function DecTarget(Eng, TIU)
	Target_Distance = Target_Distance -1;
	print("Target Distance = " .. Target_Distance);
	SetCounter(C4, Target_Distance);
	return true;
end;
Function27Name, Function27Label = DecTarget, "Target -1";
--[[---------------------------------------------------------------------------------------]]
function WBTarget(Eng, TIU)
	MyDirection = WB;
	print("Direction = Westbound");
	return true;
end;
Function28Name, Function28Label = WBTarget, "Westbound";
--[[---------------------------------------------------------------------------------------]]
function EBTarget(Eng, TIU)
	MyDirection = EB;
	print("Direction = Eastbound");
	return true;
end;
Function29Name, Function29Label = EBTarget, "Eastbound";
--[[---------------------------------------------------------------------------------------]]
function event(FromPC, EventType, P1, P2, P3)
	-- receive an event from the dispatcher and setup the engine in use
	-- and the destination

	MyEventType = EventType;
	print("event() from ", FromPC, " - Type : ", MyEventType, " P1 = ", P1, " P2 = ", P2, " P3 = ", P3);

	if (MyEventType == NOEVENT) then
		print("event : Non Event Received");
		return true
		end;
	if (MyEventType == STARTUPENGINE) then
		MyEngineNo = P1;
		Name, PS = GetName(MyEngineNo);
		print("event : Engine#" .. MyEngineNo .. " " .. Name .. " PS" .. PS);
		Title(title .. " - " .. Name, MyEngineNo);
		-- TODO if engine is already running, then skip this startup code
		Smoke(OFF, 0, MyEngineNo, MyTIUNo);
		StartUp(0, MyEngineNo, MyTIUNo);	-- start selected engine
		-- let engine startup
		Sleep(15);
		-- set acceleration and decelleration values
		Rate(1, 1, 0, MyEngineNo, MyTIUNo);
		print("event : Set acceleration/decelleration rate to 1");
		StopForwardScheduled = false;
		StopReverseScheduled = false;
		print(string.format("Run Time = %.2f Seconds", RunTime()));
		return true;
		end;
	if (MyEventType == SHUTDOWNENGINE) then
		MyEngineNo = P1;
		ShutDown(0, MyEngineNo, MyTIUNo);
		Sleep(5);
		if (SoundOff) then
			SoundOnButton(MyEngineNo, MyTIUNo)
			end;
		return true;
		end;
		
	if (MyEventType == RUNTOEVENT) then
		MyDest = P1;
		MyDirection = P2;
		SM = IDLE;
		SetCounter(C2, IDLE);
		
		DTOStart = DTO(MyEngineNo, MyTIUNo);
		print(string.format("Trip Odometer Start %.2f Smiles", DTOStart));
		print(string.format("Run Time = %.2f Seconds", RunTime()));

		math.randomseed(os.time());
		RunToStation(MyDest, MyDirection);

		return true;
		end;
return true;	-- unknown event, ignore it
end;
--[[---------------------------------------------------------------------------------------]]




