#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include <time.h>
#include <bncs_datetime.h>
#include <ccConfig.h>
#include "Autorot.h"

#define MAIN_PNL	1

// TIMER process definitions
#define STARTUP_TIMER	1
#define STARTUP_TIMER_TIMEOUT 2000
// Definitions for record startup process
#define RECORD_START_TIMER	2
#define PREVIEW_WAIT_TIME	500
#define RECORD_TIMOUT_COUNT 20
// Misc timers
#define TRANSPORT_CMD_CHANGE_TIMER 3
#define TRANSPORT_RESPONSE_TIME 3000	// 3 seconds to respond
#define MEDIA_STATUS_UPDATED 4
#define MEDIA_STATUS_UPDATED_TIME 2000
#define CHECK_INFODRIVER_LOCAL_TIMER	5
#define CHECK_INFODRIVER_LOCAL_TIME	30000
#define COUNTDOWN_WAIT_TIMER 6
#define COUNTDOWN_WAIT_TIMEOUT 2000	//2 seconds wait before starting new recording

// Define the infodriver slot used to test for internal operation mode
#define INFORDRIVER_TEST_SLOT 4096

// Define some constants for working with the HyperDeck Infodriver
#define HYPERDECK_MAX_SLOTS 100
#define HYPERDECK_COMMS 1
#define HYPERDECK_SPEED 2
#define HYPERDECK_TRANSPORT 3
#define HYPERDECK_LOOP 4
#define HYPERDECK_RECORD_NAMED 5
#define HYPERDECK_VIDEOFORMAT 6
#define HYPERDECK_VIDEOINPUT 7
#define HYPERDECK_FILEFORMAT 8
#define HYPERDECK_ACTIVESLOT 9
#define HYPERDECK_CURRENTCLIP 10
#define HYPERDECK_TIMECODE 11
#define HYPERDECK_SLOT01_STATUS 12
#define HYPERDECK_SLOT01_NAME 13
#define HYPERDECK_SLOT01_REC_TIME 14
#define HYPERDECK_SLOT02_STATUS 15
#define HYPERDECK_SLOT02_NAME 16
#define HYPERDECK_SLOT02_REC_TIME 17
#define HYPERDECK_CLIP_COUNT 50

// Studio state values
#define STUDIO_NEITHER 0
#define STUDIO_REHEARSE 1
#define STUDIO_TRANSMIT 2
#define STUDIO_NETWORK 3

#define LOGGING_NAME_AND_FOLDER "Autorot"

// Error status values.
// 0	AUTOROT_ERROR_OK			Normal responses, system normal
// 1	AUTOROT_ERROR_BADCOMM		HyperDeck driver reports comms issue with HyperDeck unit.
// 2	AUTOROT_ERROR_NOVIDEO		No video input detected when attempt to record
// 3	AUTOROT_ERROR_NOMEDIA		No media cards detected, unable to record
// 4	AUTOROT_ERROR_UNKNOWN		Unable to start recording - unknown reason.
// 5	AUTOROT_ERROR_NOTRANSPORT	Transport state chage commands not being actioned
// 6	AUTOROT_ERROR_INFOLOCAL		Infodriver is operating in local mode. Assume Hyperdeck driver not running
// 7	AUTOROT_ERROR_SHORTREC		A WARNING that there is less than 750 seconds record time when start record

#define AUTOROT_ERROR_OK	0
#define AUTOROT_ERROR_BADCOMM	1
#define AUTOROT_ERROR_NOVIDEO	2
#define AUTOROT_ERROR_NOMEDIA	3
#define AUTOROT_ERROR_UNKNOWN	4
#define AUTOROT_ERROR_NOTRANSPORT	5
#define AUTOROT_ERROR_INFOLOCAL	6
#define AUTOROT_ERROR_SHORTREC	7
// Constant to define warning threshold of low media. Assume programme duration of about 12 minutes
#define SHORTREC_MINIMUM_TIME	750

// Macro to make our class visible to the outside world
EXPORT_BNCS_SCRIPT(Autorot)


// >>>>>>>>>>
// Constructor - equivalent to ApplCore STARTUP
Autorot::Autorot(bncs_client_callback * parent, const char * path) : bncs_script_helper(parent, path)
{
	// Open logging
	myLog.setLogName(LOGGING_NAME_AND_FOLDER);	// Create the log folder if it does not exist
	if (myLog.openLogFile())	
	{
		myLog.write("Log opened");	// Open or create and open the log file. Then add a message
	}	

	panelShow(MAIN_PNL, "status.bncs_ui");

	initVariables();	// Initialise module variables

	timerStart(STARTUP_TIMER, STARTUP_TIMER_TIMEOUT);
}


// >>>>>>>>>>
// Destructor - equivalent to ApplCore CLOSEDOWN
Autorot::~Autorot()
{
	// Delete any revertive registrations
	if (hdk.revertReg) {
		infoUnregister(hdk.driver);	// Unregister
		initCommSlot(&hdk);			// Clear all values in data object
	}

	if (rxTx.revertReg) {
		infoUnregister(rxTx.driver);
		initCommSlot(&rxTx);
	}
}


// >>>>>>>>>>
// All button pushes and notifications come here
void Autorot::buttonCallback(buttonNotify *b)
{
	// No active buttons on the automatic panel.
}


