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

© Copyright 2024 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 "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Engine_Information_Thread.h"
#include "Engine_Info.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"

#include "math.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 Engine_Info_Thread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TEngineInfoThread::TEngineInfoThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}
//---------------------------------------------------------------------------
__fastcall TEngineInfoThread::TEngineInfoThread(bool CreateSuspended, TEngineInfo * 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;

Finished = false;
// 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("TEngineInfoThread() 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 (iFactorCount == 22) {  // WTIU WiFi
        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 (iFactorCount == 21) {   // 'q' command returns 4 bytes of data
            //
            // 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 TEngineInfoThread::~TEngineInfoThread()
{
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ptrTIUCommThread->PrintItem("TEngineInfoThread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TEngineInfoThread::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("TEngineInfoThread: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_Info_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) {
    bool bretv = true;
    //
    if (Terminated) break;			// in case we are terminated
    //
    if (DODUpdate) {
        // Send DOD sequence (memory locations 0x34-0x37)
		bretv &= ptrTIUCommThread->Send_String_FIFO(EngineNo, "V4034", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDODCount, DODbyteResp);
        }
    if (Terminated) break;			// in case we are terminated
    //
    if (DTOUpdate) {
        // Send DTO sequence (memory locations 0x3C-0x3F)
		bretv &= ptrTIUCommThread->Send_String_FIFO(EngineNo, "V403C", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDTOCount, DTObyteResp);
        }
    if (Terminated) break;			// in case we are terminated
    //
    if (DTVUpdate) {
        // Send DTV sequence (memory locations 0x4C-0x4D)
		bretv &= ptrTIUCommThread->Send_String_FIFO(EngineNo, "V204C", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDTVCount, DTVbyteResp);
        }
    if (Terminated) break;			// in case we are terminated
    //
    if (DCHUpdate) {
        // Send DCH sequence (memory locations 0x38-0x3B)
        bretv &= ptrTIUCommThread->Send_String_FIFO(EngineNo, "V4038", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iDCHCount, DCHbyteResp);
        }
    //
    if (Terminated) break;			// in case we are terminated
    //
    if (DEBUGGING && Setup->WTIU_WiFi_Mode->Checked) {
        // Send Q sequence
        bretv &= ptrTIUCommThread->Send_String_FIFO(EngineNo, "Q", TIUNo, RemoteNo, 21, QUERY_WAIT, NO_RECORD, NULL, NULL, &iQCount, QbyteResp);
        }
    //
    if (Terminated) break;			// in case we are terminated
    //
    if (bretv) Synchronize(EI);		// Perform the function synchronously only if all V command were successful
    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 TEngineInfoThread::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 = "TEngineInfoThread::Query() " + String(CommandString) + " : " + byteBinaryStringToHexString(QBuf + 5, 4);
	Synchronize(PrintItem);
    }
return retv;
}
//---------------------------------------------------------------------------

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

void __fastcall TEngineInfoThread::CheckUpdateFlags(void)     // called using Synchronize()
{
DODUpdate = CallingOpScreen->DODUpdate->Checked && CallingOpScreen->DODUpdate->Enabled;
DTOUpdate = CallingOpScreen->DTOUpdate->Checked && CallingOpScreen->DTOUpdate->Enabled;
DTVUpdate = CallingOpScreen->DTVUpdate->Checked && CallingOpScreen->DTVUpdate->Enabled;
DCHUpdate = CallingOpScreen->DCHUpdate->Checked && CallingOpScreen->DCHUpdate->Enabled;
}
//---------------------------------------------------------------------------

void __fastcall TEngineInfoThread::CleanUp(void)     // called using Synchronize()
{
// not used
}
//---------------------------------------------------------------------------

void __fastcall TEngineInfoThread::EI(void)     // called using Synchronize()
{
//
if (DODUpdate) {
	// Send DOD sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("DOD Query");
	// decode the response into a mileage
    unsigned long lVal = 0;
	if (Setup->NumRetries->Value == 10 || fSF == 0.0) {	// ignore serial port mode or divide by zero - Don't have value
		CallingOpScreen->DOD->Text = "N/A";
		} else {
        if (iDODCount == 22) {
            // Handle WTIU ASCII Response
            int h0, h1, h2, h3;
	    	int retv = sscanf(DODbyteResp, "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 (iDODCount == 21) {   // 'q' command returns 4 bytes of data
        		// pull out the value
	        	// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		        //   87   D9   00   00   04   04   E1   31   05
    		    //                          ^^^^^^^^^^^^^^^^^^^   Odometer Mileage -- returned by the Engine via the TIU
                lVal = DODbyteResp[5] << 24 | DODbyteResp[6] << 16 | DODbyteResp[7] << 8 | DODbyteResp[8];
                } else {
                lVal = 0;
                }
            }

        //const double fDOD = (DODFACTOR * lVal)/fSF;	// multiplier found by experimentation
        const double fDOD = DODEQUATION(lVal, fSF);	// multiplier found by experimentation
        char DODStr[64];
        snprintf(DODStr, sizeof DODStr, "%8.2f", fDOD);
        CallingOpScreen->DOD->Text = DODStr;
        const double Sfeet = fDOD * 5280.0;
        snprintf(DODStr, sizeof DODStr, "%15.5f Sfeet", Sfeet);
        CallingOpScreen->DOD->Hint = DODStr;
        if (DEBUGGINGX(5)) {
        	DB->Debug_Print("Odometer : " + CallingOpScreen->DOD->Text + " Smiles (" + CallingOpScreen->DOD->Hint + ") Raw Value = " +
				Format((String)"0x%8.8X", ARRAYOFCONST(((int)lVal))).UpperCase(), clGreen);
			DB->Debug_Print(Format((String)"DODButton=%.6f raw=%d DODFACTOR=%.6f ScaleFactor=%f ", ARRAYOFCONST((fDOD, (int)lVal, /*DODFACTOR*/ 512.0/DTOFACTOR, fSF))), clBlue);
            }
		}
	}
if (Terminated) return;	// in case we are terminated
//
if (DTOUpdate) {
	// Send DTO sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("DTO Query");
	// Process and display the response to the DTO command
	// decode the response into a mileage
    unsigned long lVal = 0;
	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 : " + 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);
            }
		}
	}
if (Terminated) return;	// in case we are terminated
//
if (DTVUpdate) {
	// Send DTV sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("DTV Query");
	// Send DTV sequence
	// decode the response into a voltage
    unsigned long lVal = 0;
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		CallingOpScreen->DTV->Text = "N/A";
		} else {
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0, h1;
	    	int retv = sscanf(DTVbyteResp, "q%x %x", &h0, &h1);   // %x requires &int
		    if (retv == 2) {
                lVal =  h0 << 8 | h1;
                } else {
                lVal = 1;
                }
            } else {
            if (Setup->Radio_COM_Mode->Checked) {   // 'q' command returns 4 bytes of data
        		// pull out the value
	        	// 965A 99A6 5555 5555 5559 5559 A596 6566 555A
		        //   87   D9   00   00   04   04   E1   31   05
    		    //                          ^^^^^^^^^   Track Voltage -- returned by the Engine via the TIU
    	    	lVal = ((DTVbyteResp[5] << 8) + DTVbyteResp[6]);
                } else {
                lVal = 0;
                }
            }
        //
        const float RawValue = lVal;
        float volts;
        // Quadradic equation from cubic regression (volts = ax^2 + bx + c) of data points. See "CRC Analysis.wb3" page "DTV"
        // RawValue = 8x^2 - 8x + 32		R^2 = 1.0 (Coefficient of Determination)
        // Put equation into the form:  0 = 8x^2 - 8x + (32 - RawValue)
        if (RawValue >= 32) {
            // Using the quadradic equation to solve:
            float a = 8;
            float b = -8;
            float c = 32 - RawValue;		// for RawValue >=32 (which is volts = 1.0)
            // then  b^2 - 4ac Only the positive root is valid
            float radicand = a*a - 4*a*c;
            volts = (-b + sqrt(radicand))/(2*a);
            } else {
            volts = 0.0;
            }
        //
		char DTVStr[64];
		snprintf(DTVStr, sizeof DTVStr, "%7.1f", volts);
		CallingOpScreen->DTV->Text = DTVStr;
		if (DEBUGGINGX(5)) DB->Debug_Print("Track Voltage : " + CallingOpScreen->DTV->Text + " V Raw Value = " +
        	Format((String)"0x%4.4X", ARRAYOFCONST(((int)lVal))).UpperCase(), clGreen);
		}
	}
