//---------------------------------------------------------------------------
/*
Remote Train Control Program for Windows

© Copyright 2019 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
//---------------------------------------------------------------------------
/*
Handles the dispatch list between the main VCL thread and the communications thread.

When commands are added to the end of the dispatch list, the program does not wait for
a response. So this is useful only for send and forget commands like scheduling
a bell off command for 10 seconds in the future. Expected byte count must be 11.

The dispatch list works only with commands that can be recorded (RECORD_OK)

Handles LashUps.

*/
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#pragma package(smart_init)

#include "RTCMegaMaster.h"		// Dispatch List

#include "Message_Thread.h"
#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Timer_Thread.h"
#include "PC_Thread.h"
#include "Prog_Control.h"
#include "Tserial_event.h"
#include "Comm_Thread.h"
#include "Volume_Control.h"
#include "AIU_Control.h"
#include "DL_Thread.h"
#include "Debug_Controls.h"

#include <algorithm>	// STL

// Rocksoft™ Model CRC Algorithm
#include "crcmodel.h"

extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern TDispatch_List_Thread * ptrDL_Thread;		// pointer to Dispatch List Thread

extern TOp * ptrOp[ABSOLUTEMAX];   		// Operation Control Pointers - indexed by actual engine number because 0 is a factory reset engine
extern TVolume * ptrVolume[ABSOLUTEMAX];	// Volume Control Pointers - indexed by actual engine number because 0 is a factory reset engine
extern TAIUControl * ptrAIUControl[TIUMAX][AIUMAX];	// AIU Control Pointers for 25 AIU - indexed by TIUNo-1 and AIUNo-1
extern TProgram_Control *ptrProgram_Control[PCMAX];	// pointer(s) to Program Control Threads

static String DStrings[] = DSTRINGS;

//---------------------------------------------------------------------------
//   Important: Methods and properties of objects in VCL can only be
//   used in a method called using Synchronize, for example:
//
//      Synchronize(UpdateCaption);
//
//   where UpdateCaption could look like:
//
//      void __fastcall TDispatch_List_Thread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TDispatch_List_Thread::TDispatch_List_Thread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}

//---------------------------------------------------------------------------
__fastcall TDispatch_List_Thread::TDispatch_List_Thread(bool CreateSuspended, TForm * Callingthis,
		 HWND Hndl, int Remote)
	: TThread(CreateSuspended)
{

// grab the Remote & TIU Numbers
MyRemoteNo = Remote;
assert(MyRemoteNo >= 0 && MyRemoteNo <= REMOTEMAX);

MyCallingthis = Callingthis;
MyHndl = Hndl;

DL_RunTime = 0;

Finished = false;
// FreeOnTerminate is set so that parent does not have to worry about it
FreeOnTerminate = true;

// termination
OnTerminate = &RunOnTerminate;

DispatchListCount = 0;	// start Dispatch List empty
}
//---------------------------------------------------------------------------