// >>>>>>>>>>
// All revertives come here
int Autorot::revertiveCallback(revertiveNotify *r)
{
	// Expect revertives from two devices only - hyperdeck and studio status
	if (hdk.revertReg) {
		if (r->device() == hdk.driver) {
			haveHdkRevert = true;		// Revertives have been seen from Hyperdeck infodriver
			hdkRevertProcess(r->index(), r->sInfo());
		}
	}

	if (rxTx.revertReg) 
	{
		if ((r->device() == rxTx.driver) && (r->index() == rxTx.offset))
		{
			haveStudioStatRevert = true;	// Revertives seen from studio status
			// Studio rehearse/tx status returned. Only 4 values should ever occur:
			// 0 = neither rehearse nor transmit, 1 = Rehearse, 2 = Transmit, 3 = Network
			// We are really only interested when the value changes, or is the first
			// value received. Initial state for status value is set negative to force 
			// recognition of report 1.
			int nowval = r->sInfo().toInt();	// Extract the current status.

			if ((nowval >= STUDIO_NEITHER) && (nowval <= STUDIO_NETWORK))	// Validate the range
			{
				rehTx.current = nowval;
				if (rehTx.current != rehTx.previous)
				{
					
					if ((rehTx.current == STUDIO_TRANSMIT) || (rehTx.current == STUDIO_NETWORK) && ((rehTx.previous==STUDIO_REHEARSE)||(rehTx.previous==STUDIO_NEITHER))&&doProcess)
					{
						// Studio has entered TX state. Initiate record if not recording
						if (tState.current != ts::record)
						{
							// Check for mounted media, log error if none present. Variable
							// 'mediaIsAvailable' is updated by any status change after allowing
							// transient states to settle
							if (!mediaIsAvailable)
							{
								myLog.write("Recording requested but no record media available");
								reportErrorState(AUTOROT_ERROR_NOMEDIA, myLog.lastLogItem());
							}
							// Check there is at least 750 seconds of record available - otherwise flag error of low media.
							if (mediaIsAvailable)
							{
								if ((hdkStat.mediaTime1 + hdkStat.mediaTime2) <= SHORTREC_MINIMUM_TIME)
								{
									myLog.write("WARNING - less than 12 minutes record time available");
									reportErrorState(AUTOROT_ERROR_SHORTREC, myLog.lastLogItem());
								}
							}

							// Trigger timer to test that command causes transport state change
							timerStart(TRANSPORT_CMD_CHANGE_TIMER, TRANSPORT_RESPONSE_TIME);
							transpCmdTimerActive = true;
							if (tState.current == ts::preview)
							{
								recordTimeout = 0;		// Reset timeout counter
								makeRecording = true;	// Set control flag
								timerStart(RECORD_START_TIMER, PREVIEW_WAIT_TIME);
								startRecording();		// Try to start the recording.
							}
							else if (tState.current == ts::stop)
							{
								// Enable preview
								makeRecording = true;
								startRecording();	// Switch to preview status
							}
							else
							{
								makeRecording = true;
								hyperDeckStop();	// Issue stop command
							}
						}
					}


					// If there is a recording status and have left Transmit or Network state stop the recording
					if (((rehTx.previous == STUDIO_TRANSMIT)||(rehTx.previous==STUDIO_NETWORK)) && (rehTx.current == STUDIO_REHEARSE))
					{
						if (hdkStat.transportState == ts::record)
						{
							hyperDeckStop();	// Issue stop command
							myLog.write("Recording stopped");
						}
					}
					// If we go straight from TX to Network then the current recording should be stopped and a new recording should be initiated
					if ((rehTx.current == STUDIO_NETWORK) && (rehTx.previous == STUDIO_TRANSMIT))
					{
						if (hdkStat.transportState == ts::record)
						{
							hyperDeckStop();	// Issue stop command
							myLog.write("Recording stopped");
						}
						timerStart(COUNTDOWN_WAIT_TIMER, COUNTDOWN_WAIT_TIMEOUT);
						
					}

					// Update the status panel mimic. Label names are St_Stat_01 etc. So use status value
					// number as selection index. Use statesheet WN_Studio_OFF and alarm_ok (selected)
					if (rehTx.previous >= 0) // Ignore the startup condition of previous == -1
					{
						textPut("statesheet", "WN_Studio_OFF", MAIN_PNL, bncs_string("St_Stat_%1").arg(rehTx.previous + 1, '0', 2));
					}
					textPut("statesheet", "alarm_ok", MAIN_PNL, bncs_string("St_Stat_%1").arg(rehTx.current + 1, '0', 2));

					rehTx.previous = rehTx.current;

				}	// End if (rehTx.current != rehTx.previous)
			}		// End if ((nowval >= STUDIO_NEITHER) && (nowval <= STUDIO_TRANSMIT))
		}			// End if ((r->device() == rxTx.driver) && (r->index() == rxTx.offset))
	}				// End if (rxTx.revertReg) 

	return 0;
}


// >>>>>>>>>>
// All database name changes come back here
void Autorot::databaseCallback(revertiveNotify *r)
{
	// No database changes are relevant to this automatic panel.
}


// >>>>>>>>>>
// All parent notifications come here i.e. when this script is just one 
// component of another dialog then our host might want to tell us things
bncs_string Autorot::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if (p->value() == "all")
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string( "" );
			return sl.toString( '\n' );
		}
	}

	else if (p->command() == "instance" && p->value() != my_instance)
	{	
		// This code block _should_ be invoked as part of panel startup when panel host
		// sends a (composite) instance value.
		if (newInstance(p->value()) == 0)
		{
			// The composite instance was found and all elements sucessfully extracted.
			// Need to register for revertives from the Hyperdeck driver and the Studio
			// Rehearse/Transmit information.
			haveInstance = true;	// Set flag indicating we have a valid instance

			// Registration for the hyperdeck is in three parts - the block of slots between
			// HYPERDECK_COMMS (slot 1) and HYPERDECK_SLOT02_REC_TIME (slot 17). We also register
			// for information on the number of clips on the active disk HYPERDECK_CLIP_COUNT
			// (slot 50), and the maximum slot number used for ping response testing. This 
			// automatic has no interest in the names of any clips. Remember HyperDeck uses blocks
			// of 100 slots, so must add base offset to the slot range for the device. 
			//
			// Registration for the studio rehearse/transmit data is a single slot identified
			// in the instance data.
			infoRegister(hdk.driver, hdk.offset + HYPERDECK_COMMS, hdk.offset + HYPERDECK_SLOT02_REC_TIME, false);
			infoRegister(hdk.driver, hdk.offset + HYPERDECK_CLIP_COUNT, hdk.offset + HYPERDECK_CLIP_COUNT, true);
			infoRegister(hdk.driver, INFORDRIVER_TEST_SLOT, INFORDRIVER_TEST_SLOT, true);
			hdk.revertReg = true;	// Note the active registration

			// Trigger the timer that does a background test on the infodriver last slot. If get a 
			// reply from the infodriver slot to a slot write it is not operating in EXTERNAL mode
			// so may have lost the Hyperdeck driver
			timerStart(CHECK_INFODRIVER_LOCAL_TIMER, CHECK_INFODRIVER_LOCAL_TIME);

			infoRegister(rxTx.driver, rxTx.offset, rxTx.offset, false);
			rxTx.revertReg = true;

			// Also clear down any error report flag and error message
			reportErrorState(AUTOROT_ERROR_OK, "");
			errorFlagActive = false;
			
			// Poll for the initial states
			infoPoll(hdk.driver, hdk.offset + HYPERDECK_COMMS, hdk.offset + HYPERDECK_SLOT02_REC_TIME);
			infoPoll(hdk.driver, hdk.offset + HYPERDECK_CLIP_COUNT, hdk.offset + HYPERDECK_CLIP_COUNT);
			infoPoll(rxTx.driver, rxTx.offset, rxTx.offset);
		}
	}

	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if( p->command() == "_events" )
	{	// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;

		sl << "notify=*";		
		
		return sl.toString( '\n' );
	}

	// ***** CONNECTIONS COMMANDS HELPER LIST *****
	else if( p->command() == "_commands" )
	{	// Helper-list of any commands/parameters you might want to set at run-time
		bncs_stringlist sl;
		
		sl << "myParam=[value]";
		
		return sl.toString( '\n' );
	}

	return "";
}


