//---------------------------------------------------------------------------
/*
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>
#pragma hdrstop

#include "Train_Control.h"
#include "Timer_Thread.h"
#include "Debug_Controls.h"
#include "PC_Thread.h"
#include "Prog_Control.h"
#include "Comm_Thread.h"

#pragma package(smart_init)

//
// define DEBUGTIMER to print out timer information as this thread runs
//#define DEBUGTIMER
//

//---------------------------------------------------------------------------
extern TCommThread * ptrTIUCommThread;		// pointer to TIU communications thread
extern int PC_Tenths;						// RunTimeinTenths counter (system wide)
extern int ptrPC_Tenths[PCMAX];				// RunTimeinTenths counter in each Program Control window
extern TAbNumEdit *ptrPC_ETime[PCMAX];		// pointer(s) to ETime control in each Program Control window
//---------------------------------------------------------------------------
//   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 TMyTimerThread::UpdateCaption()
//      {
//        Form1->Caption = "Updated in a thread";
//      }
//---------------------------------------------------------------------------
__fastcall TMyTimerThread::TMyTimerThread(bool CreateSuspended, TForm * Callingthis, int PCNo)
	: TThread(CreateSuspended)
{
// PCNo and Callingthis are not used
assert(PCNo == 0);
assert(Callingthis == NULL);

Finished = false;			// Once started, the timer thread is not terminated until
							// the main RTC program terminates
// FreeOnTerminate is set so that parent does not have to worry about it							
FreeOnTerminate = true;

// termination
OnTerminate = &RunOnTerminate;
PC_Tenths = 0;	// on start clear the global time clock

Priority = tpTimeCritical;
}
//-----------------------------------------------------------
#ifdef DEBUGTIMER
static LARGE_INTEGER StartTime, StartFrequency;
#endif
//-----------------------------------------------------------
void __fastcall TMyTimerThread::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("TMyTimerThread: 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 = "Timer_Thread";
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
    RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD),
        (DWORD*)&info);
    }
__except (EXCEPTION_CONTINUE_EXECUTION) {
    }

// Moved from Constructor
// Create unnamed waitable timer
MyTimer = CreateWaitableTimer(NULL, false, NULL);	// Autoreset --> the timer is a synchronization timer
assert(MyTimer);	// check if CreateWaitableTimer failed

ResetTimerEvent = CreateEvent(NULL, false, false, NULL);	// Auto Reset - nonsignaled initial state
assert(ResetTimerEvent);	// check if CreateEvent failed
//
//-----------------------------------------------------------
#ifdef DEBUGTIMER
//
QueryPerformanceFrequency(&StartFrequency);
QueryPerformanceCounter(&StartTime);
//
// We now have the elapsed number of ticks, along with the
// number of ticks-per-second. We use these values
// to convert to the number of elapsed microseconds.
// To guard against loss-of-precision, we convert
// to microseconds *before* dividing by ticks-per-second.
//
StartTime.QuadPart *= 1000000;
StartTime.QuadPart /= StartFrequency.QuadPart;
#endif
//-----------------------------------------------------------

// https://docs.microsoft.com/en-us/windows/desktop/sync/using-waitable-timer-objects
// http://techiethings.blogspot.com/2010/11/waitabletimer-in-c.html

// both of the below methods to set the time work correctly
LARGE_INTEGER liDueTime;
liDueTime.QuadPart = 0L;

TenthSecondCounter = 0;

HANDLE handles[] = { MyTimer, ResetTimerEvent };
DWORD dwretv;
#ifdef COMPILESBUTDOESNOTWORK
asm{
int 0x03
}
#endif
// start the waitable Timer for 100 milliseconds and continue on if it starts up OK.
bool bTimerretv = SetWaitableTimer(MyTimer, &liDueTime, 100, NULL, NULL, false);
if (bTimerretv) {
    // Timer is running OK
    while(!Terminated) {
        //
        dwretv = WaitForMultipleObjects(2, handles, FALSE, INFINITE);

        if (dwretv == WAIT_FAILED) {	// unknown failure?
	        Terminate();
            break;	// time to terminate
            }

        //------------------------  Timer Event -----------------------------------
        if (dwretv == WAIT_OBJECT_0 + 0) {	// MyTimer Event Signaled ?
            //-----------------------------------------------------------
            #ifdef DEBUGTIMER
                LARGE_INTEGER CurrentTime, Frequency;
                //
                QueryPerformanceFrequency(&Frequency);
                QueryPerformanceCounter(&CurrentTime);
                //
                // We now have the elapsed number of ticks, along with the
                // number of ticks-per-second. We use these values
                // to convert to the number of elapsed microseconds.
                // To guard against loss-of-precision, we convert
                // to microseconds *before* dividing by ticks-per-second.
                //
                CurrentTime.QuadPart *= 1000000;
                CurrentTime.QuadPart /= Frequency.QuadPart;
            #endif
            //-----------------------------------------------------------
            //
            // bump Run Time in Tenths counter for all of the running PC Threads
            PC_Tenths++;
            // bump each Run Time in Tenths counter for each running PC Thread
            for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
                if (ptrPC_ETime[iPCNo - 1] != NULL) {
                    ptrPC_Tenths[iPCNo - 1]++;
                    }
                }

        	TenthSecondCounter++;

            if (TenthSecondCounter == 10) {
            	TenthSecondCounter = 0;
                //-----------------------------------------------------------
                #ifdef DEBUGTIMER
                if (DEBUGGING) {
                	// print timer information in milliseconds
                    ptrTIUCommThread->PrintItem("TMyTimerThread: Perf Counter (ms) " + String((CurrentTime.QuadPart - StartTime.QuadPart)/1000) +
                        " " + String(RunTimeinTenths * 100.0), (TColor) clGreen);
                    }
                #endif
                //-----------------------------------------------------------
                for (int iPCNo = 1 ; iPCNo <= PCMAX ; iPCNo++) {
                    if (ptrPC_ETime[iPCNo - 1] != NULL) {
                		// bump the ETime counter for the running PC Threads
                        ptrTIUCommThread->Bump(ptrPC_ETime[iPCNo - 1]);	// once per second
                	   	}
                	}
            	}
            assert(TenthSecondCounter <= 10);			// extra counts ?
            continue;
            }
		//------------------------  Reset Timer Event -----------------------------------
        if (dwretv == WAIT_OBJECT_0 + 1) {	// Reset Event Signaled ?
	        if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TMyTimerThread: ResetTimerEvent");
           	CancelWaitableTimer(MyTimer);
            TenthSecondCounter = 0;
            continue;
        	}
        // there should not be any other event types
        assert(false);	// unexpected event
        }
    } else {
    assert(false);		// timer failed
    }
    
// delete allocated data areas
if (MyTimer) {
	CancelWaitableTimer(MyTimer);
	CloseHandle(MyTimer);
    MyTimer = NULL;
    }
}
//---------------------------------------------------------------------------
__fastcall TMyTimerThread::~TMyTimerThread(void)
{
//
// Destructor
//
if (DEBUGGINGX(1)) ptrTIUCommThread->PrintItem("TMyTimerThread::~TMyTimerThread ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
if (ResetTimerEvent != INVALID_HANDLE_VALUE) CloseHandle(ResetTimerEvent);	// not really needed - the system closes the handle automatically when the process terminates
ResetTimerEvent = INVALID_HANDLE_VALUE;
Finished = true;			// Once started, the timer thread is not terminated until
							// the main RTC program terminates
}
//---------------------------------------------------------------------------

void __fastcall TMyTimerThread::Reset()
{
//
// Reset the timer - runs in context of the calling thread
//
SetEvent(ResetTimerEvent);
}
//---------------------------------------------------------------------------

void __fastcall TMyTimerThread::StopRequest()
{
//
// Terminate the timer - runs in context of the calling thread
//
Terminate();
}
//---------------------------------------------------------------------------

void __fastcall TMyTimerThread::RunOnTerminate(TObject * Sender)     // called Automatically using Synchronize()
{
//if (!MainForm) return;	// sometimes the assignment below gets an exception during [Exit]
//if (!MainForm->TimerThreadLED) return;
//MainForm->TimerThreadLED->Checked = false;		//turn off Debug LED
}
//---------------------------------------------------------------------------