if (Terminated) return;	// in case we are terminated
//
if (DCHUpdate) {
	// Send DCH sequence
    // ---------------------------------------------------------------------------------------
    // I believe that this calculation is correct. RTC result shows hours to be the same value
    // as the Remote but minutes and seconds are different. I believe there is an error
    // in the Remote's code.
    // ---------------------------------------------------------------------------------------
   	if (DEBUGGINGX(8)) DB->Debug_Print("DCH Query");
	// decode the response into a time
    unsigned long lVal = 0;
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		CallingOpScreen->DCH->Text = "N/A";
		} else {
        if (Setup->WTIU_WiFi_Mode->Checked) {
            // Handle WTIU ASCII Response
            int h0, h1, h2, h3;
            int retv = sscanf(DCHbyteResp, "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) {
                // 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
                //                          ^^^^^^^^^^^^^^^^^^^   Chronometer Time -- returned by the Engine via the TIU
                lVal = DCHbyteResp[5] << 24 | DCHbyteResp[6] << 16 | DCHbyteResp[7] << 8 | DCHbyteResp[8];
                } else {
                lVal = 0;
                }
            }


		//const double t = lVal/DCHFACTOR;	// estimated ticks per second
		const double fDCH = DCHEQUATION(lVal);	// estimated ticks per second
        const unsigned int tt = static_cast<int> (fDCH);
 		const unsigned int seconds = tt % 60;
		//const unsigned int minutes = ((tt/60)-1) % 60;	// adjustment of -1 gives a value that matches the Remote
		const unsigned int minutes = (tt/60) % 60;			// I believe that this calculation is correct and Remote is wrong
		const unsigned int hours = (tt/(60 * 60));
		// ARRAYOFCONST needs to have the unsigned int cast to an int
		CallingOpScreen->DCH->Text = Format((String)"%4dh:%2.2dm:%2.2ds", ARRAYOFCONST(((int)hours, (int)minutes, (int)seconds)));
		if (DEBUGGINGX(5)) {
        	DB->Debug_Print("Chronometer : " + String(CallingOpScreen->DCH->Text) + " Raw Value = " +
				Format((String)"0x%8.8X", ARRAYOFCONST(((int)lVal))).UpperCase(), clGreen);
		    DB->Debug_Print(Format((String)"DCHButton=%.6f raw=%d DCH FACTOR=%.6f", ARRAYOFCONST((fDCH, (int)lVal, DCHFACTOR))), clBlue);
            }
		}
	}