__fastcall TDispatch_List_Thread::~TDispatch_List_Thread()
{
//
if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TDispatch_List_Thread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TDispatch_List_Thread::Execute()
{

if (DEBUGGINGX(1)) {
	// print the ThreadId
    ThreadID = GetCurrentThreadId();
    ptrTIUCommThread->PrintItem("TDispatch_List_Thread:ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
// Note that Dispatch List will not work with commands that return data. Those commands must
// be sent directly.

// this does not seem to work in BCB4.0 and Thread view not available in CB10.1
// it does work on BCB6.0
// http://docwiki.embarcadero.com/RADStudio/Rio/en/Converting_an_Unnamed_Thread_to_a_Named_Thread
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = "DL_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
    RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
        (DWORD*)&info);
    }
__except (EXCEPTION_CONTINUE_EXECUTION) {
    }

// DL_FIFO - Thread safe
DL_FIFO = new TThreadList();	// - Thread Safe
assert(DL_FIFO);				// check for successful creation
TList *myList = DL_FIFO->LockList();
try {
	myList->Capacity = 100;
    }
__finally {
	DL_FIFO->UnlockList();
    }

// https://docs.microsoft.com/en-us/windows/desktop/sync/using-waitable-timer-objects

LARGE_INTEGER liDueTime;
liDueTime.QuadPart = 0L;

// Create an unnamed waitable timer.
HANDLE MyTimer = CreateWaitableTimer(NULL, false, NULL);	// Autoreset --> the timer is a synchronization timer
assert(MyTimer);	// Check if CreateWaitableTimer failed

HANDLE handles[] = { MyTimer, MainForm->DispatchListReqEvent, MainForm->TerminationDLEvent };
DWORD dwretv;

// start the waitable Timer for 100 milliseconds and continue on if it starts up OK.
bool bretv = SetWaitableTimer(MyTimer, &liDueTime, 100, NULL, NULL, false);
if (bretv) {
    // Timer is running OK
    while(!Terminated) {
		// check the FIFO to see if anything is there - alerted by DispatchListReqEvent
        dwretv = WaitForMultipleObjects(3, handles, FALSE, INFINITE);
        //------------------------  Termination Event -----------------------------------
        if (dwretv == WAIT_OBJECT_0 + 2 || dwretv == WAIT_FAILED) {	// TerminationDLEvent Event Signaled or unknown failure?
			if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TDispatch_List_Thread: TerminationDLEvent");
            // clear out the dispatch list
			// remove items from list until the count == 0
			while(DispatchListCount) {
				if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TDispatch_List_Thread: Removing Item from DL on terminate event");
				RemoveFromDispatchList(0U);	// remove the first item
				}
	        Terminate();
            break;	// time to terminate
            }
        if (Terminated) {;
            break;				// in case we are terminated
            }
        //------------------------  Timer Event -----------------------------------
        //
        // call Dispatch_Command on each timer timeout if the dispatch list is not empty
        //
        if (dwretv == WAIT_OBJECT_0 + 0) {	// MyTimer Event Signaled ?
            if (DispatchListCount != 0) {	// Is the dispatch list non-empty?
	            Dispatch_Command();		// Is there a command ready to dispatch?
                }
            DL_RunTime += 100;    // Run Time ticks for 100 milliseconds
            // once we dispatch command(s), continue on to see if there are more commands
            // in the DL_FIFO to add to the Dispatch List (or execute now)
            }
        //------------------------  Dispatch List Request Event -----------------------------------
        // Remaining event is the DispatchListReqEvent
        if (dwretv != WAIT_OBJECT_0 + 1) continue;

 		// Check if there are commands in the thread safe DL_FIFO.
		// If there are, call AddToDispatchList() to add them to the Dispatch List.
		// If not, just loop back to the WaitForMultipleObjects() call.
        struct DispatchItem *MyDispatchItem = NULL;
        while(!Terminated) {
	 		TList *myList = DL_FIFO->LockList();
			if (myList->Count > 0) {
	 			MyDispatchItem = reinterpret_cast<struct DispatchItem *>(myList->Items[0]); // Thread Safe
				// Remove(MyDispatchItem) item pointer from DL_FIFO
                // from Help: Alternative: Delete by Index:
                // While Remove is generally preferred when you have a pointer to the item, TList also has a Delete(Index: Integer) method if you need to remove an item by its position. This also needs to be performed within a locked section.
                myList->Delete(0);
	        	//if (MyDispatchItem != NULL) DL_FIFO->Remove(MyDispatchItem);
				DL_FIFO->UnlockList();
                switch(MyDispatchItem->Type) {
                	case  CMD_Type_SendString:	// type 0 commands
                        if (MyDispatchItem->TVal > 0) {
                            //
                            // Add the command to the Dispatch List
                            //
                            if (DEBUGGINGX(3)) {
                                ptrTIUCommThread->PrintItem("Dispatch List - Add (" + String(DL_RunTime/1000.0) + "s) [" + String(MyDispatchItem->TVal/1000.0) + "s] " + String((char *)MyDispatchItem->Command), clBlue);
                                }
                           AddToDispatchList(MyDispatchItem->TVal, MyDispatchItem->ConsistNo, MyDispatchItem->TIUNo, MyDispatchItem->Command,
                                MyDispatchItem->ipFlag, MyDispatchItem->FlagValue,
                                MyDispatchItem->ExpectedCount, MyDispatchItem->WaitTime, MyDispatchItem->cLashUpEngineList);
                            } else {
                            if (DEBUGGINGX(3)) {
                                ptrTIUCommThread->PrintItem("Dispatch List - Execute Immediate (" + String(DL_RunTime/1000.0) + "s) " + String((char *)MyDispatchItem->Command), clBlue);
                                }
                            //
                            // Send the command to the Comm FIFO immediately
                            //
                            (void)ptrTIUCommThread->Send_String_FIFO(MyDispatchItem->ConsistNo, MyDispatchItem->Command, MyDispatchItem->TIUNo,
                                MyDispatchItem->Remote, MyDispatchItem->ExpectedCount,
                                MyDispatchItem->WaitTime, MyDispatchItem->Record_Flag, MyDispatchItem->cLashUpEngineList);
                            if (MyDispatchItem->ipFlag != NULL) {
                                try {
                                    // set the required flag - NOTE: the item pointed to by Flag must be int
                                    * (int*)(MyDispatchItem->ipFlag) = MyDispatchItem->FlagValue;  // cast then dereference (not the other way around)
                                    }
                                catch(...) {
                                    // this is just to catch the chance that pLItem1->Flag is non NULL but points to something that has been deallocated
                                    // If caught, just ignore it
                                    if (DEBUGGINGX(1)) {
                                        ptrTIUCommThread->PrintItem("Invalid Flag Address in TDispatch_List_Thread::Execute()", clRed);
                                        }
                                    }
                                }
                            }
                        break;
                    case CMD_Type_DelayedCommand:	// type 3 commands
                        if (MyDispatchItem->TVal > 0) {
                            //
                            // Add the Delayed command to the Dispatch List
                            //
                            if (DEBUGGINGX(3)) {
                                ptrTIUCommThread->PrintItem("Dispatch List - Add to Dispatch List (" + String(DL_RunTime/1000.0) + "s) [" + String(MyDispatchItem->TVal/1000.0) + "s] " + DStrings[MyDispatchItem->DelayedCommand] + " " +  String(MyDispatchItem->iParameter), clBlue);
                                }
                            AddToDispatchList(MyDispatchItem->CallingOp, MyDispatchItem->Parent, MyDispatchItem->DelayedCommand, MyDispatchItem->ConsistNo,
                                MyDispatchItem->TIUNo, MyDispatchItem->TVal, MyDispatchItem->iParameter);
                            } else {
                            if (DEBUGGINGX(3)) {
                                ptrTIUCommThread->PrintItem("Dispatch List - Execute Immediate (" + String(DL_RunTime/1000.0) + "s) " + DStrings[MyDispatchItem->DelayedCommand] + " " +  String(MyDispatchItem->iParameter), clBlue);
                                }
                            //
                            // Process the Delayed Command immediately since TVal == 0
                            //
						    MyCallingOpScreen = MyDispatchItem->CallingOp;
						    MyParent = MyDispatchItem->Parent;
						    MyDelayedCommand = MyDispatchItem->DelayedCommand;
						    MyConsistNo = MyDispatchItem->ConsistNo;
						    MyTIUNo = MyDispatchItem->TIUNo;
							MyTimeValue = MyDispatchItem->TVal;
							MyParameter = MyDispatchItem->iParameter;
				        	Synchronize(SimpleCommand);
							Synchronize(CleanUp);
							}
                    	break;
                    default:
                        assert(false);  // fatal error
					}
    	        // delete the item
   	    	    delete MyDispatchItem;
		        MyDispatchItem = NULL;
                // go and try to process another AddToDispatchList request
	        	} else {
                // FIFO is empty
				DL_FIFO->UnlockList();
                break;	// all commands have been added to the Dispatch List from the DL_FIFO
                }
    	    } 	// while(!Terminated)
        } 	// while(!Terminated)
    } else {	// if (bretv)
    // SetWaitableTimer failed
    assert(false);
    }
//
if (MyTimer) {
	CancelWaitableTimer(MyTimer);
	CloseHandle(MyTimer);
    MyTimer = NULL;
    }
//
if (DL_FIFO) {
	delete DL_FIFO;
    DL_FIFO = NULL;
    }
}
//---------------------------------------------------------------------------

void __fastcall TDispatch_List_Thread::Dispatch_Command(void)
{
// skip if comm thread not running
if (!ptrTIUCommThread) return;			// shutdown in progress
//
// This function is designed to be called every 100 milliseconds
// (decrements TVal by 100 and dispatches if TVal == 0)

String PrintLine;
assert(ptrDL_Thread);

if (DEBUGGINGX(10)) {
	//PrintLine = "Dispatch List - Run Time " + String(RunTime/1000.0) + "s - Start";
    PrintLine = Format((String)"Dispatch List - Run Time %.1f seconds",ARRAYOFCONST((DL_RunTime/1000.0)));
	ptrTIUCommThread->PrintItem(PrintLine, clBlue);
    }
//
//
// Check for scheduled commands
//
// Traverse the Dispatch Linked List and if the time value TVal == 0,
// dispatch the command then remove the item from the list
//

if (DispatchListCount != 0) {  // Is the dispatch list non-empty?
	// Traverse the linked list and decrement each timer value unless it is already zero
	struct DispatchItem * pLItem1 = const_cast<DispatchItem *>(first);
    assert(pLItem1 != NULL);
  	if (DEBUGGINGX(6) && ((DL_RunTime % 1000) == 0)) {
    	PrintLine = "Dispatch List (" + String(DL_RunTime/1000.0) + "s) Dispatch List Count " + String(DispatchListCount);
		ptrTIUCommThread->PrintItem(PrintLine, clBlue);
        }
    unsigned int i = 0;
    while(i < DispatchListCount){
        if (pLItem1 == NULL) {
        	if (DEBUGGING) {
				PrintLine = "Error : Dispatch List - NULL Pointer Index " + String(i) + " (" + String(DL_RunTime/1000.0) + "s)";
				ptrTIUCommThread->PrintItem(PrintLine, clRed);
    			}
            return;
            }
    	if (pLItem1->TVal > 0) pLItem1->TVal -= 100;		// decrement TVal by 100 milliseconds
      	if (pLItem1->TVal == 0) {							// and check unsigned int TVal equal to 0

    	    // Send the command
            // Note that Dispatch List will not work with commands that return data. Those commands must
            // be sent directly.
            switch(pLItem1->Type) {
            	case CMD_Type_SendString:
                case CMD_Type_WaitForResponse:
                case CMD_Type_SendHex:
                    // Standard Commands
			        if (DEBUGGINGX(3)) {
						PrintLine = "Dispatch List - Standard Command - Index " + String(i) + " (" + String(DL_RunTime/1000.0) + "s) " + String((char *)pLItem1->Command);
						ptrTIUCommThread->PrintItem(PrintLine, clBlue);
			    		}
                    (void)ptrTIUCommThread->Send_String_FIFO(pLItem1->ConsistNo, pLItem1->Command, pLItem1->TIUNo,
                        pLItem1->Remote, pLItem1->ExpectedCount,
                        pLItem1->WaitTime, pLItem1->Record_Flag, pLItem1->cLashUpEngineList);
                    if (pLItem1->ipFlag != NULL) {
                        try {
                            // set the required flag - NOTE: the item pointed to by Flag must be int
                            * (int*)(pLItem1->ipFlag) = pLItem1->FlagValue;  // cast then dereference (not the other way around)
                            }
                        catch(...) {
                            // this is just to catch the chance that pLItem1->Flag is non NULL but points to something that has been deallocated
                            // If caught, just ignore it
                            if (DEBUGGINGX(1)) {
                                ptrTIUCommThread->PrintItem("Invalid Flag Address in Dispatch_Command()", clRed);
                                }
                            }
                        }
                	break;
                case CMD_Type_DelayedCommand:
                    // Delayed Type commands
			        if (DEBUGGINGX(3)) {
						PrintLine = "Dispatch List - Delayed Command - Index " + String(i) + " (" + String(DL_RunTime/1000.0) + "s) " + DStrings[pLItem1->DelayedCommand] +
                        	" " + String(pLItem1->iParameter);
						ptrTIUCommThread->PrintItem(PrintLine, clBlue);
			    		}
                    MyCallingOpScreen = pLItem1->CallingOp;
                    MyParent = pLItem1->Parent;
                    MyDelayedCommand = pLItem1->DelayedCommand;
                    MyConsistNo = pLItem1->ConsistNo;
                    MyTIUNo = pLItem1->TIUNo;
                    MyTimeValue = pLItem1->TVal;
                    MyParameter = pLItem1->iParameter;
                    // Call Delayed Command Processor
		        	Synchronize(SimpleCommand);
					Synchronize(CleanUp);
                	break;
                default:
                    assert(false);  // fatal error
                }
            struct DispatchItem * pnxtLItem = pLItem1->nxt;
        	// remove the item from the dispatch list
	        DispatchItem * pT1 = RemoveFromDispatchList(i);
			assert(pnxtLItem == pT1);
		    // pnxtLItem will be NULL when last item is being processsed but that is OK since we don't use it in that case
    	    pLItem1 = pnxtLItem;
            if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->Dispatched_Cmd_Count);
            // see if we can dispatch another command
        	continue;
	        }
    	pLItem1 = pLItem1->nxt;
		i++;
		} // while(i < DispatchListCount);
    }
if (DEBUGGINGX(11)) {
	PrintLine = "Dispatch List - Run Time " + String(DL_RunTime/1000.0) + "s - End";
	ptrTIUCommThread->PrintItem(PrintLine, clBlue);
   	}
}
//---------------------------------------------------------------------------
// DumpLinkedList()
//
// Dump the Linked List
// returns false if the list is empty
// dumps on DEBUGLEVEL 5 or higher
//
//---------------------------------------------------------------------------
bool __fastcall TDispatch_List_Thread::DumpDispatchList(void) {
bool retv;
String PrintLine;
  if (!DEBUGGINGX(5)) {
		if (DispatchListCount != 0) return true;	// not empty
		return false;	// empty
		}
  // Dump the linked list only if debug level is 5 or greater
  TCHAR msgBuf[128]; // allocate buf from stack only as long as needed
  if (DispatchListCount != 0) {  // Is the list non-empty
 	PrintLine = "DumpDispatchList - (" + String(DL_RunTime/1000.0) + "s) Count " + String(DispatchListCount) + "  First " + String(IntToHex((int)first, 8)) + "  Last " + String(IntToHex((int)last, 8));
 	ptrTIUCommThread->PrintItem(PrintLine, clGreen); 
 	PrintLine = "   Adr     nxt        prev          Tval  Cmd";
 	ptrTIUCommThread->PrintItem(PrintLine, clGreen);	
    // Traverse the linked list and decrement each timer value
    DispatchItem * pLItem = (DispatchItem *)first;
	assert(pLItem != NULL);

    unsigned int i = 0;
    while(i < DispatchListCount) {
	  if (pLItem->DelayedCommand == 0) {
#if __BORLANDC__ >= 0x720	  
		  _sntprintf(msgBuf, sizeof msgBuf, TEXT("0x%08.8X 0x%08.8X 0x%08.8X %7d  %S"), pLItem, pLItem->nxt, pLItem->prv, pLItem->TVal, pLItem->Command);
#else
		  _sntprintf(msgBuf, sizeof msgBuf, TEXT("0x%08.8X 0x%08.8X 0x%08.8X %7d  %s"), pLItem, pLItem->nxt, pLItem->prv, pLItem->TVal, pLItem->Command);
#endif
		  } else {
		  _sntprintf(msgBuf, sizeof msgBuf, TEXT("0x%08.8X 0x%08.8X 0x%08.8X %7d  %s %d"), pLItem, pLItem->nxt, pLItem->prv, pLItem->TVal, DStrings[pLItem->DelayedCommand], pLItem->iParameter);
          }
	  ptrTIUCommThread->PrintItem(String(msgBuf), clGreen);
      pLItem = pLItem->nxt;
      i++;
      }
    retv = true;
    } else {
  	PrintLine = "DumpDispatchList - (" + String(DL_RunTime/1000.0) + "s) Dispatch List is empty";
  	ptrTIUCommThread->PrintItem(PrintLine, clGreen);	
    retv = false;
    }
return retv;
}
//---------------------------------------------------------------------------
// AddToDispatchList()
//
// Add command request to the head of the linked list
// returns true on success
//
// This routine cannot be used for commands which return data. Those commands must be sent directly to the Comm_Thread
//
// This routine can only be used for recordable commands
//
// This routine supports LashUps
//
// NOTE: the item pointed to by Flag must be int or bool
//---------------------------------------------------------------------------