// >>>>>>>>>>
// Timer events come here
void Autorot::timerCallback(int id)
{
	switch (id)
	{
	case STARTUP_TIMER:
		timerStop(STARTUP_TIMER);
		initialWaitDone = true;		// Startup delay expired - may be ready to start processing

		// Check if we have validity flags for all processes and set processing run flag accordingly
		mediaIsAvailable = checkMediaAvailable();
		doProcess = initialWaitDone && haveInstance && haveHdkRevert && haveStudioStatRevert && initialWaitDone;
		if (!doProcess)
		{
			myLog.write("Failed to enable processing after startup tests");
			textPut("text", "Startup initialisation failure", MAIN_PNL, "logger");
		}
		else
		{
			bncs_dateTime dt = bncs_dateTime::now();
			bncs_string infoText = bncs_string(dt.toString("dd/MM/yyyy hh:mm:ss.zzz ")) + "Startup initialisation complete.";
			textPut("text", infoText, MAIN_PNL, "logger");
		}
		break;

	case RECORD_START_TIMER:
		// Timeout interval to allow preview enable has fired. Issue record command.
		startRecording();	// Tell device to start recording
		recordTimeout++;	// Increment timeout counter
		videoInAlarm(!haveVideoInput());

		if (recordTimeout > RECORD_TIMOUT_COUNT)
		{
			timerStop(RECORD_START_TIMER);
			makeRecording = false;
			if (haveVideoInput())
			{
				myLog.write("Unable to start recording. Reason unknown. Input video detected");
				reportErrorState(AUTOROT_ERROR_UNKNOWN, myLog.lastLogItem());
			}
			else
			{
				myLog.write("Unable to start recording. No input video detected");
				reportErrorState(AUTOROT_ERROR_NOVIDEO, myLog.lastLogItem());
			}
		}	
		break;

	case TRANSPORT_CMD_CHANGE_TIMER:
		// If come to this code a transport command timed out. Kill timer but report/log the error
		timerStop(TRANSPORT_CMD_CHANGE_TIMER);
		myLog.write("Hyperdeck transport not responding to commands");
		reportErrorState(AUTOROT_ERROR_NOTRANSPORT, myLog.lastLogItem());
		break;

	case MEDIA_STATUS_UPDATED:
		// One or both disk drive mount status changed to trigger this timer id. On timeout
		// check the mounted status and update module variable 'mediaIsAvailable'
		timerStop(MEDIA_STATUS_UPDATED);
		mediaIsAvailable = checkMediaAvailable();
		break;

	case CHECK_INFODRIVER_LOCAL_TIMER:
		// On each tick of this timer we try writing to the designated test slot in the HyperDeck
		// infodriver. If this infodriver is operating in INTERNAL mode a respose is sent to the
		// network and we can assume that the HyperDeck driver module is not running.
		if (!pingRx)
		{
			// No response means normal operations
			if (internErrMode)
			{
				// We _were_ getting ping replies - so clear internErrMode and report error has cleared
				myLog.write("HyperDeck Infodriver is external, HyperDeck driver controlling Infodriver. Status OK");
				internErrMode = false;
				reportErrorState(AUTOROT_ERROR_OK, "Infodriver issue cleared");
			}
		}

		pingRx = false;
		infoWrite(hdk.driver, selectPing ? "ping" : "pong", INFORDRIVER_TEST_SLOT);
		selectPing = !selectPing;
		break;
	case COUNTDOWN_WAIT_TIMER:
		// Studio has entered TX state. Initiate record if not recording
		if (tState.current != ts::record)
		{
			// Check for mounted media, log error if none present. Variable
			// 'mediaIsAvailable' is updated by any status change after allowing
			// transient states to settle
			if (!mediaIsAvailable)
			{
				myLog.write("Recording requested but no record media available");
				reportErrorState(AUTOROT_ERROR_NOMEDIA, myLog.lastLogItem());
			}
			// Check there is at least 750 seconds of record available - otherwise flag error of low media.
			if (mediaIsAvailable)
			{
				if ((hdkStat.mediaTime1 + hdkStat.mediaTime2) <= SHORTREC_MINIMUM_TIME)
				{
					myLog.write("WARNING - less than 12 minutes record time available");
					reportErrorState(AUTOROT_ERROR_SHORTREC, myLog.lastLogItem());
				}
			}

			// Trigger timer to test that command causes transport state change
			timerStart(TRANSPORT_CMD_CHANGE_TIMER, TRANSPORT_RESPONSE_TIME);
			transpCmdTimerActive = true;
			if (tState.current == ts::preview)
			{
				recordTimeout = 0;		// Reset timeout counter
				makeRecording = true;	// Set control flag
				timerStart(RECORD_START_TIMER, PREVIEW_WAIT_TIME);
				startRecording();		// Try to start the recording.
			}
			else if (tState.current == ts::stop)
			{
				// Enable preview
				makeRecording = true;
				startRecording();	// Switch to preview status
			}
			else
			{
				makeRecording = true;
				hyperDeckStop();	// Issue stop command
			}
		}
		timerStop(COUNTDOWN_WAIT_TIMER);
		break;


	default:	// Unhandled timer event
		timerStop(id);
		myLog.write(bncs_string("Autorot: Unexpected timer triggered. Timer ID = %1").arg(id));
		break;
	}	// End => switch (id)
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

// A function that is called during module startup to set initial values of variables for the modules use.
void Autorot::initVariables(void)
{
	my_instance = "";			// Holds the name of the configuring instance used by this automatic panel
	initCommSlot(&hdk);
	initCommSlot(&rxTx);
	initCommSlot(&errFlag);
	initCommSlot(&errMsg);

	// Get the stem name part of the record filename from objects.xml.
	stemName = getObjectSetting("autorot_stem", "stemName");
	if (stemName == "")
	{
		stemName = "StC_Rot_";	// Catch all in case object entry is missing
		myLog.write("Object entry for \"autorot_stem\" not found. Using default stem name \"StC_RoT_\"");
	}

	recordTimeout = 0;
	rehTx.current = 0;			// Initialise the change of studio Rehearse/Transmit detection stores
	rehTx.previous = -1;
	makeRecording = false;		// Flag that is true when trying to start a recording
	transpCmdTimerActive = false;
	mediaIsAvailable = false;	// True when at least one media card is mounted
	errorFlagActive = false;	// True when external error report is active
	errorStateValue = 0;		// The value is the type of error sent to external display
	
	selectPing = false;			// Used to alternate between two texts to check hyperdeck infodriver mode
	pingRx = false;				// Set true if receive a ping response from infodriver -> infodrier is INTERNAL
	internErrMode = false;

	haveInstance = false;		// True when a _valid_ instance has been received
	haveHdkRevert;				// Set true when revertives seen from Hyperdeck Infodriver
	haveStudioStatRevert;		// Set true when revertives seen for studio rehearse/transmit status
	initialWaitDone = false;	// Set true when the initial timer timeout has completed
	doProcess = false;			// Set true after all startup stabilisations are complete

	// Initialise HyperDeck status object to startup values
	hdkStat.comState.current = 0;
	hdkStat.comState.previous = 0;
	hdkStat.playSpeed = 0;
	hdkStat.actSlot = 0;
	hdkStat.mediaTime1 = 0;
	hdkStat.mediaTime2 = 0;
	hdkStat.transportState = 0;
	hdkStat.disk1Status.current = 0;
	hdkStat.disk1Status.previous = 0;
	hdkStat.disk2Status.current = 0;
	hdkStat.disk2Status.previous = 0;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// void initCommSlot(commSlots *ds) is a support function that sets the data in a commSlot to zero.
void Autorot::initCommSlot(commSlots *ds)
{
	ds->driver = 0;
	ds->offset = 0;
	ds->revertReg = false;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// newInstance(newComposite) is called when this app receives a new instance value from it's parent/host.
// The Instance name passed as parameter newInstance is the id of a composite instance with entries that
// define how this automatic connects to the record device and how it gets it's source of Rehearse/Transmit
// status. Two further entries define how this automatic can signal other applications, such as the studio
// record control panel that there is a problem, and return a descriptive text message.
// 
// The function returns 0 on sucessfull extraction of instance data, and 1 if there is an error.
//
// <instance composite="yes" id="Autorot">
//    <element id="Hyperdeck" instance="Hyperdeck_01" />
//    <element id="RehTxStat" instance="StC TX State" />
//    <element id="ErrorStat" instance="StC Autorot Error Flag" />
//    <element id="ErrorMsg"  instance="StC Autorot Error Text" />
// </instance>
//
// The first group id defines the instance that has the infodriver ID and offset that provides the
// HyperDeck unit that makes the RoT. There is an example of this instance below.
//    <instance composite="no" id="Hyperdeck_01" ref="device=931,offset=0" address="192.168.42.21" type="Hyperdeck" alt_id="Hyperdeck 1" />
// 
// The second group id defines the infodriver device and offset that has the Studio Transmission status.
//    <instance composite="no" id="StC TX State" ref="device=661,offset=3" type="Studio Status" location="CTA"  alt_id="Studio C Reh/Tx Status" />
//
// The third group is the infodriver ID and offset used to signal problems in the Automatic. This slot
// stores a simple good ("0") or error "1" value. 
//    <instance composite="no" id="StC Autorot Error Flag" ref="device=661,offset=9" type="Alarm Info" location="CTA"  alt_id="Studio C AutoRoT Status Flag" />
//
// The fourth group is the infodriver ID and offset used to send text error reports for other panels
// to process. problems in the Automatic. 
//    <instance composite="no" id="StC Autorot Error Text" ref="device=661,offset=10" type="Alarm Info" location="CTA"  alt_id="Studio C AutoRot Error Text" />
// ----------------------------------------------------------------------------------------------------------
int Autorot::newInstance(bncs_string newComposite)
{
	const int maxElements = 4;	// Number of instance elements we expect
	int num_inst = 0;		// A counter used during instance 
	int ele[maxElements] = { -1, -1, -1, -1 };
	int hdk_drv = 0;		// Hyperdeck infodriver id
	int hdk_off = 0;		// Hyperdeck infodriver offset 
	int rehTx_drv = 0;		// Infodriver that signals studio Rehearse/Transmit status.
	int rehTx_off = 0;		// Infodriver slot number that holds the Reh/Tx status
	int errFlg_drv = 0;
	int errFlg_off = 0;
	int errMsg_drv = 0;
	int errMsg_off = 0;
	bool inst0, inst1, inst2, inst3;
	
	bncs_string inst_names[maxElements];	// Holds the instance names found in the complex instance
	bncs_string inst_ids[maxElements];		// Holds the id name of the instances in the complex instance.
	
	// Declare a lambda function to help sort data. Finds position of tagName in array tagvals.
	auto id = [](const bncs_string tagvals[maxElements], const bncs_string tagName) {
		if (tagvals[0] == tagName) return 0;
		else if (tagvals[1] == tagName) return 1;
		else if (tagvals[2] == tagName) return 2;
		else if (tagvals[3] == tagName) return 3;
		return -1;
	};	// END LAMBDA FUNCTION

	bncs_config composite("instances." + newComposite);	// Try to find the entry in instances.xml
	myLog.write("Composite instance name is " + newComposite);

	if (!composite.isValid())
	{	
		// The composite instance does not exist. Report the problem
		myLog.write("Unknown composite instance name " + newComposite);
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;	// Return with error set.
	}
	if (composite.attr("composite").lower() != "yes")
	{	// Name is not a composite instance.
		myLog.write("Instance name " + newComposite + " is not a composite instance");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}
	// Check there are four child entries
	while (composite.isChildValid())
	{
		if (num_inst < maxElements) 
		{	// Save first maxElements entries in the composite instance
			inst_ids[num_inst] = composite.childAttr("id");
			inst_names[num_inst] = composite.childAttr("instance");
		}
		num_inst++;
		composite.nextChild();
	}

	if (num_inst != maxElements)
	{
		myLog.write("Expected 4 individual instance elements, but found " + bncs_string(num_inst));
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Now extract the 4 instances of interest to this module. The user may have created these in any order
	// in the complex instance. We need to know which instance is the specific one for our requirements.
	// The individual instances have id names that are one of "Hyperdeck", "RehTxStat", "ErrorStat" and
	// "ErrorMsg". Use the lambda fumction defined earlier to find the position of each id in the inst_ids
	// array. The index is stored in array ele[] where ele[0] is for the Hyperdeck, ele[1] is for the 
	// RehTxState, ele[2] is for the ErrorStat and ele[3] is for the ErrorMsg. After the search if any entry
	// has the value -1 the expected tag was not found.
	ele[0] = id(inst_ids, "Hyperdeck");
	ele[1] = id(inst_ids, "RehTxStat");
	ele[2] = id(inst_ids, "ErrorStat");
	ele[3] = id(inst_ids, "ErrorMsg");

	if ((ele[0] == -1) || (ele[1] == -1) || (ele[2] == -1) || (ele[3] == -1))
	{
		if (ele[0] == -1) myLog.write("Element with id field 'Hyperdeck' is missing");
		if (ele[1] == -1) myLog.write("Element with id field 'RehTxStat' is missing");
		if (ele[2] == -1) myLog.write("Element with id field 'ErrorStat' is missing");
		if (ele[3] == -1) myLog.write("Element with id field 'ErrorMsg' is missing");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	bncs_config hdk_inst("instances." + inst_names[ele[0]]);
	bncs_config studioRehTx("instances." + inst_names[ele[1]]);
	bncs_config arot_err("instances." + inst_names[ele[2]]);
	bncs_config arot_msg("instances." + inst_names[ele[3]]);

	// Check each of the instances are valid (were found).
	if (!hdk_inst.isValid())
	{
		myLog.write("Unable to find instance for Hyperdeck properties - provided instance name was " + inst_names[ele[0]]);
		textPut("text", "[Invalid] " + inst_names[ele[0]] , MAIN_PNL, "Hdk_InstanceName");
		textPut("text", "", MAIN_PNL, "Hdk_InfoID");
		textPut("text", "", MAIN_PNL, "Hdk_InfoOffset");
	}
	if (!studioRehTx.isValid())
	{
		myLog.write("Unable to find instance for Studio Tx status - provided instance name was" + inst_names[ele[1]]);
		textPut("text", "[Invalid] " + inst_names[ele[1]], MAIN_PNL, "RehTx_InstanceName");
		textPut("text", "", MAIN_PNL, "RehTx_InfoID");
		textPut("text", "", MAIN_PNL, "RehTx_InfoOffset");
	}
	if (!arot_err.isValid())
	{
		myLog.write("Unable to find instance for error flag output - provided instance name was " + inst_names[ele[2]]);
		textPut("text", "[Invalid] " + inst_names[ele[2]], MAIN_PNL, "ErrFlg_InstanceName");
		textPut("text", "", MAIN_PNL, "ErrFlg_InfoID");
		textPut("text", "", MAIN_PNL, "ErrFlg_InfoOffset");
	}
	if (!arot_msg.isValid())
	{
		myLog.write("Unable to find instance for error message output - provided instance name was " + inst_names[ele[3]]);
		textPut("text", "[Invalid] " + inst_names[ele[3]], MAIN_PNL, "ErrMsg_InstanceName");
		textPut("text", "", MAIN_PNL, "ErrMsg_InfoID");
		textPut("text", "", MAIN_PNL, "ErrMsg_InfoOffset");
	}

	if (!hdk_inst.isValid() || !studioRehTx.isValid() || !arot_err.isValid() || !arot_msg.isValid()) {
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Extract and store locally the infodriver numbers and slots, update the automatic mimic panel.
	inst0 = getDev(inst_names[ele[0]], &hdk_drv, &hdk_off);
	inst1 = getDev(inst_names[ele[1]], &rehTx_drv, &rehTx_off);
	inst2 = getDev(inst_names[ele[2]], &errFlg_drv, &errFlg_off);
	inst3 = getDev(inst_names[ele[3]], &errMsg_drv, &errMsg_off);
	
	// Validate the entry for the Hyperdeck - note offset=0 is allowed.
	if (!inst0 || (hdk_drv == 0))
	{
		// One or more properties invalid
		if (!inst0) myLog.write("Failed to find Hyperdeck device or offset from instance");
		if (hdk_drv == 0) myLog.write("Hyperdeck interface infodriver may not be device 0");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Validate the entry for the Studio Reh/Tx
	if (!inst1 || (rehTx_drv == 0) || (rehTx_off == 0))
	{
		// One or more properties invalid
		if (!inst1) myLog.write("Failed to find Studio Rehearse/Transmit device or offset");
		if (rehTx_drv == 0) myLog.write("Studio Status infodriver may not be device 0");
		if (rehTx_off == 0) myLog.write("Studio Status offset may not be 0");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Validate the entry for the Error flag infodriver and offset
	if (!inst2 || (errFlg_drv == 0) || (errFlg_off == 0))
	{
		// One or more properties invalid
		if (!inst2) myLog.write("Failed to find Studio Rehearse/Transmit device or offset");
		if (errFlg_drv == 0) myLog.write("Autorot error flag infodriver may not be device 0");
		if (errFlg_off == 0) myLog.write("Autorot error flag offset may not be 0");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Validate the entry for the Studio Reh/Tx
	if (!inst3 || (errMsg_drv == 0) || (errMsg_off == 0))
	{
		// One or more properties invalid
		if (!inst3) myLog.write("Failed to find Studio Rehearse/Transmit device or offset");
		if (errMsg_drv == 0) myLog.write("\nAutorot: Autorot error message infodriver may not be device 0");
		if (errMsg_off == 0) myLog.write("\nAutorot: Autorot error message offset may not be 0");
		controlShow(MAIN_PNL, "MajorError"); // Show error message on automatic panel
		return 1;
	}

	// Everything validated - Prepare to update all globals and register for revertives
	// But first, if the hyperdeck and reh/tx status is currently registered for revertives unregister them
	if (hdk.revertReg)
	{
		infoUnregister(hdk.driver);	// Unregister
		initCommSlot(&hdk);			// Clear all values in data object
	}

	if (rxTx.revertReg)
	{
		infoUnregister(rxTx.driver);
		initCommSlot(&rxTx);
	}

	// Update the mimic displays
	textPut("text", inst_names[ele[0]], MAIN_PNL, "Hdk_InstanceName");
	textPut("text", bncs_string(hdk_drv), MAIN_PNL, "Hdk_InfoID");
	textPut("text", bncs_string(hdk_off), MAIN_PNL, "Hdk_InfoOffset");

	textPut("text", inst_names[ele[1]], MAIN_PNL, "RehTx_InstanceName");
	textPut("text", bncs_string(rehTx_drv), MAIN_PNL, "RehTx_InfoID");
	textPut("text", bncs_string(rehTx_off), MAIN_PNL, "RehTx_InfoOffset");
	
	textPut("text", inst_names[ele[2]], MAIN_PNL, "ErrFlg_InstanceName");
	textPut("text", bncs_string(errFlg_drv), MAIN_PNL, "ErrFlg_InfoID");
	textPut("text", bncs_string(errFlg_off), MAIN_PNL, "ErrFlg_InfoOffset");
	
	textPut("text", inst_names[ele[3]], MAIN_PNL, "ErrMsg_InstanceName");
	textPut("text", bncs_string(errMsg_drv), MAIN_PNL, "ErrMsg_InfoID");
	textPut("text", bncs_string(errMsg_off), MAIN_PNL, "ErrMsg_InfoOffset");

	// Store the driver id's and offsets in global stores.
	my_instance = newComposite;	// Name of composite instance
	hdk.driver = hdk_drv;
	hdk.offset = hdk_off;
	rxTx.driver = rehTx_drv;
	rxTx.offset = rehTx_off;
	errFlag.driver = errFlg_drv;
	errFlag.offset = errFlg_off;
	errMsg.driver = errMsg_drv;
	errMsg.offset = errMsg_off;

	return 0;	// Signal successful process 
}
// --------------------------------------------------------


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// A function to manage the revertives from the Hyperdeck. Parameter sIndex is the
// Infodriver slot number, parameter sInfo is the revertive string.
void Autorot::hdkRevertProcess(const int sIndex, const bncs_string sInfo)
{
	bool aFlag;
	int newState = ts::unknown;
	int avalue;
	bncs_string revert;

	switch (sIndex)
	{
	case HYPERDECK_COMMS:
		avalue = hd::unknown;
		revert = sInfo.lower();
		if (revert == hdkStatName[1]) avalue = hd::connected;
		else if (revert == hdkStatName[2]) avalue = hd::disconnected;
		else if (revert == hdkStatName[3]) avalue = hd::reconnecting;
		hdkStat.comState.current = avalue;

		// Next actione depend on detecting a change in status
		if ((hdkStat.comState.current != hdkStat.comState.previous) && doProcess)
		{
			// Put status on the local panel
			aFlag = (avalue == hd::connected);
			textPut("text", aFlag ? "OK" : "FAIL", MAIN_PNL, "HD_Comms");
			textPut("statesheet", aFlag ? "alarm_ok" : "alarm_alarm", MAIN_PNL, "HD_Comms");
			// Report the status into the external report interface
			if (aFlag)
			{
				// Comms has switched to OK
				myLog.write("HyperDeck comms OK");
				reportErrorState(AUTOROT_ERROR_OK, myLog.lastLogItem());
			}
			else
			{
				myLog.write("HyperDeck comms failure. Status is " + sInfo);
				reportErrorState(AUTOROT_ERROR_BADCOMM, myLog.lastLogItem());
			}
		}
		// Note the current state for subsequent status
		hdkStat.comState.previous = hdkStat.comState.current;
		break;

	case HYPERDECK_SPEED:
		hdkStat.playSpeed = sInfo.toInt();
		textPut("text", sInfo + "%", MAIN_PNL, "HD_Speed");
		break;

	case HYPERDECK_TRANSPORT:
		textPut("text", sInfo, MAIN_PNL, "HD_Transport");	// Update the state mimic

		// Convert the string into an integer value
		newState = transportState_as_int(sInfo);
		hdkStat.transportState = newState;
		if (newState != ts::unknown)
		{
			// Kill the transport responsive timer of it is running.
			if (transpCmdTimerActive)
			{
				timerStop(TRANSPORT_CMD_CHANGE_TIMER);
				transpCmdTimerActive = false;
			}
			tState.current = newState;
			if ((tState.previous != tState.current) && doProcess)
			{
				// Transport state has changed - do we need to take any action?
				if (makeRecording)
				{
					switch (tState.current) {
					case ts::stop:
						startRecording();	// Put HyperDeck into preview mode
						break;

					case ts::preview:
						// Start the timer that issues record requests.
						timerStart(RECORD_START_TIMER, PREVIEW_WAIT_TIME);
						myLog.write("Start recording of " + recFileName);
						videoInAlarm(!haveVideoInput());	// Update the status display with video input state
						break;

					case ts::record:
						// Stop interval timer, clear makeRecording flag as we are now recording.
						timerStop(RECORD_START_TIMER);
						makeRecording = false;
						reportErrorState(AUTOROT_ERROR_OK, "Recording");
						break;
					}	// End => switch (tState.current)
				}	// End => if (makeRecording)
			}	// End => if (tState.previous != tState.current)
		}	// End => if (newState != ts::unknown)
		break;

	case HYPERDECK_LOOP:
		// Not currently used
		break;

	case HYPERDECK_RECORD_NAMED:
		// Write only slot - should never have a useful revertive report
		break;

	case HYPERDECK_VIDEOFORMAT:
		// Not currently used
		break;

	case HYPERDECK_VIDEOINPUT:
		// Not currently used
		break;

	case HYPERDECK_FILEFORMAT:
		textPut("text", sInfo, MAIN_PNL, "FileFormat");
		break;

	case HYPERDECK_ACTIVESLOT:
		if ((sInfo == "1") || (sInfo == "2"))
		{
			// This panel only displays the value. We show an icon tick or icon cross.
			aFlag = (sInfo == "1");
			textPut("pixmap", aFlag ? "/images/tick.png" : "/images/cross.png", MAIN_PNL, "Disk1_03");
			textPut("pixmap", aFlag ? "/images/cross.png" : "/images/tick.png", MAIN_PNL, "Disk2_03");
		}
		break;

	case HYPERDECK_CURRENTCLIP: 
		// Not used in this automatic.
		break;

	case HYPERDECK_TIMECODE:
		// Not used in this automatic.
		break;

	case HYPERDECK_SLOT01_STATUS:
		// The Hyperdeck driver frequently emits spurious data during record operations
		// Typically this is the number of seconds of record time remaining. This service
		// code filters the reports to accept only "empty", "mounting", and "mounted".
		avalue = diskStatus_as_int(sInfo);
		if (avalue == hdDsk::unknown) return;
		// Show the filetered status on the mimic panel
		textPut("text", sInfo, MAIN_PNL, "Disk1_01");
		// Categorise significant states as a integer for later testing of live inputs
		hdkStat.disk1Status.previous = hdkStat.disk1Status.current;	// Note the previous state
		hdkStat.disk1Status.current = avalue;		// Note the new state
		
		// If the disk status has changed, trigger a timer that will check for a media
		// available status allowing for transient settling possibilities.
		timerStart(MEDIA_STATUS_UPDATED, MEDIA_STATUS_UPDATED_TIME);
		break;

	case HYPERDECK_SLOT01_NAME:
		// Not used in this automatic.
		break;

	case HYPERDECK_SLOT01_REC_TIME:
		textPut("text", sInfo, MAIN_PNL, "Disk1_02");
		hdkStat.mediaTime1 = sInfo.toInt();
		break;

	case HYPERDECK_SLOT02_STATUS:
		avalue = diskStatus_as_int(sInfo);
		if (avalue == hdDsk::unknown) return;
		textPut("text", sInfo, MAIN_PNL, "Disk2_01");
		hdkStat.disk2Status.previous = hdkStat.disk2Status.current;
		hdkStat.disk2Status.current = avalue;
		timerStart(MEDIA_STATUS_UPDATED, MEDIA_STATUS_UPDATED_TIME);
		break;

	case HYPERDECK_SLOT02_NAME:
		// Not used in this automatic.
		break;

	case HYPERDECK_SLOT02_REC_TIME:
		textPut("text", sInfo, MAIN_PNL, "Disk2_02");
		hdkStat.mediaTime2 = sInfo.toInt();
		break;

	case HYPERDECK_CLIP_COUNT:
		textPut("text", sInfo, MAIN_PNL, "HD_ClipCount");
		hdkStat.clipCount = sInfo.toInt();
		break;

	case INFORDRIVER_TEST_SLOT:
		// This item is invoked when the hyperdeck infodriver is responding to a infodriver
		// write - suggesting the HyperDeck driver is not running as this places the infodriver
		// into external mode.
		pingRx = true;
		if (!internErrMode)
		{
			// Ping responses have just started so make error report.
			if (sInfo == (selectPing ? "pong" : "ping"))
			{
				myLog.write("Hdk Infodriver INTERNAL. Hyperdeck driver fail?");
				reportErrorState(AUTOROT_ERROR_INFOLOCAL, myLog.lastLogItem());
				internErrMode = true;
			}
		}
		break;
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function transportState_as_int(bncs_string nowState) converts the transport status name string
// into an integer value. The integers are extracted from enum ts::tranStat
int	Autorot::transportState_as_int(bncs_string nowState)
{
	int modeId = ts::unknown;

	if (nowState == ts_names[1]) modeId = ts::preview;
	else if (nowState == ts_names[2]) modeId = ts::record;
	else if (nowState == ts_names[3]) modeId = ts::stop;
	else if (nowState == ts_names[4]) modeId = ts::play;
	else if (nowState == ts_names[5]) modeId = ts::playloop;
	else if (nowState == ts_names[6]) modeId = ts::forward;
	else if (nowState == ts_names[7]) modeId = ts::rewind;

	return modeId;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function diskStatus_as_int(bncs_string nowVal) converts the disk status name
// string into an integer value. The integers are extracted from enum
// hdDsk::hyperDeckDiskStatus
int Autorot::diskStatus_as_int(bncs_string nowVal)
{
	int statId = hdDsk::unknown;

	if (nowVal == hdkDiskStatName[1]) statId = hdDsk::empty;
	else if (nowVal == hdkDiskStatName[2]) statId = hdDsk::mounting;
	else if (nowVal == hdkDiskStatName[3]) statId = hdDsk::mounted;

	return statId;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// makeRecFileName(void) is a service function that creates the record filename as a combination of a stem
// name with date and time appended. The stem name is read from an entry in objects.xml as part
// of the module initialisation.
//
// NOTE:
// Whilst it is very tempting to create a ISO 8601 style string with the colon seperating the hours
// minutes and seconds fields there is a problem as the colon is a reserved character in both Windows
// and macOS operating systems. Thus the minus or hypen character is used as the field divider in
// both the date and time fields.
//
// It uses a stem name held in module variable stemName and appends the time and date information.
// For example: let stemName = "StC_RoT_" which creates a filename of the form
// "StC-RoT_2021-04-14T13-24-56" so the record date and time can be read by a human..
// The assembled name is stored in module-wide variable 'recFileName'. Note that no file type
// is used because the Hyperdeck appends a type that depends on the record settings on the unit.
//
void Autorot::makeRecFileName(void)
{
	__time64_t long_time;
	struct tm timeinfo;
	bncs_string year, month, day, hour, minute, second;
	bncs_string buildFileName;

	_time64(&long_time);	// Get the time as 64 bit integer
	_localtime64_s(&timeinfo, &long_time);	// Extract into integers

	// Build the formatted filename
	buildFileName = stemName;
	buildFileName += bncs_string(timeinfo.tm_year + 1900, '0', 4) + "-";
	buildFileName += bncs_string(timeinfo.tm_mon + 1, '0', 2) + "-";
	buildFileName += bncs_string(timeinfo.tm_mday, '0', 2) + "T";
	buildFileName += bncs_string(timeinfo.tm_hour, '0', 2) + "-";
	buildFileName += bncs_string(timeinfo.tm_min, '0', 2) + "-";
	buildFileName += bncs_string(timeinfo.tm_sec, '0', 2);

	recFileName = buildFileName;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// startRecording() issues a record command to the HyperDeck after updating
// the record file name variable.
void Autorot::startRecording(void)
{
	makeRecFileName();
	infoWrite(hdk.driver, recFileName, HYPERDECK_RECORD_NAMED);
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// hyperDeckStop() issues a stop command to the HyperDeck. 
void Autorot::hyperDeckStop(void)
{
	infoWrite(hdk.driver, "stop", HYPERDECK_TRANSPORT, true);
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Function to show/clear an alarm indicator when there is no video input.
// Alarm condition activated when parameter noVideo == true
void Autorot::videoInAlarm(bool noVideo)
{
	if (noVideo)
	{
		textPut("statesheet", "alarm_alarm", MAIN_PNL, "HD_Vid_In");
	}
	else
	{
		// Video is present and recording is available - clear any error message
		textPut("statesheet", "alarm_ok", MAIN_PNL, "HD_Vid_In");
	}
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function haveVideoInput returns true if there is valid video input detected.
// Video input is available if one or both of the disk drives are mounted and
// the associated record time is not zero and transport state is 'preview'
bool Autorot::haveVideoInput(void)
{
	bool hv = false;
	hv = (
		  (hdkStat.disk1Status.current == hdDsk::mounted) && (hdkStat.mediaTime1 != 0) ||
		  (hdkStat.disk2Status.current == hdDsk::mounted) && (hdkStat.mediaTime2 != 0)
		 )
		 && (tState.current == ts::preview);
	return hv;
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// reportErrorState(int errorStatus, bncs_string errMessage) is a service function
// to store the error status value and the error status message into the
// designated infodriver slots. 
void Autorot::reportErrorState(int errorStatus, bncs_string errMessage)
{
	infoWrite(errFlag.driver, bncs_string(errorStatus), errFlag.offset);
	infoWrite(errMsg.driver, errMessage, errMsg.offset);
	errorStateValue = errorStatus;
	errorFlagActive = errorStatus == AUTOROT_ERROR_OK ? false : true;
	textPut("text", "Status " + bncs_string(errorStatus) + " => " + myLog.lastLogItem(), MAIN_PNL, "logger");
}


// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// function checkMediaAvailable(void) returns true if there is at least 1 mounted
// drive present.
bool Autorot::checkMediaAvailable(void)
{
	return (hdkStat.disk1Status.current == hdDsk::mounted) || (hdkStat.disk2Status.current == hdDsk::mounted);
}


// ****************************************************************************
// Implementation of class eventLog
// ****************************************************************************
eventLog::eventLog(void)
{
	this->isOpen = false;
	this->logName = "";		// Will be updated later - hopefully!
}


// setLogName is called to set the folder and file name for the log
void eventLog::setLogName(bncs_string logFileName)
{
	bncs_string lfn = "badlog";	// A default name if null is passed as logFileName
	
	// For example if parameter logName is Autorot the log file is:
	// %CC_ROOT%/%CC_SYSTEM%/logs/Autorot/Autorot.log
	
	if (logFileName != "") lfn = logFileName;	// update folder and file name
	
	bncs_string cfgpath = bncs_string(ccConfig::getDevPath());	// Get path to config files.
	cfgpath.replace('\\', '/');						// Enforce uniform path element divider format
	int posn = cfgpath.find("config");				// Look for the terminating config path element
	if (posn >= 0) {cfgpath = cfgpath.left(posn);}	// Trim off the 'config' text
	cfgpath = cfgpath + "logs/" + lfn;				// Add named path element to form folder path

	unsigned int fileAttrib = GetFileAttributes((const char*)cfgpath);	// Check for folder existence
	if (fileAttrib == INVALID_FILE_ATTRIBUTES)
	{
		// Assume the folder does not exist - so create it
		CreateDirectoryA((const char*)cfgpath, NULL);
	}
	this->logName = cfgpath + "/" + lfn + ".log";	// Save the filename with path for later use.
}


eventLog::~eventLog()
{
	// Destructor closes any open log file
	if (this->isOpen)
	{
		this->write("Log closed");
		myLogFile.close();
		this->isOpen = false;
	}
}


// eventLog::openLogFile() is called after initialisation to make the log available
// for writing.
bool eventLog::openLogFile(void)
{
	if (this->isOpen) return true;
	this->myLogFile.open((const char*)this->logName, ios::out | ios::app);
	this->isOpen = this->myLogFile.is_open();
	return this->isOpen;
}


// eventLog::isLogOpen() reports the open/closed state of the log file
bool eventLog::isLogOpen(void)
{
	return this->isOpen;
}


// eventLog::write(message) adds a data and time stamp to the message and stores it in the log.
// The composited message is saved in a private variable so it can be passed back on request.
void eventLog::write(bncs_string newEntry)
{
	// Get the current data and time as a string using bncs_dateTime
	bncs_dateTime dt = bncs_dateTime::now(); 
	bncs_string log_txt = bncs_string(dt.toString("dd/MM/yyyy hh:mm:ss.zzz ")) + newEntry + "\n";

	if (this->isOpen)
	{
		// Only write data if the file is open for output
		myLogFile << log_txt;
		myLogFile.flush();
		this->lastLogMessage = log_txt;
	}
}


// eventLog::lastLogItem is called to return a copy of the last message written to the log.
bncs_string eventLog::lastLogItem(void)
{
	return this->lastLogMessage;
}



