//---------------------------------------------------------------------------
/*
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
//---------------------------------------------------------------------------
#include <vcl.h>
#include <tchar.h>
#pragma hdrstop

#include "RTCMegaMaster.h"		// Dispatch List
#include "Message_Popup.h"
#include "Train_Control.h"
#include "PBFileThread.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"

#pragma package(smart_init)

extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
//---------------------------------------------------------------------------
/*			ReturnValue

-3			SetWaitableTimer failed
-1			Unknown error
iParameter	TerminatationPBEvent received
FAIL		Failure executing the Playback File
SUCCESS		Success
*/
//
// This thread can be executed by Setup but only one can be running at any time
// This thread can be executed by Hot_Com and up to 100 threads can be running at any time
//		That is, each Hot_Com button can invoke a thread
//---------------------------------------------------------------------------
//   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 TPlaybackFileThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TPlaybackFileThread::TPlaybackFileThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------

__fastcall TPlaybackFileThread::TPlaybackFileThread(bool CreateSuspended, TForm * Caller, HWND Parent, int Remote, String FName,
		int ButtonNo, int Parameter)
        : TThread(CreateSuspended)
{
// Calling window - right now, just the Main window
CallingScreen = Caller;
// Filename to playback
FileName = FName;
assert(!FName.IsEmpty());
// Button Number
iButtonNo = ButtonNo;
assert(iButtonNo >= -1 && iButtonNo <= 100); 	// CALLEDBYSETUP is playback from the Setup window. 1-100 is playback from the Hot Buttons
												// Playback from Command Line is not working yet
// parameter for the playback, this value is not used
iParameter = Parameter;

Finished = false;
// FreeOnTerminate is not set so caller must delete thread
FreeOnTerminate = false;
// ReturnValue is accessable using WaitFor()

// Parent Handle - Used to send Stop Playback Message
ParentHandle = Parent;
// Remote Number
RemoteNo = Remote;
assert(Remote >= 0 && Remote <= REMOTEMAX);
PBBuffer = NULL;
// termination
OnTerminate = &RunOnTerminate;

MessagePopup = NULL;
ReturnValue = -1;
}
//---------------------------------------------------------------------------

