//---------------------------------------------------------------------------
/*
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 "Train_Control.h"
#include "Route_Scene_Thread.h"
#include "Debug_Controls.h"
#include "Setup_Control.h"
#include "Comm_Thread.h"

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

void SetACCBit(int Chan, char * Bits);
void SetSWBits(int Chan, char * Bits, int Setting);

#pragma package(smart_init)

extern TCommThread * ptrTIUCommThread;				// pointer to TIU communications thread
extern bool TIUSelection[TIUMAX];                  // Flags to indicate that a TIU is present
extern int AIUCount[TIUMAX];						// Number of AIU connected to each TIU

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

__fastcall TRouteSceneThread::TRouteSceneThread(bool CreateSuspended, TForm * Caller, HWND Parent, int Remote, String FName,
		int RSNumber, int Parameter)
        : TThread(CreateSuspended)
{
// Calling window - right now, just the Main window
CallingScreen = Caller;
// Filename to with Route/Scene information
FileName = FName;
assert(!FileName.IsEmpty());

// Route/Scene and TIU Number
RSNo = RSNumber;
assert(RSNo >= -1 && RSNo <= 256); 	// 1-256 is playback from the Hot Buttons or Route/Scene Window

// parameter : one of the 5 buttons on the Route/Scene Window
iParameter = Parameter;

Finished = false;
// FreeOnTerminate is set so that parent does not have to worry about it
FreeOnTerminate = true;

// Parent Handle - right now, this value is not used
ParentHandle = Parent;
// Remote Number
RemoteNo = Remote;
assert(Remote >= 0 && Remote <= REMOTEMAX);
// Engine Number is not acted upon in the command but must be specified
EngineNo = 1;

if (DEBUGGING) {
    ptrTIUCommThread->PrintItem("Starting Route/Scene Processing - #" + String(RSNo), (TColor) clGreen);
    }

// termination
OnTerminate = &RunOnTerminate;
}
//---------------------------------------------------------------------------
__fastcall TRouteSceneThread::~TRouteSceneThread()
{
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ptrTIUCommThread->PrintItem("TRouteSceneThread:~ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
Finished = true;
}
//---------------------------------------------------------------------------

void __fastcall TRouteSceneThread::Execute()
{
// Calls to Sleep() are ok in this route/scene thread
if (DEBUGGINGX(1)) {
	// print the ThreadId
    ThreadID = GetCurrentThreadId();
    ptrTIUCommThread->PrintItem("TRouteSceneThread: ThreadID " + IntToHex((int)ThreadID, 8), (TColor) clGreen);
    }
if (Terminated) {
	return;					// in case we are terminated
    }

// 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 = "Route_Scene_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(!Terminated) {
	RS();			// Perform the route/scene setup in the thread
    break;			// execute only once
    }
//
Synchronize(CleanUp);	// Perform the cleanup synchronously
return;
}
//---------------------------------------------------------------------------

void __fastcall TRouteSceneThread::Initialize(void)     // called using Synchronize()
{
// make sure that the file exists
if (!FileExists(FileName)) {
    MainForm->DebugMessagePopup("Route/Scene Error", "Route/Scene file not found : " + FileName, NULL, 1000);
    // all done
    Terminate();
    return;
    }
TStringList * SAList;
SAList = new TStringList();	// deleted below
assert (SAList != NULL);	// make sure that construction succeeded
SAList->Clear();
SAList->LoadFromFile(FileName);
SACount = SAList->Count;

for (int Index = 0 ;  Index < SACount ; Index++) {
    // Get the lines into a local String array
    SAString[Index] = SAList->Strings[Index];
    }

if (DEBUGGING) DB->Debug_Print("Switch/Accessory Data Loaded from " + FileName);

delete SAList;
}
//---------------------------------------------------------------------------

void __fastcall TRouteSceneThread::RS(void)     // Runs in the thread
{
//
// build commands to control routes and scenes listed in the Route/Scene Window
//
if ((iParameter == RSACTIVATE || iParameter == RSRESET) && SACount != 0) {
    //
    // Accessories/Switches in the given Route/Scene (RSNo) are activated.
    // or
    // Accessories/Switches in the given Route/Scene (RSNo) are returned to their
    // original states shown on the AIU Controls Window.
    //
    int type, Chan, AIUNo, TIUNo, DefSetting, RouteSetting;
    // Switches
	struct {
		bool flag;
        byte Bits[27];	// 26 characters - 100 bits used
		} SW[TIUMAX] = {{false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"},
				   {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}
                   #if TIUMAX == 8
                   , {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}
                   #endif
                   };
    // Accessories
   	struct {
		bool flag;
        byte Bits[15];	// 14 characters - 50 bits used
		} ACCOn[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#if TIUMAX == 8
                , {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#endif
        		},
                ACCOff[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
                #if TIUMAX == 8
         		, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
         		#endif
         		};
	// Look at each Item in the List
	for (int Index = 0 ;  Index < SACount ; Index++) {
    	// send one command to TIU for each Accessory and Switch
		const int retv = _stscanf(SAString[Index].c_str(),TEXT("%*[ -~]%d,%d,%d,%d,%d,%d"), &type, &Chan, &AIUNo, &TIUNo, &DefSetting, &RouteSetting);
		assert(retv == THCONTROL_COUNT);	// make sure 6 paramters were found
        assert(type <= 1);	// check the limits on the parameters
        assert(Chan <= 10);
        // ignore non-existant TIU and AIU
        if (!TIUSelection[TIUNo - 1]) continue;
        if (AIUNo > AIUCount[TIUNo - 1]) continue;
        assert(DefSetting <= 1);
        assert(RouteSetting <= 1);
        const int ChanCmd = (Chan - 1) + ((AIUNo - 1) * 10);
        const int Setting = (iParameter == RSACTIVATE) ? RouteSetting : DefSetting;
        assert (ChanCmd < 50);	// 50 possible channels per TIU
        if (DEBUGGINGX(3)) {
            ptrTIUCommThread->PrintItem("Index " + String(Index) + " Item : " + SAString[Index] + String(RSNo), (TColor) clGreen);
            }
        // if type == 0, it's an accessory
        	// 50 bits in 14 characters
        	// Setting : 0 = no change
            // Ag command and Setting : 1 = on
            // Ah command and Setting : 1 = off
        if (type == 0) {
        	if (Setting == 1) {
            	ACCOn[TIUNo - 1].flag = true;
				SetACCBit(ChanCmd, ACCOn[TIUNo - 1].Bits);	// Ag command to turn accessories on
                }
            if (Setting == 0) {
            	ACCOff[TIUNo - 1].flag = true;
				SetACCBit(ChanCmd, ACCOff[TIUNo - 1].Bits);	// Ah command to turn accessories off
            	}
			}
        // if type == 1, it's a switch
        	// 100 bits in 26 characters
            // Setting : 0 = "10" = set reverse
            // Setting : 1 = "01" = set normal
        if (type == 1) {
        	SW[TIUNo - 1].flag = true;
            SetSWBits(ChanCmd, SW[TIUNo - 1].Bits, Setting);// Ab command to set the switches to either normal or reverse
     	  	}
		}
	// send a command to each TIU for accessories and for switches
    for (int iTIU = 0; iTIU < TIUMAX ; iTIU++) {
        // Convert value into chars and send command
		char str[64];
		// TIUMAX commands to set the switches to either normal or reverse
		if (SW[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ab%26s", SW[iTIU].Bits);		// "Ab"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
		// TIUMAX commands to turn accessories on
 		if (ACCOn[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ag%14s", ACCOn[iTIU].Bits);	// "Ag"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
		// TIUMAX commands to turn accessories off
 		if (ACCOff[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ah%14s", ACCOff[iTIU].Bits);	// "Ah"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
	    }
	}
//---------------------------------------------------------------------------
if ((iParameter == SETALLSWNORMAL) && SACount != 0) {
    int type, Chan, AIUNo, TIUNo, DefSetting, RouteSetting;
    //
    // All Switches in the given Route/Scene (RSNo) are returned to their NORMAL position
    //
	struct {
		bool flag;
        byte Bits[27];	// 26 characters - 100 bits used
		} SW[TIUMAX] = {{false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"},
				   {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}
                   #if TIUMAX == 8
                   , {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}, {false, "00000000000000000000000000"}
                   #endif
                   };
	// Look at each Item in the List
	for (int Index = 0 ;  Index < SACount ; Index++) {
    	// send one command to TIU for each Accessory and Switch
		const int retv = _stscanf(SAString[Index].c_str(),TEXT("%*[ -~]%d,%d,%d,%d,%d,%d"), &type, &Chan, &AIUNo, &TIUNo, &DefSetting, &RouteSetting);
		assert(retv == THCONTROL_COUNT);	// make sure 6 paramters were found
        assert(type <= 1);	// check the limits on the parameters
        assert(Chan <= 10);
        // ignore non-existant TIU and AIU
        if (!TIUSelection[TIUNo - 1]) continue;
        if (AIUNo > AIUCount[TIUNo - 1]) continue;
        assert(DefSetting <= 1);
        assert(RouteSetting <= 1);
        const int ChanCmd = (Chan - 1) + ((AIUNo - 1) * 10);
        const int Setting = 1;	// Normal
        assert (ChanCmd < 50);	// 50 possible channels per TIU
        // if type == 0, it's an accessory so ignore it
        if (type == 0) {
			continue;
			}
        if (DEBUGGINGX(3)) {
            ptrTIUCommThread->PrintItem("Index " + String(Index) + " Item : " + SAString[Index] + String(RSNo), (TColor) clGreen);
            }
        // if type == 1, it's a switch
        	// 100 bits in 26 characters
            // Setting : 0 = "10" = set reverse
            // Setting : 1 = "01" = set normal
        if (type == 1) {
        	SW[TIUNo - 1].flag = true;
            SetSWBits(ChanCmd, SW[TIUNo - 1].Bits, Setting);// Ab command to set the switches to either normal or reverse
     	  	}
		}
	// send a command to each TIU for switches
    for (int iTIU = 0; iTIU < TIUMAX ; iTIU++) {
        // Convert value into chars and send command
		char str[64];
		// TIUMAX commands to set the switches to either normal or reverse
		if (SW[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ab%26s", SW[iTIU].Bits);	// "Ab"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
	    }
    }
//---------------------------------------------------------------------------
if ((iParameter == SETALLACCYON) && SACount != 0) {
    int type, Chan, AIUNo, TIUNo, DefSetting, RouteSetting;
    //
    //  All Accessories in the given Route/Scene (RSNo) are set to their ON state
    //
   	struct {
		bool flag;
        byte Bits[15];	// 14 characters - 50 bits used
		} ACCOn[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#if TIUMAX == 8
                , {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#endif
        		},
                ACCOff[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
                #if TIUMAX == 8
         		, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
         		#endif
         		};
	// Look at each Item in the List
	for (int Index = 0 ;  Index < SACount ; Index++) {
    	// send one command to TIU for each Accessory and Switch
		const int retv = _stscanf(SAString[Index].c_str(),TEXT("%*[ -~]%d,%d,%d,%d,%d,%d"), &type, &Chan, &AIUNo, &TIUNo, &DefSetting, &RouteSetting);
		assert(retv == THCONTROL_COUNT);	// make sure 6 paramters were found
        assert(type <= 1);	// check the limits on the parameters
        assert(Chan <= 10);
        // ignore non-existant TIU and AIU
        if (!TIUSelection[TIUNo - 1]) continue;
        if (AIUNo > AIUCount[TIUNo - 1]) continue;
        assert(DefSetting <= 1);
        assert(RouteSetting <= 1);
        const int ChanCmd = (Chan - 1) + ((AIUNo - 1) * 10);
        //const int Setting = 1;	// All On
        assert (ChanCmd < 50);	// 50 possible channels per TIU
        // if type == 1, it's a switch ignore it
        if (type == 1) {
        	continue;
     	  	}
        if (DEBUGGINGX(3)) {
            ptrTIUCommThread->PrintItem("Index " + String(Index) + " Item : " + SAString[Index] + String(RSNo), (TColor) clGreen);
            }
        // if type == 0, it's an accessory
        	// 50 bits in 14 characters
        	// Setting : 0 = no change
			// Ag command and Setting : 1 = on
            // Ah command and Setting : 1 = off
        //if (type == 0) {
        	//if (Setting == 1) {
            	ACCOn[TIUNo - 1].flag = true;
				SetACCBit(ChanCmd, ACCOn[TIUNo - 1].Bits);	// Ag command to turn accessories on
            //    }
            //if (Setting == 0) {
            //	ACCOff[TIUNo - 1].flag = true;
			//	SetACCBit(ChanCmd, ACCOff[TIUNo - 1].Bits);	// Ah command to turn accessories off
            //	}
			//}
		}
	// send a command to each TIU for accessories
    for (int iTIU = 0; iTIU < TIUMAX ; iTIU++) {
        // Convert value into chars and send command
		char str[64];
		// TIUMAX commands to turn accessories on
 		if (ACCOn[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ag%14s", ACCOn[iTIU].Bits);	// "Ag"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
		// TIUMAX commands to turn accessories off
		if (ACCOff[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ah%14s", ACCOff[iTIU].Bits);	// "Ah"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
            }
	    }
    }

//---------------------------------------------------------------------------
if ((iParameter == SETALLACCYOFF) && SACount != 0) {
    int type, Chan, AIUNo, TIUNo, DefSetting, RouteSetting;
    //
    //  All Accessories in the given Route/Scene (RSNo) are set to their OFF state 
    //
   	struct {
		bool flag;
        byte Bits[15];	// 14 characters - 50 bits used
		} ACCOn[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#if TIUMAX == 8
                , {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
        		#endif
        		},
                ACCOff[TIUMAX] = {{false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
                #if TIUMAX == 8
         		, {false, "00000000000000"}, {false, "00000000000000"}, {false, "00000000000000"}
         		#endif
         		};
	// Look at each Item in the List
	for (int Index = 0 ;  Index < SACount ; Index++) {
    	// send one command to TIU for each Accessory and Switch
		const int retv = _stscanf(SAString[Index].c_str(),TEXT("%*[ -~]%d,%d,%d,%d,%d,%d"), &type, &Chan, &AIUNo, &TIUNo, &DefSetting, &RouteSetting);
		assert(retv == THCONTROL_COUNT);	// make sure 6 paramters were found
        assert(type <= 1);	// check the limits on the parameters
        assert(Chan <= 10);
        // ignore non-existant TIU and AIU
        if (!TIUSelection[TIUNo - 1]) continue;
        if (AIUNo > AIUCount[TIUNo - 1]) continue;
        assert(DefSetting <= 1);
        assert(RouteSetting <= 1);
        const int ChanCmd = (Chan - 1) + ((AIUNo - 1) * 10);
        //const int Setting = 0;	// All Off
        assert (ChanCmd < 50);	// 50 possible channels per TIU
        // if type == 1, it's a switch ignore it
        if (type == 1) {
        	continue;
     	  	}
        if (DEBUGGINGX(3)) {
            ptrTIUCommThread->PrintItem("Index " + String(Index) + " Item : " + SAString[Index] + String(RSNo), (TColor) clGreen);
            }            
        // if type == 0, it's an accessory
        	// 50 bits in 14 characters
        	// Setting : 0 = no change
            // Ag command and Setting : 1 = on
            // Ah command and Setting : 1 = off
        //if (type == 0) {
        	//if (Setting == 1) {
            //	ACCOn[TIUNo - 1].flag = true;
            //	SetACCBit(ChanCmd, ACCOn[TIUNo - 1].Bits);	// Ag command to turn accessories on
             //   }
			//if (Setting == 0) {
            	ACCOff[TIUNo - 1].flag = true;
				SetACCBit(ChanCmd, ACCOff[TIUNo - 1].Bits);	// Ah command to turn accessories off
            //	}
			//}
		}
	// send a command to each TIU for accessories
    for (int iTIU = 0; iTIU < TIUMAX ; iTIU++) {
        // Convert value into chars and send command
		char str[64];
		// TIUMAX commands to turn accessories on
 		if (ACCOn[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ag%14s", ACCOn[iTIU].Bits);	// "Ag"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
		// TIUMAX commands to turn accessories off
		if (ACCOff[iTIU].flag) {
			(void)snprintf(str, sizeof(str), "Ah%14s", ACCOff[iTIU].Bits);	// "Ah"
			(void)ptrTIUCommThread->Send_String_FIFO(EngineNo, str, iTIU + 1, RemoteNo, 11, AIU_WAIT, RECORD_OK, NULL, NULL, NULL);
			Sleep(AIU_DELAY);
			}
	    }
    }
}
//---------------------------------------------------------------------------

void __fastcall TRouteSceneThread::CleanUp(void)     // called using Synchronize()
{
// not needed
}
//---------------------------------------------------------------------------

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

void SetACCBit(int Chan, char * Bits)
{
// set bit number Chan in the 14 character Bits string
assert(Chan <= TOTALCHANMAX);
const int nib = Chan / 4;
const int Index = 13 - nib;
int cc;
sscanf(&Bits[Index], "%1x", &cc);
const int bb = Chan % 4;
const bit[4] = {0x01, 0x02, 0x04, 0x08};
cc |= bit[bb];
char dd[10];
const int retv = snprintf(dd, sizeof dd, "%1x", cc);
assert (retv == 1);
Bits[Index] = dd[0];
}
//---------------------------------------------------------------------------

void SetSWBits(int Chan, char * Bits, int Setting)
{
// set 2 bits for Chan in the 26 character Bits string using Setting
assert(Chan <= TOTALCHANMAX);
assert(Setting <= 2);
// For Setting == 0 or reverse, set bits to "10" or 0x02
// For Setting == 1 or Normal, set bits to "01" or 0x01
const int nib = Chan / 2;
const int Index = 25 - nib;
int cc;
sscanf(&Bits[Index], "%1x", &cc);
const int bb = (Chan % 2) * 2;	// left shift amount
cc |= ((Setting == 0)? 0x02 : 0x01) << bb ;
char dd[10];
const int retv = snprintf(dd, sizeof dd, "%1x", cc);
assert (retv == 1);
Bits[Index] = dd[0];
}
//---------------------------------------------------------------------------