bool __fastcall TDispatch_List_Thread::AddToDispatchList(unsigned int TimeValue, byte iConsistNo, byte iTIUNo,
	char * Command, int *Flag, int FlagValue, int Expected, int WaitTime, byte * ptrLashUpList) {
//
// "TimeValue" time value in milliseconds
//
assert (Command != NULL);          					 // invalid - can't process
assert (strlen(Command) <= (CMDMAX - 1)); // too long - can't process
assert (TimeValue == 0 || TimeValue >= 100);

TimeValue = (TimeValue + 50) / 100 * 100; 			// Round to nearest 100
//	TimeValue is decrement with " -= 100;" and tested for zero so it must always be a multiple of 100

String PrintLine;
//
// always schedule the command even if when == 0
//
DispatchListAddInProgress = true;
struct DispatchItem * pLItem;
assert (sizeof (struct DispatchItem) > 8);
pLItem = new struct DispatchItem;	// deleted in RemoveFromDispatchList()
assert(pLItem != NULL);
//
pLItem->Type = CMD_Type_SendString;
pLItem->TVal = TimeValue;     // Data for the command being requested
pLItem->ConsistNo = iConsistNo;
pLItem->TIUNo = iTIUNo;
strncpy(pLItem->Command, Command, CMDMAX);

pLItem->DelayedCommand = DC_NULL;
// or
//pLItem->DelayedCommand = static_cast<DCommand>(0);

pLItem->iParameter = 0;
pLItem->ipFlag = Flag;
pLItem->FlagValue = FlagValue;
pLItem->ExpectedCount = Expected;
pLItem->WaitTime = WaitTime;

pLItem->Remote = MyRemoteNo;		// Dispatch List always works with RTC's slected remote
pLItem->Record_Flag = RECORD_OK;	// Dispatch List can only work with commands that can be recorded - others must be sent directly
if ((ptrLashUpList != NULL) && (ptrLashUpList[0] != '\0')) {
	strncpy(pLItem->cLashUpEngineList, ptrLashUpList, LASHUP_ENGINE_LIST_LENGTH);
    } else {
    pLItem->cLashUpEngineList[0] = '\0';
    }

if (DispatchListCount > 0){          // If the list contains one or more items
	// add to end of list
    pLItem->nxt = (DispatchItem *) NULL;		// last item in list always has NULL in nxt
    pLItem->prv = (DispatchItem *) last;
    last->nxt = pLItem;
    first->prv = NULL;		// first item in list always has NULL in prv
    last = pLItem;			// the new item becomes the last item in the list
    DispatchListCount++;
    if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Count);
    }
    else if (DispatchListCount == 0){   //If no items are present in the list, this one becomes the one and only
        first = pLItem;			// first & last both point to the one new item
        last = pLItem;
        pLItem->nxt = (DispatchItem *) NULL; // last item in list always has NULL in nxt
        pLItem->prv = (DispatchItem *) NULL; // first item in list always has NULL in prv
        DispatchListCount = 1;
        if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Count);
	    } else assert(false);