unsigned int toInt(char c)
{
// Convert a Hex digit to a binary number
  if (c >= '0' && c <= '9') return      (c - '0');
  if (c >= 'A' && c <= 'F') return 10 + (c - 'A');
  if (c >= 'a' && c <= 'f') return 10 + (c - 'a');
  return -1;
}
//---------------------------------------------------------------------------
__fastcall TPlaybackFileThread::~TPlaybackFileThread()
{
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ptrTIUCommThread->PrintItem("TPlaybackFileThread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::Execute()
{
// Calls to Sleep() are ok in this playback thread
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ThreadID = GetCurrentThreadId();
    ptrTIUCommThread->PrintItem("TPlaybackFileThread:ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }

// 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 = "Plackback_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
    RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
        (DWORD*)&info);
    }
__except (EXCEPTION_CONTINUE_EXECUTION) {
    }

if (Terminated) {
	return;						// in case we are terminated
    }

Synchronize(Initialize);		// Perform the setup synchronously

// 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
if (MyTimer == NULL) {
	assert(false);	// CreateWaitableTimer failed
    }

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

// start the waitable Timer at 1000 milliseconds and continue on if it starts up OK.
bool bretv = SetWaitableTimer(MyTimer, &liDueTime, 1000, NULL, NULL, false);
if (bretv) {
    // Timer is running OK
	while (!Terminated) {
        //
        dwretv = WaitForMultipleObjects(2, handles, FALSE, INFINITE);
        // call PB() on each timer timeout
        // Terminate on each TerminationEvent
        //------------------------  Timer Event -----------------------------------
        if (dwretv == WAIT_OBJECT_0 + 0) {	// MyTimer Event Signaled ?
            // playback commands - index points to the command being processed
    		//
    		Synchronize(ShowTime);		// display the run time
    		PB();						// Playback all commands for this time
            if (ReturnValue == FAIL) break;
            // go and try to playback the next command
            continue;
            }
        //------------------------  Termination Event -----------------------------------
        if (dwretv == WAIT_OBJECT_0 + 1) {	// TerminationPBEvent Event Signaled ?
	        if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TPlaybackFileThread: TerminationPBEvent");
	        Terminate();
            ReturnValue = iParameter;
            break;	// time to terminate
            }
       // continue waiting
       }
    } else {
    // SetWaitableTimer failed
    assert(false);
    }
if (MyTimer) {
	CancelWaitableTimer(MyTimer);
	CloseHandle(MyTimer);
    }
Synchronize(CleanUp);	// Perform the cleanup synchronously
return;
}
//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::Initialize(void)     // called using Synchronize()
{
// playback is started
MessagePopup = NULL;
MainForm->iPlayingBackCounter++;
bPBThreadRunning = true; 	// when set to false, will cancel playback

// first make sure that the file exists
if (!FileExists(FileName)) {
    MainForm->DebugMessagePopup("Playback Error", "Playback file not found", FileName, 1000);
    // Send a stop Playback message to Parent (could be a Hot Button (1 to 100), Setup (-1) or a Command Line request <-- which is not working yet)
    if (ParentHandle) {
    	(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
        }
    Terminate();
    return;
	}

// create a buffer for the playback
PBBuffer = new TStringList();
// Load the playback buffer with the file
PBBuffer->Clear();  // I believe Clearing necessary because LoadFromFile uses Add() to add the read lines to the buffer
PBBuffer->LoadFromFile(FileName);
//
index = 0;	// first command is at index == 0 of TStrings
// check for comment '/' in column 1
while(PBBuffer->Strings[index][1] == '/') {   // The AnsiString [] operator assumes a base index of 1.
    // skip this record
    if (DEBUGGING) DB->Debug_Print("Playback: " + PBBuffer->Strings[index]);
    index++;	// check next record for "//", keep skiping until comments are gone
   	}
// Errors Thrown
// Time Format Errors (all formats) ----------------------------------------
// 100 Can't read the timestamp from the first recorded step
// 101 Can't read the timestamp from the last recorded step (there might be a blank line at the end of the file)
// 103 Can't read the timestamp from the current step
// Radio Modes -----------------------------------------------
// 200 Wrong number of parameters in recorded step (must be 2 (Format 6 encoded), 5 (Format 4 Engine) or 6 (Format 5 Engine or LashUp))
    // eg: Format 6 (encoded engine or lashup)
    // 1428471519 5555669955AA56555559955A9659565A59555A56AAAA969A5A6A99A556964D

    // eg: Format 4 (engine or all engine)
    // 1717163462 100 d0 2 15  // 5555659A659965655A565A6A55669A694D

    // eg: Format 5 (lashup)
    // 1717163655 101 u4 (-16,2) 2 15  // 5555669965AA656969596966656665656666AAAA65595A6A9655AA6A4D


// 201 format of encoded command is not valid
// 202 LashUp specified for a regular engine
// 203 LashUp string is too short
// 204 LashUp Consist number is not 101
// 205 TIU number is not 1-5
// 206 TIU number is not 1-5
// 207 LashUp Consist number is not 101
// 208
// WTIU/WIU Mode ---------------------------------------------
// 200 Wrong number of parameters in recorded step (must be 4 (Format 7 Engine or LashUp))
    // eg: Format 7 (single engine / all engine / lashup)
    // 1717163330 2 d0 2  // d0 okay
    // 1715312555 100 Vv0100 3  // Vv0100 okay
    // 1717161219 101 |u4,9103ÿ 2  // |u4,9103ÿ okay
// 206 TIU number is not 1-5
// 300 "error" response
// 301 "timeout" response
// 302 "failure" response
// 303 "reset" response
//
try {
   	// initialize start time from first record. It's in the same spot for both formats
    // time() = The number of seconds since January 1, 1970
    if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld"), &StartTime_t) != 1) {
       	throw (100);	// throw an error if we can't get the time. It's in the same spot for all formats
        }
    // calculate total time of the playback
    // difference in seconds from first to last playback record
    if (_stscanf(PBBuffer->Strings[PBBuffer->Count-1].c_str(), TEXT("%ld"), &EndTime_t) != 1) {
       	throw (101);	// throw an error if we can't get the time. It's in the same spot for all formats
        }
    // save the start and delta times
    CurrentTime_t = StartTime_t;
    ThisTime_t = StartTime_t;
    }	// end of try
catch (int Oops) {	// specific thrown exception
    MainForm->CaughtErrors->Value ++;
   	// error, stop playback
	MainForm->DebugMessagePopup("Playback Error", "Recorded file: Format Error : " + String(Oops), NULL, 1000);
   	// stop all engines on all TIU with an Emergency Stop
    // Send CM_EMERGENCYSTOP message to MainForm
    (void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
    // Send a stop Playback message to Parent
    if (ParentHandle) {
    	(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
        }
    ReturnValue = FAIL;
    Terminate();
    return;
   	}
catch (...) {		// All other exceptions
    MainForm->CaughtErrors->Value ++;
   	// error, stop playback
	MainForm->DebugMessagePopup("Playback Error", "Recorded file: Unknown Format Exception", NULL, 1000);
   	// stop all engines on all TIU with an Emergency Stop
    // Send CM_EMERGENCYSTOP message to MainForm
    (void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
    // Send a stop Playback message to Parent
    if (ParentHandle) {
    	(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
        }
    ReturnValue = FAIL;
    Terminate();
	return;
   	}
// please wait message
if (!MessagePopup) {
    Application->ProcessMessages(); // process all messages before the popup
    MessagePopup = new TMessagePopup(CallingScreen, CallingScreen->Handle, NULL, iButtonNo);	// Synchronize() Pass "CallingScreen" as the owner
                                                                                                // CALLEDBYSETUP or button 1 to 100
    assert(MessagePopup != NULL);		// make sure that construction worked
    // return messages to the CallingScreen
    MessagePopup->Show();
    MessagePopup->StatusLine->Caption = "";
    MessagePopup->CancelButton->Caption = "Cancel";
    MessagePopup->CancelButton->ShowHint = true;
    MessagePopup->CancelButton->Hint = "Cancel this Playback";
    MessagePopup->CancelButton->Visible = true; // **** I don't know how to get a message to this thread in order to terminate the thread
                                            // so I send the message to the CallingScreen and have that routine send a terminate
                                            // to this thread
    MessagePopup->CancelButton->Tag = 1;
    MessagePopup->RepeatPlayback->Checked = false;
    MessagePopup->RepeatPlayback->Visible = true;

    if (iButtonNo >= 0) { 	// Hot Button 1 to 100
        // Normal Hot Button message
        MessagePopup->MessageLine->Caption = "Button " + String(iButtonNo) + " \"" + ExtractFileName(FileName) + "\" playback for " + String(EndTime_t - StartTime_t) + " Seconds";
        } else {
        // Special Playback from Setup window for ButtonNo = -1
        MessagePopup->MessageLine->Caption = "\"" + ExtractFileName(FileName) + "\" playback for " + String(EndTime_t - StartTime_t) + " Seconds";
        MessagePopup->RepeatPlayback->Checked = Setup->RepeatPlayback->Checked;
        }
    }
ReturnValue = SUCCESS;
}
//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::ShowTime(void)     // called using Synchronize()
{
// Display the elapsed time ThisTime - StartTime;
if (MessagePopup) {
	MessagePopup->StatusLine->Caption = "Step " + String(index) +
		" of " + String(PBBuffer->Count) + " completed (at " + String(CurrentTime_t - StartTime_t) + " seconds)";
	}
if (DEBUGGING) DB->Debug_Print("TPlaybackFileThread : " + String(CurrentTime_t - StartTime_t) + " seconds");
RepeatPlayback = MessagePopup->RepeatPlayback->Checked;
}

//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::CleanUp(void)     // called using Synchronize()
{
if (MessagePopup != NULL) {
   	delete MessagePopup;
 	MessagePopup = NULL;
	}
MainForm->iPlayingBackCounter--;
assert(MainForm->iPlayingBackCounter >= 0);
if (PBBuffer) {
	delete PBBuffer;
    PBBuffer = NULL;
    }
}
//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::PB(void)     // Runs in the thread
{
String PrintLine;

// playback a command - index points to the command in the PBBuffer being processed
__try { 	// __finally
	//---------
	try {	// catch
        //
        while (true) {
        	char cCommand[256];
            char cLashUpEngineList[LASHUP_ENGINE_LIST_LENGTH];
            int EngNum, RemNum, TIUNum;
            if (Terminated) {
				bPBThreadRunning = false;
				return;					// in case we are terminated
    			}
            // check for comment  '/' in column 1
        	while(PBBuffer->Strings[index][1] == '/') {   // The [] operator assumes a base index of 1.
            	// skip this record
            	if (DEBUGGING) {
                	PrintLine = "TPlaybackFileThread : " + PBBuffer->Strings[index];
                	ptrTIUCommThread->PrintItem(PrintLine, clBlack);
                    }
            	index++; 	// check next record for "//", keep skiping until comments are gone
        		}
            // get the time in the current record
			if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld"), &ThisTime_t) != 1) {
           		throw (103);	// throw an error if we can't get ThisTime;
               	}

            // ThisTime contains the time on the current record
            // CurrentTime contains the current time
    	    // Does the requested time match the current time?
   			if (ThisTime_t <= CurrentTime_t) {	// time or catchup time?
				if (!bPBThreadRunning) {
                	Terminate();
                    break;
                    }
                // display the line for debugging
                if (DEBUGGING) {	// DB->Debug_Print("Playback: " + PBBuffer->Strings[index]);
                	PrintLine = "TPlaybackFileThread : " + PBBuffer->Strings[index];
	                ptrTIUCommThread->PrintItem(PrintLine, clBlack);
                    }
                // --------------------------------------------------------------------------------------------------------
                // Recorded file can contain comments starting with "//" in the FIRST column or after all of the parameters
                // --------------------------------------------------------------------------------------------------------
                // Strip off comments on the line
                int Pos = PBBuffer->Strings[index].Pos("/");
                if (Pos != 0) {
                	// chop off the comment
					String YY = PBBuffer->Strings[index];
                    YY.SetLength(Pos - 1);
                    PBBuffer->Strings[index] = YY;
                    }
                //
                // In the code below I use RemNum in the _stscanf() calls but RemoteNo in the calls to Send_String_FIFO
                // I do that because when playing back, we have respect the remote number from the Main window. The current
                // environment at playback might be set for a different remote number than when recorded. I think this
                // is safe to do.
                //
                // ------------------- Radio Mode Formats ---------------------------------------
            	// Start scanning the input line for both encoded and non-encoded formats
            	// time() = The number of seconds since January 1, 1970
                // ------------------------------------------------------------------------------
                if (Setup->Radio_COM_Mode->Checked) {
                    // Radio Mode Format
                    //
                    // ---------------------------------------------------------------------------
                    // Non-encoded format (default):
                    // ------------------
                    // 5/6 Fields, in three formats (Formats 1, 2, & 3 are deprecated)
                    //
                    // Format 4 (5 parameters) : 1428471519 2 m4 2 15  				// Use Format 4 for All non-Lashup Commands
                    // Format 5 (6 parameters) : 1429975153 101 u5 (5,-6,8,7) 1 15	// Use Format 5 for LashUp Commands
                    //
                    // Encoded Format:
                    // --------------
                    // 2 fields in one format for all commands:
                    //
                    // Format 6 (2 parameters): 1428471519 5555669955AA56555559955A9659565A59555A56AAAA969A5A6A99A556964D  // comment here
                    //
                    // ---------------------------------------------------------------------------
                    // Scan the easy human readable non-encoded format 3 (deprecated) & format 5 - LashUp string in "()" - 6 parameters
                    if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld %d %255hs \(%127hs %d %d"), &ThisTime_t, &EngNum, cCommand, cLashUpEngineList, &TIUNum, &RemNum) == 6) {
                        //
                        // TIU must be 1-TIUMAX
                        if (TIUNum < 1 || TIUNum > TIUMAX) throw (206);
                        // ----------------------
                        if (EngNum != 101) throw(207);	// All LashUps are sent to TIU as #101 (always DCS #102 0x66)
                        // A LashUp  - Convert LashUp ASCII to binary
                        // Format "5,-6,8,7)"  - leading parentheses has been removed by the sscanf
                        char cCS[12];
                        int j = 0;
                        if (cCommand[0] != 'U') cCS[j++] = 0x2C; 	// NOTE NOTE : special case the "U" command does
                                                                    // not use the 0x2C character
                        int StrL = strlen(cLashUpEngineList);	// form "-4,6,2)"
                        if (StrL < 4) throw(203);				// minimum of a head and tail engine, a comma, and
                                                                // a right parenthsis  (4 characters)
                        // up to 10 engines
                        int retv = sscanf(cLashUpEngineList, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
                            &cCS[j], &cCS[j+1], &cCS[j+2], &cCS[j+3], &cCS[j+4], &cCS[j+5], &cCS[j+6], &cCS[j+7], &cCS[j+8], &cCS[j+9]);
                        assert (retv <= 10);	// can't be more than 10 engines
                        //
                        for (int i = j ; i < j + retv ; i++) {
                            if (cCS[i] & 0x80 ) cCS[i] = (abs(cCS[i]) | 0x80);	// convert - sign to H.O. bit
                            cCS[i]++;											// and convert Engine # to DCS Engine #
                            }
                        j = j + retv;
                        cCS[j++] = 0xFF;
                        cCS[j] = '\0';	// NULL Terminate
                        // Send, inserting a new Sequence Number and currently selected RemoteNo, use LONG_WAIT for timeout when playing back a recording
                        if (!ptrTIUCommThread->Send_String_FIFO(EngNum, cCommand, TIUNum, RemoteNo, 11, LONG_WAIT, NO_RECORD, cCS, NULL, NULL)) {
                            bPBThreadRunning = false;
                            Terminate();	// cancel on error
                            }
                        } else {
                        // ---------------------------------------------------------------------------
                        // Scan the easy human readable non-encoded format 4 - 5 parameters
                        if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld %d %255hs %d %d"), &ThisTime_t, &EngNum, cCommand, &TIUNum, &RemNum)  == 5) {
                            cLashUpEngineList[0] = '\0';
                            // ASCII Format ie : "1439352669 101 m4 2 15"
                            // TIU must be 1-5
                            if (TIUNum < 1 || TIUNum > TIUMAX) throw (206);
                            // Send, inserting a new Sequence Number and currently selected RemoteNo, use LONG_WAIT for timeout when playing back a recording
                            if (!ptrTIUCommThread->Send_String_FIFO(EngNum, cCommand, TIUNum, RemoteNo, 11, LONG_WAIT, NO_RECORD, NULL, NULL, NULL)) {
                                bPBThreadRunning = false;
                                Terminate();	// cancel on error
                                }
                            } else {
                            // ---------------------------------------------------------------------------
                            // then check for encoded format 6 - command in encoded "569A" format - 2 parameters
                            if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld %255hs"), &ThisTime_t, cCommand) == 2) {
                                // Encoded Format  (ie "569A" format)  1428471519 5555669955AA56555559955A9659565A59555A56AAAA969A5A6A99A556964D
                                if (!MainForm->ValidCommand(String(cCommand))) throw (201);	// Is it a valid command format?
                                // Send it just as it was recorded
                                if (!ptrTIUCommThread->TIUSendWaitforResponse_FIFO(0, cCommand, 11, LONG_WAIT, RemoteNo, 0, NULL, NULL, NULL)) {
                                    bPBThreadRunning = false;
                                    Terminate();	// cancel on error
                                    }
                                //
                                } else {
                                throw (200);	// throw an error if we can't get the correct number of items - means there is a format error
                                }
                            }
                        }
                    }
                // ------------------- WTIU Mode Formats ------------------------------------
            	// Start scanning the input line
            	// time() = The number of seconds since January 1, 1970
                // ------------------------------------------------------------------------------
                if (Setup->WTIU_WiFi_Mode->Checked) {
                    // Handle WIU ASCII Response
                    //
                    // Simple ASCII format 7 with 4 parameters (Time, Engine Number, Command, TIU Number, // comments are stripped off)
                    // 1428471519 12 m4 1
                    // 1717161355 2 v0100 2  // v0100 okay
                    // 1717161231 101 |u5,9103ÿ 2  // |u5,9103ÿ okay
                    // 1717161611 100 VDd1 2  // VDd1 okay
                    //
                    int iPBCount;;
                    char PBRespBuf[256];
                    int EngNum;

                    if (_stscanf(PBBuffer->Strings[index].c_str(), TEXT("%ld %d %255hs %d"), &ThisTime_t, &EngNum, cCommand, &TIUNum) == 4) {
                        // TIU must be 1-5
                        if (TIUNum < 1 || TIUNum > TIUMAX) throw (206);
                        //
                        if (!ptrTIUCommThread->Send_String_FIFO(EngNum, cCommand, TIUNum, 15, 11, LONG_WAIT, NO_RECORD, NULL, NULL, &iPBCount, PBRespBuf)) {
                            bPBThreadRunning = false;
                            Terminate();	// cancel on error
                            }
                        if (strstr(PBRespBuf,"error")) {
                            throw (300);	// throw an error if we get an error response
                            }
                        if (strstr(PBRespBuf,"timeout")) {
                            throw (301);	// throw an error if we get a timeout response
                            }
                        if (strstr(PBRespBuf,"failure")) {
                            throw (302);	// throw an error if we get a failure response
                            }
                        if (strstr(PBRespBuf,"reset")) {
                            throw (303);	// throw an error if we get a reset response
                            }
                        } else {
                        throw (200);	// throw an error if we can't get the correct number of items - means there is a format error
                        }
                    }
                //
                // ---------------------------------------------------------------------------
                //
    	        index++;	// bump to next command
	        	if (!bPBThreadRunning) {
				    // Send a stop Playback message to Parent
				    if (ParentHandle) {
				    	(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
						}
                	Terminate();
                	break;
                    }
                // End of File ?
                if (index == PBBuffer->Count) {
                	if (RepeatPlayback) { 	// if repeat is on, reset everything and repeat, stop by pressing cancel
                    	// replay the playback
					    index = 0;
                        // reset the CurrentTime and ThisTime to StartTime;
    					CurrentTime_t = StartTime_t;
					    ThisTime_t = StartTime_t;
                    	if (DEBUGGING) {
                			PrintLine = "TPlaybackFileThread : Playback Repeating";
	                		ptrTIUCommThread->PrintItem(PrintLine, clBlack);
                            }
                        return;
                    	} else {
                    	// Playback is over -  NORMAL EXIT
                    	bPBThreadRunning = false;
			        	if (DEBUGGING) {
	                		PrintLine = "TPlaybackFileThread : Playback Complete";
    	            		ptrTIUCommThread->PrintItem(PrintLine, clBlack);
                            }
                        if (ParentHandle) {		// sent message to reset the buttons
						    	(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
					        }
                        Terminate();	// exit when complete
						return;
                    	}
                    }
                continue;	// to play more commands from this time slot
                }  // if (ThisTime <= CurrentTime)
    	    // No more commands for this time slot
            break;
            } // while(true)
        }	// try
    catch (int Oops) {		// specific thrown exception
        MainForm->CaughtErrors->Value ++;
		// error, stop playback
		bPBThreadRunning = false;
        PrintLine = String("TPlaybackFileThread : Error ") + "Recorded file: Format Error : " + String(Oops);
        ptrTIUCommThread->PopupItem(PrintLine);
	    // Send a stop Playback message to Parent
    	if (ParentHandle) {
    		(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
	        }
       	// stop all engines on all TIU with an Emergency Stop
        // Send CM_EMERGENCYSTOP message to MainForm
        (void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
        Terminate();	// cancel on error
        ReturnValue = FAIL;
        return;
        } // catch(int)
    catch (...) {		// All other exceptions
        MainForm->CaughtErrors->Value ++;
    	// error, stop playback
        bPBThreadRunning = false;
        PrintLine = String("TPlaybackFileThread : Error ") + "Recorded file: Unknown Format Exception";
        ptrTIUCommThread->PopupItem(PrintLine);
        // Send a stop Playback message to Parent
	    if (ParentHandle) {
    		(void) PostMessage(ParentHandle, CM_STOPPLAYBACK, NULL, iButtonNo);
        	}
       	// stop all engines on all TIU with an Emergency Stop
        // Send CM_EMERGENCYSTOP message to MainForm
        (void) PostMessage(MainForm->Handle, CM_EMERGENCYSTOP, 0, 0);
        Terminate();	// cancel on error
        ReturnValue = FAIL;
		return;
       	} // catch(...)
    // ---------------------------------------------------------------------------
    }
__finally {
    CurrentTime_t++;		// tick the clock
	}

ReturnValue = SUCCESS;
return;
}
//---------------------------------------------------------------------------

void __fastcall TPlaybackFileThread::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
if (MessagePopup) MessagePopup->CancelButtonClick(MessagePopup->CancelButton);	// Take down message box on termination
}
//---------------------------------------------------------------------------

