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

#include "RTCMegaMaster.h"		// Dispatch List

#include "Message_Popup.h"
#include "Engine_Commands.h"
#include "Train_Control.h"
#include "Op_Control.h"
#include "Volume_Control.h"
#include "Track_Signal_Thread.h"
#include "Track_Signal.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"
#include "perfgrap.h"

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

#pragma package(smart_init)


extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern TVolume * ptrVolume[ABSOLUTEMAX];	// Volume Control Pointers - indexed by actual engine number because 0 is a factory reset engine

//---------------------------------------------------------------------------
//   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 TTrackSignalThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TTrackSignalThread::TTrackSignalThread(bool CreateSuspended)
	: TThread(CreateSuspended)
{
assert(false);	// never called since I used the overloaded constructor below
}

//---------------------------------------------------------------------------
__fastcall TTrackSignalThread::TTrackSignalThread(bool CreateSuspended, TTrackSignal * CallingTS, HWND Parent,
		TOp * CallingOp, int Eng, int TIU, int Remote, DWORD delay, bool RepeatParameter)
	: TThread(CreateSuspended)
{
// Calling window
CallingTSScreen = CallingTS;
// Calling Operations window
CallingOpScreen = CallingOp;
// Engine Number and TIU Number
EngineNo = Eng;
TIUNo = TIU;
RemoteNo = Remote;
// Milliseconds to wait before repeating
DelayValue = delay;
// Always repeats so this parameter is not used
bRepeatParameter = RepeatParameter;
Finished = false;
// FreeOnTerminate is not set so caller must delete thread
// ReturnValue is accessable using WaitFor()
ReturnValue = -1;
//
// Parent Handle - right now, this value is not used
ParentHandle = Parent;
// termination
OnTerminate = &RunOnTerminate;

PingbyteResp[0] = NULL;
}
//---------------------------------------------------------------------------
__fastcall TTrackSignalThread::~TTrackSignalThread()
{
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ptrTIUCommThread->PrintItem("TTrackSignalThread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TTrackSignalThread::Execute()
{
// Calls to Sleep() are ok in this thread
// NO VCL Calls in this thread
if (Terminated) {
	return;						// in case we are terminated
    }
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ThreadID = GetCurrentThreadId();
    ptrTIUCommThread->PrintItem("TTrackSignalThread: 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 = "Track_Signal_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
    RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
        (DWORD*)&info);
    }
__except (EXCEPTION_CONTINUE_EXECUTION) {
    }
//
Synchronize(Initialize);		// Perform the setup synchronously
//
while (true) {
    //
    if (Terminated) break;			// in case we are terminated
    //

    if (Setup->WTIU_WiFi_Mode->Checked) {
        // the WTIU requires a "yX" command before the "p4" command
        AnsiString aCommand = "y" + String(EngineNo + 1);
	    (void)ptrTIUCommThread->Send_String_FIFO(2, aCommand.c_str(), TIUNo, RemoteNo, 11, LONG_WAIT, NO_RECORD);
        }
	bool bretv = ptrTIUCommThread->Send_String_FIFO(EngineNo, "p4", TIUNo, RemoteNo, 15, LONG_WAIT, NO_RECORD, NULL, NULL, &iPingCount, PingbyteResp);
    if (bretv) Synchronize(TS);		// Display the results so far
    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;
	}	// while(true)
//
Sleep(2000);
Synchronize(CleanUp);	// Perform the cleanup synchronously
ReturnValue = SUCCESS;
}
//---------------------------------------------------------------------------
static void __fastcall (__closure *VolumeOnValueChange)(TObject *Sender);
//---------------------------------------------------------------------------

void __fastcall TTrackSignalThread::Initialize(void)     // called using Synchronize()
{
if (CallingOpScreen && CallingOpScreen->SoundsSwitch->Checked) {	// if sound is on, turn it off
	// Sound off - Remote turns off v1 (Engine) and v2 (Accent)
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "v1000", TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "v2000", TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    //
	TVolume * ptrVol = ptrVolume[EngineNo];
	if (ptrVol) {	// reflect the change on to any open Sound Controls window
   		TAbVSlider* ptrV = ptrVol->EngineVolume;
	    VolumeOnValueChange = ptrV->OnValueChange;
   		ptrV->OnValueChanged = NULL;
	    ptrV->Value = 0;
		ptrV->OnValueChange = VolumeOnValueChange;
        //
   		ptrV = ptrVol->AccentVolume;
	    VolumeOnValueChange = ptrV->OnValueChange;
   		ptrV->OnValueChanged = NULL;
	    ptrV->Value = 0;
		ptrV->OnValueChange = VolumeOnValueChange;
	   	}
    }
if (!CallingOpScreen) {
    // no operations window so just turn off v1 (engine) and v2 (accent)
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "v1000", TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, "v2000", TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    }
//
p4count = 0;	// initialize counts and averages
p4ave = 0;
p4total = 0;
//
PingNum = 1;
}
//---------------------------------------------------------------------------

void __fastcall TTrackSignalThread::CleanUp(void)     // called using Synchronize()
{
if (CallingOpScreen && CallingOpScreen->SoundsSwitch->Checked) { 	// if sound should be on, turn it on.
   	char str[15];
	// Sound back on - v1 (Engine) and v2 (Accent)
    snprintf(str, sizeof str, "v1%03d", MainForm->EngineData->Cells[VOL_ENGINE][EngineNo].ToInt());
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    snprintf(str, sizeof str, "v2%03d", MainForm->EngineData->Cells[VOL_ACCENT][EngineNo].ToInt());
   	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    //
    TVolume * ptrVol = ptrVolume[EngineNo];
	if (ptrVol) {	// reflect the change on to any open Sound Controls window
   		TAbVSlider* ptrV = ptrVol->EngineVolume;
	    VolumeOnValueChange = ptrV->OnValueChange;
   		ptrV->OnValueChanged = NULL;
	    ptrV->Value = MainForm->EngineData->Cells[VOL_ENGINE][EngineNo].ToInt();
		ptrV->OnValueChange = VolumeOnValueChange;
        //
   		ptrV = ptrVol->AccentVolume;
	    VolumeOnValueChange = ptrV->OnValueChange;
   		ptrV->OnValueChanged = NULL;
	    ptrV->Value = MainForm->EngineData->Cells[VOL_ACCENT][EngineNo].ToInt();
		ptrV->OnValueChange = VolumeOnValueChange;
	   	}
    }
if (!CallingOpScreen) {
    // no operations window so just turn sound back on
   	char str[15];
	// Sound back on - v1 (Engine) and v2 (Accent)
    snprintf(str, sizeof str, "v1%03d", MainForm->EngineData->Cells[VOL_ENGINE][EngineNo].ToInt());
	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    snprintf(str, sizeof str, "v2%03d", MainForm->EngineData->Cells[VOL_ACCENT][EngineNo].ToInt());
   	(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, TIUNo, RemoteNo, 11, SHORT_WAIT, NO_RECORD);
    }
}
//---------------------------------------------------------------------------

void __fastcall TTrackSignalThread::TS(void)     // called using Synchronize()
{
/*
    
    The following is from The DCS O Gauge Companion 2nd Edition, page 55:

    DCS Signal Strength

    DCS signal strength is a number, from 1-10, that's calculated based on the
    number of data packets that the TIU sends and receives back from a PS2 engine
    that's performing a DCS signal strength test.

    During the test, the TIU sends a continuous series of data packets to the PS2
    engine. The PS2 engine is expected to reply to every packet that it receives,
    however, some packets are either not received by the engine or the response is
    not received by the TIU. Regardless, the TIU calculates DCS signal strength by
    counting how many response packets the TIU receives from the PS2 engine out of
    each hundred data packets the TIU sent, and then looks at a sliding scale to
    determine the signal strength that should be reported. The scale is not linear,
    rather, it is such that 87-100 packets equates to a DCS signal strength of “10”,
    80-86 equates to a “9”, and so on.
*/

// pull out the number of packets lost out of TSMAX
int p4loss = TSMAX;

if (Setup->WTIU_WiFi_Mode->Checked) {
    // Handle WTIU ASCII Response
    // pull out the value (maximum 13 characters)
	// p4xxx okay /r/n
    //   ^^^ -- this is the response - the number of packets to the engine out of TSMAX packets sent that were lost.
    //
    int h0;
    int retv = sscanf(PingbyteResp, "p4%d", &h0);
    if (retv == 1) {
        p4loss = h0;
        } else {
        p4loss = TSMAX;
        }
    } else {
    if (Setup->Radio_COM_Mode->Checked) {     // p4 command returns 1 byte of data
        // Handle Radio Binary Response
        // pull out the value
        //A59959AA5559555555565955
        //  E4  5D  04  00  01  08
        //                ^^^^ ------ Indicates there is 1 data byte in the response
        //                    ^^^^ -- this is the response - the number of packets to the engine out of TSMAX packets sent that were lost.
        //
        p4loss = PingbyteResp[5];
        } else {
        p4loss = TSMAX; // incorrect response
        }
    }

if (p4loss > TSMAX) {
    // invalid response value, set to all packets lost and set flag
    p4loss = TSMAX;
    CallingTSScreen->LED->LED->ColorOn = clYellow;
    }
p4count++;
const int p4Val = (TSMAX - p4loss);	// calculate the "X out of TSMAX" score. We display the number of good packets out of TSMAX
p4total += p4Val;
p4ave = p4total / p4count;
CallingTSScreen->FTS->Text =        "  " + String(p4Val) + " out of " + String(TSMAX);

// River Nights     - https://www.heavy.ai/blog/12-color-palettes-for-telling-better-stories-with-your-data
TColor ColorHex[] = {(TColor)0x0000b3, (TColor)0x58117c, (TColor)0xaf2144, (TColor)0xff531a, (TColor)0xe6880d, (TColor)0xc7b700, (TColor)0x5ad45a, (TColor)0x4ee08b, (TColor)0x78dceb};
// update graph with the colors of the rainbow
if (p4Val >= 87) {      // 87-TSMAX = 10
    CallingTSScreen->Signals->DataPoint(ColorHex[7], p4Val);
    } else if (p4Val >= 80) {       // 80-86 = 9
        CallingTSScreen->Signals->DataPoint(ColorHex[6], p4Val);
        } else if (p4Val >= 75) {       // 8
            CallingTSScreen->Signals->DataPoint(ColorHex[5], p4Val);
            } else if (p4Val >= 68) {       // 7
                CallingTSScreen->Signals->DataPoint(ColorHex[4], p4Val);
                } else if (p4Val >= 61) {       // 6
                    CallingTSScreen->Signals->DataPoint(ColorHex[3], p4Val);
                    } else if (p4Val >= 54) {       // 5
                        CallingTSScreen->Signals->DataPoint(ColorHex[2], p4Val);
                        } else if (p4Val >= 47) {       //4
                            CallingTSScreen->Signals->DataPoint(ColorHex[1], p4Val);
                                } else {                        // 3
                                CallingTSScreen->Signals->DataPoint(ColorHex[0], p4Val);
                                }

CallingTSScreen->Signals->Update();

CallingTSScreen->FTSAverage->Text = "  " + String(p4ave) + " out of " + String(TSMAX);

if (DEBUGGINGX(5)) {
   	DB->Debug_Print("Engine " + String(EngineNo) + " - TIU " + String(TIUNo) + " Instant: " + CallingTSScreen->FTS->Text);
	//DB->Debug_Print("Engine " + String(EngineNo) + " - TIU " + String(TIUNo) + " Average: " + CallingTSScreen->FTSAverage->Text);
    }
//
DelayValue = CallingTSScreen->UpdateRateSpin->ValueAsInt * 1000L;
CallingTSScreen->QueryLabel->Caption = "Ping # " + String(PingNum++);
}
//---------------------------------------------------------------------------

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

void __fastcall TTrackSignalThread::ClearCounts()
{
// Always runs in context of the calling thread
//
p4count = 0;	// initialize counts and averages
p4ave = 0;
p4total = 0;
//
PingNum = 1;
}
//---------------------------------------------------------------------------

