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

© Copyright 2015 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>
#pragma hdrstop

#include "Message_Popup.h"
#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Engine_Status_Thread.h"
#include "Engine_Status.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"

#include <math.h>
#include <stdio.h>
#include <string.h>

#pragma package(smart_init)

extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications 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 TEngineStatusThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TEngineStatusThread::TEngineStatusThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------
__fastcall TEngineStatusThread::TEngineStatusThread(bool CreateSuspended, TEngineStatus * CallingOp, HWND Parent,
		int Eng, int TIU, int Remote, DWORD delay, bool Repeat)
	: TThread(CreateSuspended)
{
// Calling window - right now, just the Operations window (Engine)
CallingOpScreen = CallingOp;
// Engine Number and TIU Number
EngineNo = Eng;
TIUNo = TIU;
RemoteNo = Remote;
// Milliseconds to wait before repeating
DelayValue = delay;
// false = run once, true = repeat
bRepeat = Repeat;
// FreeOnTerminate is not set so caller must delete thread
FreeOnTerminate = false;
// ReturnValue is accessable using WaitFor()
ReturnValue = -1;
//
// Parent Handle - right now, this value is not used
ParentHandle = Parent;
//
TestValue = 1;
// termination
OnTerminate = &RunOnTerminate;

PrintColor = clBlack;
// Fetch Scaling Factor once since it does not change
bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2008", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iFactorCount, FactorResp);
if (!bretv) {	// retry once
    if (DEBUGGING) ptrTIUCommThread->PrintItem("TEngineStatusThread() Retrying V2008");
	bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2008", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iFactorCount, FactorResp);
	}
int iSF = 0;
if (bretv) {
    if (Setup->WTIU_WiFi_Mode->Checked) {
        // Handle WTIU ASCII Response
        int h0, h1;
		int retv = sscanf(FactorResp, "q%x %x", &h0, &h1);   // %x requires &int
		if (retv == 2) {
            iSF = h0 << 8 | h1;
            } else {
            iSF = 1;
            }
        } else {
        if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
            // Handle Radio Binary Response
            //
            // pull out the scale factor on success
            // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
            //   87   D9   00   00   04   04   E1   31   05
            //                          ^^^^^^^^^             Scale Factor -- returned by the TIU
            iSF = FactorResp[5] << 8 | FactorResp[6];
            } else {
            iSF = 0;
            }
        }
    } else {
    Terminate();	// failed to get Scale Factor
    }