DispatchListAddInProgress = false;
if (DEBUGGINGX(5)) {
	PrintLine = "Add @ " + String(TimeValue) + " - " + String(Command) + " 0x" + String(IntToHex((int)pLItem, 8));
	ptrTIUCommThread->PrintItem(PrintLine, clBlue);
    }
if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Total);
(void) DumpDispatchList();
return true;
}
//---------------------------------------------------------------------------
// AddToDispatchList() for type DL_Type_DelayedCommand commands
//
// Add command request to the head of the linked list
// returns true on success
//
// This routine cannot be used for commands which return data. Those commands must be sent directly to the Comm_Thread
//
// This routine can only be used for recordable commands
//
// This routine supports LashUps
//
// NOTE: the item pointed to by Flag must be int or bool
//---------------------------------------------------------------------------

bool __fastcall TDispatch_List_Thread::AddToDispatchList(TOp * CallingOp, HWND Parent, DCommand DelayedCommand,
		unsigned char iConsistNo, unsigned char iTIUNo, unsigned int TimeValue, int iParameter) {
//
// "TimeValue" time value in milliseconds
//
assert (TimeValue == 0 || TimeValue >= 100);

TimeValue = (TimeValue + 50) / 100 * 100; 			// Round to nearest 100

String PrintLine;
//
// always schedule the command even if when == 0
//
DispatchListAddInProgress = true;
struct DispatchItem * pLItem;
assert (sizeof (struct DispatchItem) > 8);
pLItem = new struct DispatchItem;	// deleted in RemoveFromDispatchList()
assert(pLItem != NULL);
//
pLItem->Type = CMD_Type_DelayedCommand;
pLItem->TVal = TimeValue;     // Data for the command being requested
pLItem->ConsistNo = iConsistNo;
pLItem->TIUNo = iTIUNo;
pLItem->Remote = MyRemoteNo;		// Dispatch List always works with RTC's selected remote
pLItem->ipFlag = NULL;
pLItem->FlagValue = 0;

// for DL_Type_DelayedCommand type commands in the Dispatch List FIFO
pLItem->CallingOp = CallingOp;			// Calling Operations window pointer
pLItem->DelayedCommand = DelayedCommand;// Delayed Command
pLItem->iParameter = iParameter;		// Parameter for some commands
pLItem->Parent = Parent;				// Handle of parent for PostMessage() and SendMessage()

if (DispatchListCount > 0){                 // If the list contains one or more items
	// add to end of list
    pLItem->nxt = (DispatchItem *) NULL;	// last item in list always has NULL in nxt
    pLItem->prv = (DispatchItem *) last;
    last->nxt = pLItem;
    first->prv = NULL;		                // first item in list always has NULL in prv
    last = pLItem;			                // the new item becomes the last (end) item in the list
    DispatchListCount++;
    if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Count);
    }
    else if (DispatchListCount == 0){           //If no items are present in the list, this one becomes the one and only
        first = pLItem;			                // first & last both point to the one new item
        last = pLItem;
        pLItem->nxt = (DispatchItem *) NULL;    // last item in list always has NULL in nxt
        pLItem->prv = (DispatchItem *) NULL;    // first item in list always has NULL in prv
        DispatchListCount = 1;
        if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Count);
	    } else assert(false);
DispatchListAddInProgress = false;
if (DEBUGGINGX(5)) {
	//PrintLine = "Add @ " + String(TimeValue) + " - " + String(DelayedCommand) + " 0x" + String(IntToHex((int)pLItem, 8));
	PrintLine = "Add @ " + String(TimeValue) + " - " + DStrings[DelayedCommand] + " " + String(iParameter) + " 0x" + String(IntToHex((int)pLItem, 8));
	ptrTIUCommThread->PrintItem(PrintLine, clBlue);
    }
if (DEBUGGING) ptrTIUCommThread->Bump(MainForm->DL_FIFO_Total);
(void) DumpDispatchList();
return true;
}
//---------------------------------------------------------------------------
// RemoveFromDispatchList(unsigned int index)
//
// Remove indexed command from the linked list
// returns true on sucess
//
// called only from Timer ISR or routines only called by the Timer ISR
//---------------------------------------------------------------------------

struct DispatchItem * __fastcall TDispatch_List_Thread::RemoveFromDispatchList(unsigned int index) {
assert(index < DispatchListCount);
assert(DispatchListCount != 0);
DispatchItem * pretv = NULL;
String PrintLine;

// Remove item at index from the list (index == 0 is first item)

if (DispatchListCount == 1) {	// the entire RemoveFromDispatchList() function must not be interruptable by the Message Queue
	// remove the one item in the list
    assert(first != NULL);
    delete first;	// this is one of TWO places that pLItem is deleted
    if (DEBUGGINGX(7)) {
    	PrintLine = "Remove " + String(index) + " " + String(IntToHex((int)first, 8));
		ptrTIUCommThread->PrintItem(PrintLine, clBlue);
        }
    first = (DispatchItem *) NULL;
    last = (DispatchItem *) NULL;
    pretv = (DispatchItem *) NULL;
    DispatchListCount = 0;
    //if (DEBUGGING) ptrTIUCommThread->Dec(MainForm->DL_FIFO_Count);
    if (DEBUGGING) ptrTIUCommThread->ClearCounter(MainForm->DL_FIFO_Count);
    } else {
    struct DispatchItem * pLItem = (DispatchItem *)first;
    unsigned int i = 0;
    while (i < index) {
	  assert(pLItem != (DispatchItem *) NULL);
      pLItem = pLItem->nxt;
      i++;
      }
    if (pLItem != first) pretv = pLItem->prv->nxt = pLItem->nxt;
    if (pLItem != last) pLItem->nxt->prv = pLItem->prv;
    // if first or last is being removed, update first or last pointers
    if (pLItem == last) {
    	last = pLItem->prv;
        pLItem->nxt = (DispatchItem *) NULL;
		}
    if (pLItem == first) {
    	first = pretv = pLItem->nxt;
        pLItem->prv = (DispatchItem *) NULL;
        }
    DispatchListCount--;
    //if (DEBUGGING) ptrTIUCommThread->Dec(MainForm->DL_FIFO_Count);
    if (DEBUGGING) ptrTIUCommThread->SetCounter(MainForm->DL_FIFO_Count, DispatchListCount);
    if (DispatchListCount == 0) last = first = NULL;  // list has emptied
    assert(pLItem != (DispatchItem *) NULL);
    delete pLItem;	// this is one of TWO places that pLItem is deleted
 	if (DEBUGGINGX(7)) {
    	PrintLine = "Remove " + String(index) + " " + String(IntToHex((int)pLItem, 8));
		ptrTIUCommThread->PrintItem(PrintLine, clBlue);
        }
    }
(void) DumpDispatchList ();
return pretv;
}
//---------------------------------------------------------------------------
// ClearDispatchList()
//
// Remove all commands from the Dispatch linked list
// returns true on sucess
//
// Called from Lua function via PC_Thread
//---------------------------------------------------------------------------