if (Terminated) return;	// in case we are terminated
//
if (DEBUGGING && Setup->WTIU_WiFi_Mode->Checked) {    // WiFi Mode Only
	// Send Q sequence
	if (DEBUGGINGX(8)) DB->Debug_Print("Q Query");
	// decode the response into a quality value 0-100
    int iVal = 0;
	if (Setup->NumRetries->Value == 10) {	// ignore serial port mode - Don't have value
		CallingOpScreen->QVal->Text = "N/A";
		} else {
        if (iQCount >= 11) {        // WiFi response is minimum of 11 characters, max of 13 characters
            // Handle WTIU ASCII Response
            int h0;
	    	int retv = sscanf(QbyteResp, "Q%d", &h0);
		    if (retv == 1) {
                iVal = h0;
                } else {
                iVal = 0;
                }
            }
        if (iQCount == 0) {
        	// TODO : check what the Remote gets
            iVal = 100;
            }
        CallingOpScreen->QVal->Color = (iVal >= 90) ? clLime : clRed;
        CallingOpScreen->QVal->Text = iVal;
        if (DEBUGGINGX(5)) {
        	DB->Debug_Print("Q : " + String(iVal));
            }
		}
	}
if (Terminated) return;	// in case we are terminated
//
DelayValue = CallingOpScreen->UpdateRateSpin->ValueAsInt * 1000L;
CallingOpScreen->QueryLabel->Caption = "Query # " + String(TestValue++);
}
//---------------------------------------------------------------------------

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

void __fastcall TEngineInfoThread::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;
}
//---------------------------------------------------------------------------