fSF = iSF;
if (DEBUGGINGX(1)) DB->Debug_Print("Scale Factor : " +
        	Format((String)"0x%4.4x", ARRAYOFCONST((iSF))).UpperCase(), clGreen);
}
//---------------------------------------------------------------------------
__fastcall TEngineStatusThread::~TEngineStatusThread()
{
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ptrTIUCommThread->PrintItem("TEngineStatusThread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
}//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::Execute()
{
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread

if (DEBUGGINGX(1)) {
	// print the ThreadId
    ThreadID = GetCurrentThreadId();
    ptrTIUCommThread->PrintItem("TEngineStatusThread: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 = "Engine_Status_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
//
ReturnValue = FAIL;
//
while (true) {
    //
    if (Terminated) break;		// in case we are terminated
    //
    if (StatusUpdate) {
        // Send Status sequence (memory location offsets 0x22 through 0x25)
		bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V4022", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iStatusCount, StatusbyteResp);
        if (!bretv) continue;	// ignore error
        }
    if (Terminated) break;		// in case we are terminated
    //
    if (SpeedUpdate) {
    	// Send Speed seqence (memory location 0x62/0x63)
		bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2062", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iSpeedCount, SpeedbyteResp);
        if (!bretv) continue;	// ignore error
        }
    if (Terminated) break;		// in case we are terminated
    //
    if (SpeedUpdate) {
    	// Send Direction byte request (memory location 0x67)
		bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V1067", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDirCount, DirbyteResp);
        if (!bretv) continue;	// ignore error
        }
    if (Terminated) break;		// in case we are terminated
    //
    if (InstantSpeedUpdate) {
		// if PS3
        if (MainForm->EngineData->Cells[ENG_PS][EngineNo].ToInt() == PS3) {	
        	// Send Speed sequence (memory location 0x60 - one byte for PS3)
			bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V1060", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iInstantCount, InstantSpeedbyteResp);
            if (!bretv) continue;	// ignore error
            }

        // if PS2
        if (MainForm->EngineData->Cells[ENG_PS][EngineNo].ToInt() == PS2) {	
	    	// Send Speed sequence (memory location 0x60/0x61 - two bytes for PS2)
			bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V2060", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iInstantCount, InstantSpeedbyteResp);
            if (!bretv) continue;	// ignore error
            }
        }
    if (Terminated) break;		// in case we are terminated
    //
    if (DTOUpdate) {
        // Send DTO sequence (memory location 0x3C-0x3F)
        bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "V403C", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDTOCount, DTObyteResp);
        if (!bretv) continue;	// ignore error
        }
    //
    if (Terminated) break;		// in case we are terminated
    Synchronize(EI);			// Perform the function synchronously
    if (!bRepeat) break;		// exit in run once mode
	if (Terminated) break;
    Sleep(DelayValue/5);		// DelayValue ticks split 5 ways
	if (Terminated) break;
    Sleep(DelayValue/5);		// DelayValue ticks split 5 ways
	if (Terminated) break;
    Sleep(DelayValue/5);		// DelayValue ticks split 5 ways
	if (Terminated) break;
    Sleep(DelayValue/5);		// DelayValue ticks split 5 ways
	if (Terminated) break;
    Sleep(DelayValue/5);		// DelayValue ticks split 5 ways
	if (Terminated) break;
    Synchronize(CheckUpdateFlags);	// Perform the function synchronously
	}	// while(true)
//
Synchronize(CleanUp);	// Perform the cleanup synchronously
ReturnValue = SUCCESS;
}
//---------------------------------------------------------------------------

bool __fastcall TEngineStatusThread::Query(char * CommandString, byte * QBuf, int Engine, int TIU, int Remote)
{
// Send a Query "q" or "qXXXX" command to the TIU and wait for a response,
// then display it on the Debug window
// return true on success or false on failure
bool retv = ptrTIUCommThread->Send_String_FIFO(Engine, CommandString, TIU, Remote, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, NULL, QBuf);
if (DEBUGGINGX(5) && (QBuf != NULL)) {
	PrintLine = "TEngineStatusThread::Query() " + String(CommandString) + " : " + byteBinaryStringToHexString(QBuf + 7, 2);
	Synchronize(PrintItem);
    }
return retv;
}
//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::Initialize(void)     // called using Synchronize()
{
CheckUpdateFlags();
CallingOpScreen->Engine_Status_Label->Hint = Format((String)"Scale Factor = 0x%2.2X%2.2X (%f)", ARRAYOFCONST((FactorResp[5],  FactorResp[6], fSF)));
}
//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::CheckUpdateFlags(void)     // called using Synchronize()
{
SpeedUpdate = CallingOpScreen->SpeedUpdate->Checked && CallingOpScreen->SpeedUpdate->Enabled;
InstantSpeedUpdate = CallingOpScreen->InstantSpeedUpdate->Checked && CallingOpScreen->InstantSpeedUpdate->Enabled;
StatusUpdate = CallingOpScreen->StatusUpdate->Checked && CallingOpScreen->StatusUpdate->Enabled;
DTOUpdate = CallingOpScreen->DTOUpdate->Checked && CallingOpScreen->DTOUpdate->Enabled;
}
//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::CleanUp(void)     // called using Synchronize()
{
// not used
}
//---------------------------------------------------------------------------
void __fastcall TEngineStatusThread::EI(void)     // called using Synchronize()
{
//
if (StatusUpdate) {
	// Send Status sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("Status Query");
	// Send Status sequence
	// decode the response into a bit map
    unsigned int lVal = 0;
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		//CallingOpScreen->Status->Text = "N/A";
		} else {
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0, h1, h2, h3;
	    	int retv = sscanf(StatusbyteResp, "q%x %x %x %x", &h0, &h1, &h2, &h3);   // %x requires &int
		    if (retv == 4) {
                lVal = h0 << 24 | h1 << 16 | h2 << 8 | h3;
                } else {
                lVal = 0;
                }
            } else {
            if (Setup->Radio_COM_Mode->Checked) {
                // Handle Radio Binary Response
        		// pull out the value
	        	// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		        //   87   D9   00   00   04   04   E1   31   05
    		    //                          ^^^^^^^^^            Engine Status-- returned by the Engine via the TIU
    	    	lVal = ((StatusbyteResp[5] << 24) + (StatusbyteResp[6] << 16) + (StatusbyteResp[7] << 8) + StatusbyteResp[8]);
                } else {
                lVal = 0;
                }
            }

		char StatusStr[64];
		snprintf(StatusStr, sizeof StatusStr, "%08X", lVal);
		if (DEBUGGINGX(5)) DB->Debug_Print("Status Word (0x22 through 0x25): 0x" + String(StatusStr));
        CallingOpScreen->EngineStatusGroupBox->Caption = "Engine Status - 0x" +  String(StatusStr);
        CallingOpScreen->AbLED24_7->Checked = !(lVal & 0x8000);	// inverted Marker Lights
        CallingOpScreen->AbLED24_6->Checked = !(lVal & 0x4000);	// inverted
        CallingOpScreen->AbLED24_5->Checked = !(lVal & 0x2000);	// inverted Proto Rev
        CallingOpScreen->AbLED24_4->Checked = !(lVal & 0x1000);	// inverted Brake Squeal
        CallingOpScreen->AbLED24_3->Checked = lVal & 0x0800;	// Click Clack
        CallingOpScreen->AbLED24_2->Checked = lVal & 0x0400;	// Cab Chat
        CallingOpScreen->AbLED24_1->Checked = !(lVal & 0x0200);	// inverted Smoke Volume (Maximum)
        CallingOpScreen->AbLED24_0->Checked = !(lVal & 0x0100);	// inverted Smoke Volume (Medium)
        CallingOpScreen->AbLED25_7->Checked = lVal & 0x0080;
        CallingOpScreen->AbLED25_6->Checked = lVal & 0x0040;
        CallingOpScreen->AbLED25_5->Checked = lVal & 0x0020;
        CallingOpScreen->AbLED25_4->Checked = lVal & 0x0010;
        CallingOpScreen->AbLED25_3->Checked = lVal & 0x0008;
        CallingOpScreen->AbLED25_2->Checked = lVal & 0x0004;
        CallingOpScreen->AbLED25_1->Checked = lVal & 0x0002;
        CallingOpScreen->AbLED25_0->Checked = lVal & 0x0001;
		}
	}
if (SpeedUpdate) {
	if (DEBUGGINGX(8)) DB->Debug_Print("Speed/Direction Query");
	// decode the response into a bit map
    unsigned int Dir = 0;
    unsigned int Spd = 0;
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		CallingOpScreen->Speed->Text = "N/A";
		} else {
        CallingOpScreen->DirLED->LED->ColorOff = clLime;
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0;
	    	int retv = sscanf(DirbyteResp, "q%x", &h0);   // %x requires &int
		    if (retv == 1) {
                Dir = h0;
                } else {
                Dir = 0;
                }
            } else {
            if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
                // Handle Radio Binary Response
                // pull out the value
	            // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		        //   87   D9   00   00   04   04   E1   31   05
        	    //                          ^^^^                 Engine direction-- returned by the TIU
                Dir = DirbyteResp[5] & 0x01;
                } else {
                Dir = 0;
                }
            }


        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0, h1;
	    	int retv = sscanf(SpeedbyteResp, "q%x %x", &h0, &h1);   // %x requires &int
		    if (retv == 2) {
                Spd = (h0 << 8 | h1) >> 3;	// divide by 8 to get Smiles
                } else {
                Spd = 1;
                }
            } else {
            if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
                // Handle Radio Binary Response
                // PS2 and PS3 gets the set speed not the instantaneous speed
                // pull out the value
                // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
                //   87   D9   00   00   04   04   E1   31   05
                //                          ^^^^^^^^^            Engine Speed * 8 (goal speed, not instanteanous)-- returned by the Engine via the TIU
                Spd = (SpeedbyteResp[5] << 8 | SpeedbyteResp[6]) >> 3;	// divide by 8 to get Smiles
                } else {
                Spd = 0;
                }
            }

		char SpeedStr[64];
		snprintf(SpeedStr, sizeof SpeedStr, "%4d", Spd);
        CallingOpScreen->DirLED->Checked = Dir;	// true = 1 = Reverse, false = 0 = Forward
        CallingOpScreen->DirLED->Caption = Dir?"Reverse":"Forward";
        if (DEBUGGINGX(5)) DB->Debug_Print("Direction (0x67): " + String(Dir?"Reverse":"Forward"));
		CallingOpScreen->Speed->Text = SpeedStr;
		if (DEBUGGINGX(5)) DB->Debug_Print("Speed (0x62/0x63): " + String(SpeedStr) + " Smiles/hour");
		}
	}