bool __fastcall TDispatch_List_Thread::ClearDispatchList(void) {
String PrintLine;
if (DispatchListCount == 0) return true;  // already empty
// remove items from list until the count == 0
while(DispatchListCount) {
	if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TDispatch_List_Thread: Removing Item from DL in ClearDispatchList()");
	RemoveFromDispatchList(0U);	// remove the first item
	}
return true;
}
//---------------------------------------------------------------------------
// Send_String_DL_FIFO
// Runs in the context of the calling thread, not TDispatch_List_Thread
// "TimeValue" time value in milliseconds
// The item pointed to by ipFlag must be int or bool
//
// Note that Dispatch List will not work with commands that return data. Those commands must
// be sent directly by calling Send_String()
//
// Command_String goes into the Dispatch List and then we are done
//
// we never wait for a response event. Return true if Command_String was correctly inserted into the Dispatch List
//---------------------------------------------------------------------------

bool __fastcall TDispatch_List_Thread::Send_String_DL_FIFO(unsigned int TimeValue, unsigned char ConsistNo, byte * Command_String,
	unsigned char TIU, unsigned char Remote, int Expected, int WaitTime, bool Record_Flag, byte * ptrLashUpEngineList, int * ipFlag, int FlagValue)
{

assert (TIU >= 1 && TIU <= TIUMAX);
assert (Command_String != NULL);
assert (Remote >= 1 && Remote <= REMOTEMAX);
assert (strlen(Command_String) <= (CMDMAX - 1));	// too long - can't process
assert (TimeValue == 0 || TimeValue >= 100);

TimeValue = (TimeValue + 50) / 100 * 100; 			// Round to nearest 100

String PrintLine;

// if Dispatch List is created, use it
if (ptrDL_Thread) {	// right now, pressing the "Connect" button creates this thread
	// allocate the dispatch list structure for one command
	struct DispatchItem * pLItem;
	assert (sizeof (struct DispatchItem) > 8);
    pLItem = new struct DispatchItem;					// deleted in TDispatch_List_Thread->Execute()
	pLItem->Type = CMD_Type_SendString;					// type CMD_Type_SendString command into the list
	pLItem->TVal = TimeValue;							// execute time in milliseconds
    pLItem->ConsistNo = ConsistNo;
    pLItem->TIUNo = TIU;
    (void) strncpy(pLItem->Command, Command_String, 128);
    pLItem->Remote = Remote;
    pLItem->ipFlag = (int *) ipFlag;
    pLItem->FlagValue = FlagValue;
    pLItem->ExpectedCount = Expected;
    assert(Expected == 11);								// Expected byte count must be 11
    pLItem->WaitTime = WaitTime;
    pLItem->Record_Flag = Record_Flag;
    assert(Record_Flag == RECORD_OK);					// Record_Flag must be RECORD_OK
    pLItem->ptrRespCode = (int *) NULL;					// not used
    pLItem->ResponseCount = (int *) NULL;				// not used
    pLItem->ResponseBuffer = (byte *) NULL;				// not used
    // use the Lashup Engine List only if the Consist number is > 101
    if ((ConsistNo >= 101) && (ptrLashUpEngineList != (byte *) NULL)) {
    	(void) strncpy(pLItem->cLashUpEngineList, ptrLashUpEngineList, LASHUP_ENGINE_LIST_LENGTH);
        } else {
        pLItem->cLashUpEngineList[0] = '\0';
        }

    pLItem->nxt = NULL;
    pLItem->prv = NULL;

    // add to the end of the dispatch list
    TList *myList = ptrDL_Thread->DL_FIFO->LockList();
    assert(myList);
    ptrDL_Thread->DL_FIFO->Add(pLItem);  // Add to end of DL_FIFO
    // Signal TDispatch_List_Thread that a command is ready
    SetEvent(MainForm->DispatchListReqEvent);
    ptrDL_Thread->DL_FIFO->UnlockList();
    //
    // Command_String goes into the Dispatch List and then we are done
    // we never wait for a response event
    //
    return true;
    }
//assert(false);	// Dispatch is not active - can be a fatal error
return false;
}
//---------------------------------------------------------------------------
// Send_Delayed_DL_FIFO
// Runs in the context of the calling thread, not TDispatch_List_Thread
// "TimeValue" time value in milliseconds
//
// Uses the Operations Window pointed to by CallingOp. The DelayedCommand handler
// toggles/moves/pushes the switch/slider/button on the CallingOp window and
// the CallingOp window actually sends the command.
//
// Note that Dispatch List will not work with commands that return data. Those commands must
// be sent directly by calling Send_String()
//
// DelayedCommand goes into the Dispatch List and then we are done
//
// we never wait for a response event. Return true if DelayedCommand was correctly inserted into the Dispatch List
//---------------------------------------------------------------------------
bool __fastcall TDispatch_List_Thread::Send_Delayed_DL_FIFO(TOp * CallingOp, HWND Parent, DCommand DelayedCommand,
		unsigned char ConsistNo, unsigned char TIU, unsigned char Remote, unsigned int TimeValue, int iParameter)
{

assert (TIU >= 1 && TIU <= TIUMAX);
// code does not check CallingOp as it may be NULL
//assert (CallingOp != NULL);
assert (TimeValue == 0 || TimeValue >= 100);

TimeValue = (TimeValue + 50) / 100 * 100; 			// Round to nearest 100

String PrintLine;

// if Dispatch List is created, use it
if (ptrDL_Thread) {	// right now, pressing the "Connect" button creates this thread
	// allocate the dispatch list structure for one command
	struct DispatchItem * pLItem;
	assert (sizeof (struct DispatchItem) > 8);
    pLItem = new struct DispatchItem;					// deleted in TDispatch_List_Thread->Execute()
	pLItem->Type = CMD_Type_DelayedCommand;				// type CMD_Type_DelayedCommand command into the list
	pLItem->TVal = TimeValue;							// execute time in milliseconds
    pLItem->ConsistNo = ConsistNo;
    pLItem->TIUNo = TIU;
    pLItem->Remote = Remote;
	pLItem->ipFlag = NULL;
	pLItem->FlagValue = 0;

	// for CMD_Type_DelayedCommand type commands in the Dispatch List
    pLItem->CallingOp = CallingOp;			// Calling Operations window pointer or NULL
    pLItem->DelayedCommand = DelayedCommand;// Delayed Command
    pLItem->iParameter = iParameter;		// Parameter for some commands
    pLItem->Parent = Parent;				// Handle of parent for PostMessage() and SendMessage()

    pLItem->nxt = NULL;
    pLItem->prv = NULL;

    // add to the end of the dispatch list
    TList *myList = ptrDL_Thread->DL_FIFO->LockList();
    assert(myList);
    ptrDL_Thread->DL_FIFO->Add(pLItem);  // Add to end of DL_FIFO
    // Signal TDispatch_List_Thread that a command is ready
    SetEvent(MainForm->DispatchListReqEvent);
    ptrDL_Thread->DL_FIFO->UnlockList();
    //
    // DelayedCommand goes into the Dispatch List and then we are done
    // we never wait for a response event
    //
    return true;
    }
//assert(false);	// Dispatch is not active - can be a fatal error
return false;
}
//---------------------------------------------------------------------------