if (InstantSpeedUpdate) {
    if (DEBUGGINGX(8)) DB->Debug_Print("Instant Speed Query");
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		CallingOpScreen->InstantSpeed->Text = "N/A";
		} else {
        unsigned int Spd = 0;
		// if PS3
        if (MainForm->EngineData->Cells[ENG_PS][EngineNo].ToInt() == PS3) {
            if (Setup->WTIU_WiFi_Mode->Checked) {
                // Handle WTIU ASCII Response
                int h0;
	    	    int retv = sscanf(InstantSpeedbyteResp, "q%x", &h0);   // %x requires &int
    		    if (retv == 1) {
                    Spd = h0;
                    } else {
                    Spd = 0;
                    }
                } else {
                if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
                    // Handle Radio Binary Response
                    // pull out the value
                    // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
                    //   87   D9   00   00   04   04   E1   31   05
                    //                          ^^^^                 Instant Engine Speed-- returned by the Engine via the TIU
                    Spd = InstantSpeedbyteResp[5];	// byte contains actual speed in Smiles
                    } else {
                    Spd = 0;
                    }
                }
            }

        // if PS2
        if (MainForm->EngineData->Cells[ENG_PS][EngineNo].ToInt() == PS2) {
            if (Setup->WTIU_WiFi_Mode->Checked) {
                // Handle WTIU ASCII Response
                int h0, h1;
	        	int retv = sscanf(InstantSpeedbyteResp, "q%x %x", &h0, &h1);   // %x requires &int
                //                                        ^^ ^^      Engine Speed * 8 (instanteanous)-- returned by the TIU
                if (retv == 2) {
                    Spd = (h0 << 8 | h1) >> 3;	// divide by 8 to get Smiles
                    } else {
                    Spd = 0;
                    }
                } else {
                if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
                    // Handle Radio Binary Reponse
                    // pull out the value
                    // 965A 99A6 5555 5555 5559 5559 A596 6566 555A
                    //   87   D9   00   00   04   04   E1   31   05
                    //                          ^^^^^^^^^             Engine Speed * 8 (instanteanous)-- returned by the TIU
                    Spd = (InstantSpeedbyteResp[5] << 8 | InstantSpeedbyteResp[6]) >> 3;	// divide by 8 to get Smiles
                    } else {
                    Spd = 0;
                    }
                }
            }
		char InstantSpeedStr[64];
		snprintf(InstantSpeedStr, sizeof InstantSpeedStr, "%4d", Spd);
		CallingOpScreen->InstantSpeed->Text = InstantSpeedStr;
		if (DEBUGGINGX(5)) DB->Debug_Print("Instantaneous Speed (0x60): " + String(InstantSpeedStr) + " Smiles/hour");
        }
	}

if (DTOUpdate) {
	// Send DTO sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("DTO Query");
	// decode the response into mileage
    unsigned long lVal;
	if (Setup->NumRetries->Value == 10 || fSF == 0.0) {	// ignore serial port mode or divide by zero - Don't have value
		CallingOpScreen->DTO->Text = "N/A";
		} else {
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0, h1, h2, h3;
	    	int retv = sscanf(DTObyteResp, "q%x %x %x %x", &h0, &h1, &h2, &h3);   // %x requires &int
		    if (retv == 4) {
                lVal = h0 << 24 | h1 << 16 | h2 << 8 | h3;
                } else {
                lVal = 1;
                }
            } else {
            if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
                // Handle Radio Binary Response
        		// pull out the value
        		// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
	        	//   87   D9   00   00   04   04   E1   31   05
    		    //                          ^^^^^^^^^^^^^^^^^^^   Trip Odometer Mileage -- returned by the Engine via the TIU
                lVal = DTObyteResp[5] << 24 | DTObyteResp[6] << 16 | DTObyteResp[7] << 8 | DTObyteResp[8];
                } else {
                lVal = 0;
                }
            }
    	//const double DTO = (lVal)/(fSF * DTOFACTOR);
		const double fDTO = DTOEQUATION (lVal, fSF);
		char DTOStr[64];
		snprintf(DTOStr, sizeof DTOStr, "%10.4f", fDTO);
		CallingOpScreen->DTO->Text = DTOStr;
        const double Sfeet = fDTO * 5280.0;
        snprintf(DTOStr, sizeof DTOStr, "%15.2f Sfeet", Sfeet);
        CallingOpScreen->DTO->Hint = DTOStr;
		if (DEBUGGINGX(5)) {
        	DB->Debug_Print("Trip Odometer (0x3C-0x3F): " + CallingOpScreen->DTO->Text + " Smiles (" + CallingOpScreen->DTO->Hint + ") Raw Value = " +
        		Format((String)"0X%8.8X", ARRAYOFCONST(((int)lVal))).UpperCase(), clGreen);
			DB->Debug_Print(Format((String)"DTOButton=%.6f raw=%d DTOFACTOR=%.6f ScaleFactor=%f ", ARRAYOFCONST((fDTO, (int)lVal, DTOFACTOR, fSF))), clBlue);
    		}
        }
	}
//
DelayValue = CallingOpScreen->UpdateRateSpin->ValueAsInt * 1000L;
CallingOpScreen->QueryLabel->Caption = "Query # " + String(TestValue++);
}
//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
if (DEBUGGINGX(1)) DB->Debug_Print("TEngineStatusThread Terminated", clGreen);
CallingOpScreen->LED->Checked = false;
}
//---------------------------------------------------------------------------

void __fastcall TEngineStatusThread::PrintItem()     // called using Synchronize()
{
// Execution of the thread is suspended while PrintItem is executing in the main VCL thread.
if (DEBUGGING) DB->Debug_Print(PrintLine, PrintColor);
PrintColor = clBlack;
}
//---------------------------------------------------------------------------