void __fastcall TDispatch_List_Thread::SimpleCommand(void)     // called using Synchronize()
{
//
// First check for switch or accessory command which do not require any Consist to be running
switch(MyDelayedCommand) {
    case DC_SWITCH_NORMAL:	// Switch Track Normal
        {
        TShiftState Shift;	// need to fake this to call function below - create empty set
        // Created by : MyParameter = (iChan - 1) + ((iAIUNo - 1) * 10);
        const int iChan = (MyParameter % 10) + 1;
        const int iAIUNo = (MyParameter/10) + 1;
        if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]) {	// if we have an AIU window, use it
            // First check that we have a named switch
            if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Cells [SW_NAME] [iChan] != "*") {
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Col = SW_NAME;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Row = iChan;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AllToggleSwitch->Checked = false;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccDataClick(NULL);	// select the switch
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->SwStraightButtonMouseDown(NULL, (TMouseButton) NULL, Shift, 0, 0);
                }
            } else {
            // Convert value into chars
            char str[15];
            (void)snprintf(str, sizeof str, SWITCH_NORMAL, MyParameter);
			(void)ptrTIUCommThread->Send_String_FIFO(MyConsistNo, str, MyTIUNo, MainForm->RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
            }
        }
        return;
	case DC_SWITCH_REVERSE:		// Switch Track Reverse
        {
        TShiftState Shift;	// need to fake this to call function below - create empty set
        // Created by : MyParameter = (iChan - 1) + ((iAIUNo - 1) * 10);
        const int iChan = (MyParameter % 10) + 1;
        const int iAIUNo = (MyParameter/10) + 1;
        if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]) {	// if we have an AIU window, use it
            // First check that we have a named switch
            if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Cells [SW_NAME] [iChan] != "*") {
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Col = SW_NAME;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Row = iChan;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AllToggleSwitch->Checked = false;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccDataClick(NULL);	// select the switch
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->SwDivergeButtonMouseDown(NULL, (TMouseButton) NULL, Shift, 0, 0);
                }
            } else {
            // Convert value into chars
            char str[15];
            (void)snprintf(str, sizeof str, SWITCH_REVERSE, MyParameter);
			(void)ptrTIUCommThread->Send_String_FIFO(MyConsistNo, str, MyTIUNo, MainForm->RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
            }
        }
        return;
    case DC_ACCESSORY_ON:		// Accessory Activation
        {
        TShiftState Shift;	// need to fake this to call function below - create empty set
        // Created by : MyParameter = (iChan - 1) + ((iAIUNo - 1) * 10);
        const int iChan = (MyParameter % 10) + 1;
        const int iAIUNo = (MyParameter/10) + 1;
        if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]) {	// if we have an AIU window, use it
            // First check that we have a named accessory
            if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Cells [ACC_NAME] [iChan] != "*") {
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Col = ACC_NAME;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Row = iChan;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AllToggleSwitch->Checked = false;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccDataClick(NULL);	// select the accessory
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccOnButtonMouseDown(NULL, (TMouseButton) NULL, Shift, 0, 0);
                }
            } else {
            // Convert value into chars
            char str[15];
            (void)snprintf(str, sizeof str, ACC_ON, MyParameter);
			(void)ptrTIUCommThread->Send_String_FIFO(MyConsistNo, str, MyTIUNo, MainForm->RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
            }
        }
        return;
    case DC_ACCESSORY_OFF:		// Accessory Deactivation
        {
        TShiftState Shift;	// need to fake this to call function below - create empty set
        // Created by : MyParameter = (iChan - 1) + ((iAIUNo - 1) * 10);
        const int iChan = (MyParameter % 10) + 1;
        const int iAIUNo = (MyParameter/10) + 1;
        if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]) {	// if we have an AIU window, use it
            // First check that we have a named accessory
            if (ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Cells [ACC_NAME] [iChan] != "*") {
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Col = ACC_NAME;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccData->Row = iChan;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AllToggleSwitch->Checked = false;
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccDataClick(NULL);	// select the accessory
                ptrAIUControl[MyTIUNo - 1][iAIUNo - 1]->AccOffButtonMouseDown(NULL, (TMouseButton) NULL, Shift, 0, 0);
                }
            } else {
            // Convert value into chars
            char str[15];
            (void)snprintf(str, sizeof str, ACC_OFF, MyParameter);
            (void)ptrTIUCommThread->Send_String_FIFO(MyConsistNo, str, MyTIUNo, MainForm->RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
            }
        }
        return;
	}

// Second, process the commands that require a running engine and an Operations window
if (MainForm->IsConsistRunning(MyConsistNo) && MyCallingOpScreen) {
	switch (MyDelayedCommand) {
       	char str[15];
		case DC_BELLOFF:
			MyCallingOpScreen->BellSwitch->Checked = false;		// send bell off
    		break;
        case DC_BELLOFF_WITH_HORN:
            if (MyCallingOpScreen->BellOnWithHornLED->Checked) {
                MyCallingOpScreen->BellSwitch->Checked = false;	// send bell off
                }
        	break;
		case DC_BELLON:
			MyCallingOpScreen->BellSwitch->Checked = true;		// send bell on
   			break;
        case DC_BELLON_WITH_HORN:
            if (MyCallingOpScreen->BellOnWithHornLED->Checked) {		// send bell on
				MyCallingOpScreen->BellSwitch->Checked = true;
                }
        	break;
        case DC_HORN_WHISTLE_ON:
	       	MyCallingOpScreen->WhistleButton->Checked = true;		// This does not start the horn, need CM_WHISTLE message
           	(void) PostMessage(MyParent, CM_WHISTLE, true, NULL);		// true = button down
            break;

        case DC_HORN_WHISTLE_OFF:
        	MyCallingOpScreen->WhistleButton->Checked = false;	// This does not end the horn, need CM_WHISTLE message
           	(void) PostMessage(MyParent, CM_WHISTLE, false, NULL);		// false = button up
            break;

		case DC_PFA_DIR:		// Toggle the DIR button for PFA if PFA is running in Radio modes
            if (MyCallingOpScreen->PFAButton->Tag) {
       	       	MyCallingOpScreen->DIRToggle->Checked = !MyCallingOpScreen->DIRToggle->Checked;
                }
		    break;
        case DC_PFA_PFA:        // Toggle the PFA button for PFA if PFA is running in WTIU mode
            if (MyCallingOpScreen->PFAButton->Tag) {
     	       	MyCallingOpScreen->PFAButton->Checked = true;
                MyCallingOpScreen->PFAButton->Checked = false;
                }
		    break;
		case DC_PFA_DONE:		// PFA done
			// The 'u0' command is sent by DIRToggleStatusChanged() after 4 DC_PFA_DIR commands
            // or
            // The 'u0' command is sent by PFAButtonClick() after 5 DC_PFA_PFA commands
    	    MyCallingOpScreen->PFAButton->Tag = false;
    	    MyCallingOpScreen->PFAButton->ColorOff = clBtnFace;
	   	    break;
        case DC_DIR:			// Toggle the DIR button
   	       	MyCallingOpScreen->DIRToggle->Checked = !MyCallingOpScreen->DIRToggle->Checked;
		    break;
        case DC_DIR_FORWARD:
            MyCallingOpScreen->DIRToggle->Checked = true;
        	break;
        case DC_DIR_REVERSE:
	        MyCallingOpScreen->DIRToggle->Checked = false;
        	break;
        case DC_SETSPEED:			// Speed to iParameter
            MyCallingOpScreen->SpeedSlider->Value = MyParameter;
        	break;
        case DC_MPH:				// MPH to iParamter
            MyCallingOpScreen->Set_Speed(MyParameter);
        	break;
        case DC_HDLT_ON:		// headlight on
			MyCallingOpScreen->HDLTSwitch->Checked = true;	// headlight on
        	break;
        case DC_HDLT_OFF:		// headlight off
			MyCallingOpScreen->HDLTSwitch->Checked = false;	// headlight off
			break;
        case DC_HDLT_OFF_IF_STOPPED:
            if (MyCallingOpScreen->SpeedSlider->Value == 0) {	   	// if engine is stopped
				MyCallingOpScreen->HDLTSwitch->Checked = false;	// headlight off
                }
        	break;
        case DC_SXS:			// Crossing Signal
            {
	        TShiftState Shift;	// need to fake this to call function below - create empty set
	        if (MyCallingOpScreen->SXSButton->Enabled)
				MyCallingOpScreen->SXSButton->OnMouseDown(NULL, mbLeft, Shift, 0, 0);	// SXS on
			}
        	break;
        //
        // DC_MARS simulations the Mars light by flashing the headlight
        // once a second. Only works if the headlight switch is on
        //
        // Generally I don't like the implementation of this function
        //
        // NO LONGER SUPPORTED
        //
		case DC_MARS_ON:		// turn on headlight only if the headlight switch is on and running forward
			//if (MyCallingOpScreen->HDLTSwitch->Checked && MyCallingOpScreen->DIRToggle->Checked) {
			//	MyCallingOpScreen->SendOneCommand("ab1");
			//	}
			break;
		case DC_MARS_OFF:		// turn off headlight only if the headlight switch is on and running forward
			//if (MyCallingOpScreen->HDLTSwitch->Checked && MyCallingOpScreen->DIRToggle->Checked) {
			//	MyCallingOpScreen->SendOneCommand("ab0");
			//	}
			break;
        //
        //
		case DC_BEACON_ON:		// turn on beacon only if the beacon switch is enabled
        	if (MyCallingOpScreen->BeaconLightSwitch->Enabled) {
                MyCallingOpScreen->BeaconLightSwitch->Checked = true;
                }
			break;
		case DC_BEACON_OFF:		// turn off beacon only if the beacon switch is enabled
        	if (MyCallingOpScreen->BeaconLightSwitch->Enabled) {
                MyCallingOpScreen->BeaconLightSwitch->Checked = false;
                }
			break;

		case DC_MARKERS_ON:		// turn on marker only if the marker switch is enabled
        	if (MyCallingOpScreen->MarkerLightSwitch->Enabled) {
                MyCallingOpScreen->MarkerLightSwitch->Checked = true;
                }
			break;
		case DC_MARKERS_OFF:		// turn off marker only if the marker switch is enabled
        	if (MyCallingOpScreen->MarkerLightSwitch->Enabled) {
                MyCallingOpScreen->MarkerLightSwitch->Checked = false;
                }
			break;

        case DC_MASTER_VOLUME_OFF:	// Master Volume to Zero
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->MasterVolume->Value = 0;
            	} else {
				MyCallingOpScreen->SendOneCommand("v0000");
                }
			break;
        case DC_MASTER_VOLUME:		// Master Volume set
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->MasterVolume->Value = MyParameter;
            	} else {
                snprintf(str, sizeof str, "v0%03d", MyParameter);
	            MyCallingOpScreen->SendOneCommand(str);
                }
        	break;
        case DC_ENGINE_VOLUME_OFF:	// Engine Volume to Zero
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->EngineVolume->Value = 0;
            	} else {
                MyCallingOpScreen->SendOneCommand("v1000");
                }
			break;
        case DC_ENGINE_VOLUME:		// Engine Volume set
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->EngineVolume->Value = MyParameter;
            	} else {
                snprintf(str, sizeof str, "v1%03d", MyParameter);
	            MyCallingOpScreen->SendOneCommand(str);
                }
        	break;
        case DC_ACCENT_VOLUME_OFF:	// Accent Volume to Zero
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->AccentVolume->Value = 0;
            	} else {
                MyCallingOpScreen->SendOneCommand("v2000");
                }
			break;
        case DC_ACCENT_VOLUME:		// Accent Volume set
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->AccentVolume->Value = MyParameter;
            	} else {
                snprintf(str, sizeof str, "v2%03d", MyParameter);
	            MyCallingOpScreen->SendOneCommand(str);
                }
        	break;
        case DC_HORN_WHISTLE_VOLUME_OFF:	// Horn Volume to Zero
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->HornWhistleVolume->Value = 0;
            	} else {
                MyCallingOpScreen->SendOneCommand("v3000");
                }
			break;
        case DC_HORN_WHISTLE_VOLUME:		// Horn Volume set
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->HornWhistleVolume->Value = MyParameter;
            	} else {
                snprintf(str, sizeof str, "v3%03d", MyParameter);
	            MyCallingOpScreen->SendOneCommand(str);
                }
        	break;
        case DC_BELL_VOLUME_OFF:	// Bell Volume to Zero
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->BellVolume->Value = 0;
            	} else {
                MyCallingOpScreen->SendOneCommand("v4000");
                }
			break;
        case DC_BELL_VOLUME:		// Bell Volume set
	        if (ptrVolume[MyConsistNo]) {	// if we have a Volume window, use it
                ptrVolume[MyConsistNo]->BellVolume->Value = MyParameter;
            	} else {
                snprintf(str, sizeof str, "v4%03d", MyParameter);
	            MyCallingOpScreen->SendOneCommand(str);
                }
        	break;

        case DC_PLAY_SOUND:			// Play any Sound
    		snprintf(str, sizeof str, PLAY_SOUND, MyParameter);
            MyCallingOpScreen->SendOneCommand(str);
        	break;
        case DC_AB:					// "ab" command
    		snprintf(str, sizeof str, SETCMD, MyParameter);
            MyCallingOpScreen->SendOneCommand(str);
        	break;

        case DC_CPLR_F:				// "c0" command
           	(void) PostMessage(MyParent, CM_CPLRF, true, NULL);
        	break;
        case DC_CPLR_R:				// "C1" command
           	(void) PostMessage(MyParent, CM_CPLRR, true, NULL);
        	break;

        case DC_AUX1_ON:		// "ab3" command
            if (MyCallingOpScreen->ab3ab2Switch->Enabled) MyCallingOpScreen->ab3ab2Switch->Checked = true;
        	break;
        case DC_AUX1_OFF:		// "ab2" command
            if (MyCallingOpScreen->ab3ab2Switch->Enabled) MyCallingOpScreen->ab3ab2Switch->Checked = false;
        	break;

        case DC_AUX2_ON:		// "ab5" command
	        if (MyCallingOpScreen->ab5ab4Switch->Enabled) MyCallingOpScreen->ab5ab4Switch->Checked = true;
        	break;
        case DC_AUX2_OFF:		// "ab4" command
	        if (MyCallingOpScreen->ab5ab4Switch->Enabled) MyCallingOpScreen->ab5ab4Switch->Checked = false;
        	break;

        case DC_AUX3_ON:		// "ab7" command
        	if (MyCallingOpScreen->ab7ab6Switch->Enabled) MyCallingOpScreen->ab7ab6Switch->Checked = true;
        	break;
        case DC_AUX3_OFF:		// "ab6" command
        	if (MyCallingOpScreen->ab7ab6Switch->Enabled) MyCallingOpScreen->ab7ab6Switch->Checked = false;
        	break;

        case DC_SMOKE_ON:		// "abF" command
        	if (MyCallingOpScreen->SmokeSwitch->Enabled) MyCallingOpScreen->SmokeSwitch->Checked = true;
        	break;
        case DC_SMOKE_OFF:		// "abE" command
        	if (MyCallingOpScreen->SmokeSwitch->Enabled) MyCallingOpScreen->SmokeSwitch->Checked = false;
        	break;

        case DC_SOUNDS_ON:		// turn on
        	MyCallingOpScreen->SoundsSwitch->Checked = true;
        	break;
        case DC_SOUNDS_OFF:		// turn off
        	MyCallingOpScreen->SoundsSwitch->Checked = false;
        	break;

	    case DC_CHUFF_RATE_4:	// Chuff rate to 4
        	if (MyCallingOpScreen->ChuffRateSwitch->Enabled) MyCallingOpScreen->ChuffRateSwitch->Checked = true;
        	break;
	    case DC_CHUFF_RATE_2:	// Chuff rate to 2
        	if (MyCallingOpScreen->ChuffRateSwitch->Enabled) MyCallingOpScreen->ChuffRateSwitch->Checked = false;
        	break;

	    case DC_PROTO_CHUFF_ON:	// turn on
        	MyCallingOpScreen->ProtoChuffSwitch->Checked = true;
        	break;
	    case DC_PROTO_CHUFF_OFF:// turn off
        	MyCallingOpScreen->ProtoChuffSwitch->Checked = false;
        	break;

    	case DC_BRAKE_SQUEAL_ON:	// turn on
        	MyCallingOpScreen->BrakesSwitch->Checked = true;
        	break;
	    case DC_BRAKE_SQUEAL_OFF:	// turn off
        	MyCallingOpScreen->BrakesSwitch->Checked = false;
        	break;

    	case DC_CAB_CHAT_ON:	// turn on
        	MyCallingOpScreen->CabChatSwitch->Checked = true;
        	break;
	    case DC_CAB_CHAT_OFF:	// turn off
        	MyCallingOpScreen->CabChatSwitch->Checked = false;
        	break;
    	case DC_CPLR_SLACK_ON:	// turn on
        	MyCallingOpScreen->CouplerSlackSwitch->Checked = true;
        	break;

	    case DC_CPLR_SLACK_OFF:	// turn off
        	MyCallingOpScreen->CouplerSlackSwitch->Checked = false;
        	break;
    	case DC_CLICK_CLACK_ON:	// turn on
        	MyCallingOpScreen->ClickClackSwitch->Checked = true;
        	break;
	    case DC_CLICK_CLACK_OFF:	// turn off
        	MyCallingOpScreen->ClickClackSwitch->Checked = false;
        	break;

    	case DC_PROTO_WHISTLE_ON:	// turn on
        	if (MyCallingOpScreen->ProtoWhistleSwitch->Enabled) MyCallingOpScreen->ProtoWhistleSwitch->Checked = true;
        	break;
	    case DC_PROTO_WHISTLE_OFF:	// turn off
        	if (MyCallingOpScreen->ProtoWhistleSwitch->Enabled) MyCallingOpScreen->ProtoWhistleSwitch->Checked = false;
        	break;
        case DC_PROTO_WHISTLE:		// Quilling Whistle to MyParameter
            MyCallingOpScreen->WhistleSlider->Value = MyParameter;
        	break;

    	case DC_SMOKE_WHISTLE_ON:	// turn on
        	if (MyCallingOpScreen->SmokeWhistleSwitch->Enabled) MyCallingOpScreen->SmokeWhistleSwitch->Checked = true;
        	break;
	    case DC_SMOKE_WHISTLE_OFF:	// turn off
        	if (MyCallingOpScreen->SmokeWhistleSwitch->Enabled) MyCallingOpScreen->SmokeWhistleSwitch->Checked = false;
        	break;
            
    	case DC_SWINGING_BELL_ON:	// turn on
        	if (MyCallingOpScreen->SwingingBellSwitch->Enabled) MyCallingOpScreen->SwingingBellSwitch->Checked = true;
        	break;
	    case DC_SWINGING_BELL_OFF:	// turn off
        	if (MyCallingOpScreen->SwingingBellSwitch->Enabled) MyCallingOpScreen->SwingingBellSwitch->Checked = false;
        	break;

        default:
        	Terminate();	// invalid Engine Command
            break;
		}
    } else {
    // the consist is no longer running, display a message
    if (DEBUGGINGX(5)) ptrTIUCommThread->PrintItem("Delayed Command - Consist " + String(MyConsistNo) + " - TIU " + String(MyTIUNo) + " not running (already shutdown?)", clRed);
    }
return;
}
//---------------------------------------------------------------------------
void __fastcall TDispatch_List_Thread::CleanUp(void)     // called using Synchronize()
{
// This function is empty now except for the status message at the end
if (MyCallingOpScreen)
  switch (MyDelayedCommand) {
	case DC_BELLOFF:
//    	MyCallingOpScreen->ptrBellOff = NULL;	// indicate that this thread is terminating
		break;
    case DC_BELLON:
//    	MyCallingOpScreen->ptrBellOn = NULL;	// indicate that this thread is terminating
		break;
	case DC_MARS_ON:
	case DC_MARS_OFF:
//		if (MyCallingOpScreen->HDLTSwitch->Checked) {	// leave headlight in switch state
//			MyCallingOpScreen->SendOneCommand("ab1");
//			} else {
//            MyCallingOpScreen->SendOneCommand("ab0");
//            }
//		MyCallingOpScreen->ptrMarsLight = NULL;	// indicate that his thread is terminating
		break;
	case DC_BEACON_ON:
	case DC_BEACON_OFF:
//		if (MyCallingOpScreen->BeaconLightSwitch->Checked) {	// leave beacon in switch state
//			MyCallingOpScreen->SendOneCommand("abD");
//            } else {
//            MyCallingOpScreen->SendOneCommand("abC");
//            }
//		MyCallingOpScreen->ptrBeaconLight = NULL;	// indicate that his thread is terminating
		break;
    case DC_MPH:
//    	MyCallingOpScreen->ptrMPH = NULL;	// indicate that his thread is terminating
        break;
	}
if (DEBUGGINGX(5)) ptrTIUCommThread->PrintItem("Delayed Command " + DStrings[MyDelayedCommand] + " (Parameters " + String(MyConsistNo) + "," + String(MyParameter) + ") Processing Complete after delay of " + String(MyTimeValue) + " seconds", clBlue);
}
//---------------------------------------------------------------------------

int __fastcall TDispatch_List_Thread::Count_DL_FIFO(int option)
{
//
// Return the number of entries in the DL FIFO
//
TList *myList = ptrDL_Thread->DL_FIFO->LockList();
assert(myList);
int DL_Count = myList->Count;
ptrDL_Thread->DL_FIFO->UnlockList();
return DL_Count;
}
//---------------------------------------------------------------------------

unsigned int __fastcall TDispatch_List_Thread::Count_Dispatch_List(int option)
{
//
// Return the number of entries in the dispatch list
//
return DispatchListCount;
}
//---------------------------------------------------------------------------

void __fastcall TDispatch_List_Thread::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
// not used
}
//---------------------------------------------------------------------------
