/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		StC_TakeCancel.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			19th March 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a TAKE component for use in Wood Norton Studio C which has a 3-level matrix
package routing system (Vision, Digital Audio, Analogue Audio). The source and destination 
selection buttons or grids send package crosspoint numbers to this component. The component 
implements a TAKE button and a DESELECT button. 

??? The component requires an instance string that defines the matrix to be used.

Other interactions happen via the connections engine. This component wants to receive the source
number from the wn_matrix component, and the destination from the grid component. The component 
uses the instance to look up the name of the source and the destination, displaying these on the 
mimics either side of the take button. When a source and destination are selected the TAKE button
is activated and flashes between two states of red background. A timeout of ten seconds is 
implemented after which the Take deselects. If the TAKE is pressed it emits a text string
that can be sent to the destination button by the connections grid. The destination grid sends
the take command to the router. This component then deselects the TAKE button and enables the 
UNDO button. If the user presses UNDO the component emits a message to be sent to teh destination
grid which sends the UNDO to the router. The UNDO button is disabled. If the user makes a source 
or destination selection the new source ID will cancel any active UNDO potential.

The component also emits a message when the user presses the DESELECT button. This should be sent
to both the source and destination grids as a selction ID of -1 which kills the highlighted
selections. The TAKE and UNDO buttons are cleared and disabled.

The design time parameters available in this module are:
   instance		MANDATORY value. Defines the Package Names logical matrix to be controlled

   sdiRouter	Necessary value. Provides instance name for Studio C SDI router;
   anlRouter	Necessary value. Provides instance name for Studio C Analogue audio router.
   aesRouter	Necessary value. Provides instance name for Studio C AES digital audio router.
   xptRouter	Neccesary value. Provides instance name for Studio C package crosspoints router.

   bgndStyle	The statesheet name that is used the set the control background colour
   takeHigh		The TAKE button can flash between two colours. These tow colours are
   takeLow		set by the takeHigh and takeLow statesheet names.
   takeDouble	Controls if the take button needs a double press.
				Set True to double press, False for single press. Default True

The runtime input parameters include the static list above, plus:
   sourceID		Set the source ID for Take action
   destID		Set the destination ID for Take actions

There is just one hostNotify event message - typically used by the connections engine to send clear
selection messages to source and destination grids. The message takes the form of "deselect=-1"

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "StC_TakeCancel.h"

#define MAIN_PANEL	1
#define PANEL_NAME "StCTakePanel.bncs_ui"

#define TICK	"/images/tick.png"
#define CROSS	"/images/cross.png"

#define TIMER_SETUP	1

#define TAKE_TIMER	2
#define TAKE_INTERVAL	1000
#define TAKE_COUNT	10

#define MIN_DRIVER 1
#define MAX_DRIVER 999
#define MATRIX_FIRST 1
#define SOURCENAMES_DB 0
#define DESTNAMES_DB 1
#define NONAME ""

// Make our class visible to the outside world
EXPORT_BNCS_SCRIPT(StC_TakeCancel)

// ============================================ //
// Constructor - equivalent to ApplCore STARTUP //
// ============================================ //
StC_TakeCancel::StC_TakeCancel(bncs_client_callback *parent, const char *path) : bncs_script_helper(parent, path)
{
	// Initialise the instance global data
	m_instance = "";	// Crosspoint package names
	m_sdiRouter = "";	// SDI router instance name
	m_anlRouter = "";	// Analogue audio router instance name
	m_aesRouter = "";	// AES digital audio router instance name
	m_xptRouter = "";	// Crosspoint lists for package name(s)

	bgndState = "";		// Set default statesheet information to null strings
	takeHighState = "";
	takeLowState = "";

	m_useVideo = true;		// Flag indicating if the video layer should be routed
	m_useAnalAudio = true;	// Flag indicating if the analogue audio should be routed
	m_useAES = true;		// Flag indicating if the AES digital audio layer should be routed.

	namesID = 0;			// BNCS package names driver ID
	sdiID = 0;				// BNCS Driver ID for SDI router.
	anlID = 0;				// BNCS Driver ID for analogue audio router.
	aesID = 0;				// BNCS Driver ID for AES digital router.
	xptID = 0;				// BNCS Driver ID holding crosspoint list for routers
	sourceID = 0;			// Source ID sent by connections engine
	destID = 0;				// Destination ID sent by connections ID
	takeCounter = 0;		// Initial value for Take counter;
	numDestinations = 0;	// Number of destinations on the matrix 

	takeEnabled = false;	// Take and Undo buttons
	m_takeDouble = true;	// Set TAKE default behaviour to double push.

	// Show the panel
	panelShow(MAIN_PANEL, PANEL_NAME);

	// Initialise state of some of the controls
	controlDisable(MAIN_PANEL, "Take");
	textPut("text", NONAME, MAIN_PANEL, "Source");
	textPut("text", NONAME, MAIN_PANEL, "Dest");
	textPut("doublepush", (m_takeDouble ? "true" : "false"), MAIN_PANEL, "Take");
	textPut("pixmap", (m_useVideo ? TICK : CROSS), MAIN_PANEL, "VideoEnabled");
	textPut("pixmap", (m_useAnalAudio ? TICK : CROSS), MAIN_PANEL, "AudioEnabled");
	textPut("pixmap", (m_useAES ? TICK : CROSS), MAIN_PANEL, "AESEnabled");
}


// ============================================= //
// Destructor - equivalent to ApplCore CLOSEDOWN //
// ============================================= //
StC_TakeCancel::~StC_TakeCancel()
{
	
}


// ============================================= //
// All button pushes and notifications come here //
// ============================================= //
void StC_TakeCancel::buttonCallback(buttonNotify *b)
{
	if (b->panel() == MAIN_PANEL)
	{
		if (b->id() == "Take")
		{
			// User has pressed the TAKE button. This button is only enabled when 
			// a non-zero, positive source and destination have been selected.
			int sdiSrc = 0;
			int anlSrc = 0;
			int aesSrc = 0;
			int sdiDest = 0;
			int anlDest = 0;
			int aesDest = 0;

			timerStop(TAKE_TIMER);										// Stop the timeout count system
			textPut("statesheet", takeHighState, MAIN_PANEL, "Take");	// Leave button in High State
			controlDisable(MAIN_PANEL, "Take");							// Disable take button
			takeEnabled = false;										// Set flag to new state
			textPut("text", NONAME, MAIN_PANEL, "Source");				// Clear the name displays
			textPut("text", NONAME, MAIN_PANEL, "Dest");

			// Now need to extract the SDI, analogue and AES source and destination crosspoints.
			getXpointData(SOURCENAMES_DB, sourceID, &sdiSrc, &anlSrc, &aesSrc);
			getXpointData(DESTNAMES_DB, destID, &sdiDest, &anlDest, &aesDest);

			// Now issue crosspoint data for selected andactive levels. A source or destination of -1
			// is sufficient to not action the route

			// Video Level
			if ((m_useVideo) && (sdiSrc >= 0) && (sdiDest >= 0))
			{
				routerCrosspoint(sdiID, sdiSrc, sdiDest);
			}

			// Audio Level
			if ((m_useAnalAudio) && (anlSrc >= 0) && (anlDest >= 0))
			{
				routerCrosspoint(anlID, anlSrc, anlDest);
			}

			// AES Level
			if ((m_useAES) && (aesSrc >= 0) && (aesDest >= 0))
			{
				routerCrosspoint(aesID, aesSrc, aesDest);
			}

			sourceID = 0;										// Clear source and destination stores
			destID = 0;

			// Tell the host to tell connected panels that they should clear selections
			hostNotify("deselect=-1");
			return;
		}


		if (b->id() == "Deselect")
		{
			// User has pressed the deselect button.
			textPut("text", NONAME, MAIN_PANEL, "Source");
			textPut("text", NONAME, MAIN_PANEL, "Dest");

			// Now clear Take and Undo facilities if they are enabled
			if (takeEnabled)
			{
				// Disable the Take control
				controlDisable(MAIN_PANEL, "Take");
				timerStop(TAKE_TIMER);
				takeEnabled = false;
			}

			hostNotify("deselect=-1");	// Send a message that connections system can send to matrix selectors.

			return;
		}	// end of if (b->id() == "Deselect")

		if (b->id() == "VideoEnabled")
		{
			// User is changing Vidoe level routing enable
			m_useVideo ^= true;			// Toggle the flag
			textPut("pixmap", (m_useVideo ? TICK : CROSS), MAIN_PANEL, "VideoEnabled");
			return;
		}

		if (b->id() == "AudioEnabled")
		{
			// User is changing Analogue Audio level routing enable
			m_useAnalAudio ^= true;		// Toggle the flag
			textPut("pixmap", (m_useAnalAudio ? TICK : CROSS), MAIN_PANEL, "AudioEnabled");
			return;
		}

		if (b->id() == "AESEnabled")
		{
			// User is changing AES level routing enable
			m_useAES ^= true;			// Toggle the flag
			textPut("pixmap", (m_useAES ? TICK : CROSS), MAIN_PANEL, "AESEnabled");
			return;
		}

	}	// end of if( b->panel() == MAIN_PANEL )
}	// end of void TakeUndoCancel::buttonCallback(buttonNotify *b)


// ======================== //
// All revertives come here //
// ======================== //
int StC_TakeCancel::revertiveCallback(revertiveNotify *r)
{
	// No revertives expected as we do not register for them
	return 0;
}

// All database name changes come back here
void StC_TakeCancel::databaseCallback(revertiveNotify *r)
{
}


// ==================================================================//
// All parent notifications come here.  When this script is just one //
// component of another dialog our host might want to tell us things //
// ================================================================= //
bncs_string StC_TakeCancel::parentCallback( parentNotify *p )
{
	if (p->command() == "return")
	{
		if( p->value() == "all" )
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string("sdiRouter=%1").arg(m_sdiRouter);		// We can set SDI router name
			sl << bncs_string("anlRouter=%1").arg(m_anlRouter);		// We can set analogue audio router name
			sl << bncs_string("aesRouter=%1").arg(m_aesRouter);		// We can set AES digital audio router name
			sl << bncs_string("xptRouter=%1").arg(m_xptRouter);		// We can set xpt router name
			sl << bncs_string("bgndStyle=%1").arg(bgndState);		// We can set the style for the background box
			sl << bncs_string("takeHigh=%1").arg(takeHighState);	// We can set the statesheet for the TAKE button high state
			sl << bncs_string("takeLow=%1").arg(takeLowState);		// and low state
			sl << bncs_string("takeDouble=%1").arg(m_takeDouble ? "True" : "False");

			return sl.toString('\n');
		}
	}


	else if (p->command() == "instance" && p->value() != m_instance)
	{
		// Our instance is being set/changed
		int driverID, slotID;
		
		// Look up the new instance name in instances.xml - extract the driver ID
		getDev(p->value(), &driverID, &slotID);

		// Validate the driver value for BNCS range
		if ((driverID >= MIN_DRIVER) && (driverID <= MAX_DRIVER))
		{
			// Valid instance - so save values
			m_instance = p->value();	// Save the new instance string
			namesID = driverID;			// Save BNCS crosspoint names driver ID.

			// Get the matrix size.
			numSources = getRouterSize(namesID, SOURCENAMES_DB);
			numDestinations = getRouterSize(namesID, DESTNAMES_DB);
	
		}	// End of actions for valid BNCS driver ID
		
		checkMatrixSet();

	}	//	End of else if (p->command() == "instance" && p->value() != m_instance)


	else if ((p->command() == "sdiRouter") && (p->value() != m_sdiRouter))
	{
		// Connections engine is sending us a BNCS driver ID for SDI router.
		int driverID, slotID;

		// Look up the new instance name in instances.xml - extract the driver ID
		getDev(p->value(), &driverID, &slotID);

		// Validate the driver value for BNCS range
		if ((driverID >= MIN_DRIVER) && (driverID <= MAX_DRIVER))
		{
			// Valid instance - so save values
			m_sdiRouter = p->value();	// Save the new instance string
			sdiID = driverID;		// Save BNCS crosspoint names driver ID.
		}	// End of actions for valid BNCS driver ID

		checkMatrixSet();

	}	// End of else if ((p->command() == "sdiRouter") && (p->value() != m_sdiRouter))


	else if ((p->command() == "anlRouter") && (p->value() != m_anlRouter))
	{
		// Connections engine is sending us a BNCS driver ID for SDI router.
		int driverID, slotID;

		// Look up the new instance name in instances.xml - extract the driver ID
		getDev(p->value(), &driverID, &slotID);

		// Validate the driver value for BNCS range
		if ((driverID >= MIN_DRIVER) && (driverID <= MAX_DRIVER))
		{
			// Valid instance - so save values
			m_anlRouter = p->value();	// Save the new instance string
			anlID = driverID;			// Save BNCS crosspoint names driver ID.
		}	// End of actions for valid BNCS driver ID

		checkMatrixSet();

	}	// End of else if ((p->command() == "anlRouter") && (p->value() != m_anlRouter))


	else if ((p->command() == "aesRouter") && (p->value() != m_aesRouter))
	{
		// Connections engine is sending us a BNCS driver ID for SDI router.
		int driverID, slotID;

		// Look up the new instance name in instances.xml - extract the driver ID
		getDev(p->value(), &driverID, &slotID);

		// Validate the driver value for BNCS range
		if ((driverID >= MIN_DRIVER) && (driverID <= MAX_DRIVER))
		{
			// Valid instance - so save values
			m_aesRouter = p->value();	// Save the new instance string
			aesID = driverID;			// Save BNCS crosspoint names driver ID.
		}	// End of actions for valid BNCS driver ID

		checkMatrixSet();

	}	// End of else if ((p->command() == "aesRouter") && (p->value() != m_aesRouter))


	else if ((p->command() == "xptRouter") && (p->value() != m_xptRouter))
	{
		// Connections engine is sending us a BNCS driver ID for crosspoint router ID.
		int driverID, slotID;

		// Look up the new instance name in instances.xml - extract the driver ID
		getDev(p->value(), &driverID, &slotID);

		// Validate the driver value for BNCS range
		if ((driverID >= MIN_DRIVER) && (driverID <= MAX_DRIVER))
		{
			// Valid instance - so save values
			m_xptRouter = p->value();	// Save the new instance string
			xptID = driverID;			// Save BNCS crosspoint names driver ID.
		}	// End of actions for valid BNCS driver ID

		checkMatrixSet();

	}	// End of else if ((p->command() == "anlRouter") && (p->value() != m_anlRouter))


	else if (p->command() == "sourceID")
	{
		// Connections engine is sending us a source ID
		bncs_string sourceName = NONAME;	// Default text string used if invalid matrix or source.

		sourceID = p->value().toInt();		// Extract the source ID from the value string.

		if (sourceID >= MATRIX_FIRST)
		{
			// Valid source selection - get the source name and show it on the source display
			if (namesID != 0)
			{
				routerName(namesID, SOURCENAMES_DB, sourceID, sourceName);	// Get the source name from INI file	
				sourceName.replace("|", " ");		// Force the text into single line mode
			}
		}

		textPut("text", sourceName, MAIN_PANEL, "Source");	// Put name or default no name text on the display

		checkTakeEnable();							// Enable/disable TAKE button

	}	// End of else if (p->command() == "sourceID")


	else if (p->command() == "destID")
	{
		// Connections engine is sending us a destination ID
		bncs_string destName = NONAME;

		destID = p->value().toInt();		// Extract the destinationID

		if (destID >= MATRIX_FIRST)
		{
			// Valid destination selection - get the destination name and show it on the source display
			if (namesID != 0)
			{
				routerName(namesID, DESTNAMES_DB, destID, destName);	// Get the destination name from INI file	
				destName.replace("|", " ");			// Force the text into single line mode
			}
			textPut("text", destName, MAIN_PANEL, "Dest");
			
			checkTakeEnable();						// Enable/disable TAKE button
		}	// End if (destID >= MATRIX_FIRST)

	}	// End of else if (p->command() == "destID")


	else if (p->command() == "bgndStyle")
	{
		// User is changing the statesheet that defines the colour of the background box.

		bgndState = p->value();
		textPut("statesheet", bgndState, MAIN_PANEL, "Background");
	}


	else if (p->command() == "takeHigh")
	{
		// User is changing the statesheet that defines the colour of the TAKE button
		// in one state of flash.

		takeHighState = p->value();
	}


	else if (p->command() == "takeLow")
	{
		// User is changing the statesheet that defines the colour of the TAKE button
		// in the other state of flash

		takeLowState = p->value();
	}

	else if (p->command() == "takeDouble")
	{
		// User wants to set/clear double push action for TAKE button. Recognised values
		// are "True" and "False" with no case dependance.
		bncs_string newval = p->value().lower();

		if ((newval == "true") || (newval == "false"))	// Only accept valid inputs
		{
			if (newval == "true")
			{
				m_takeDouble = true;
			}
			else
			{
				m_takeDouble = false;
			}
			// Send state to the button
			textPut("doublepush", (m_takeDouble ? "true" : "false"), MAIN_PANEL, "Take");
		}
	}	// End of 


	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if( p->command() == "_events" )
	{	// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;

		sl << "deselect=-1";		
		
		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 << "sourceID=[value]";
		sl << "destID=[value]";

		return sl.toString( '\n' );
	}

	return "";
}

// Timer events come here
void StC_TakeCancel::timerCallback( int id )
{
	switch( id )
	{
	case TIMER_SETUP:
		timerStop(id);
		break;

	case TAKE_TIMER:
		takeCounter--;	// Decrement the count

		if (takeCounter == 0)
		{
			timerStop(TAKE_TIMER);				// Stop the flashing
			controlDisable(MAIN_PANEL, "Take");	// Disable the control
			takeEnabled = false;				// Report the button state.
		}
		else
		{
			// Need to change the colour of the take button.
			// Use odd remainders as low statesheet selection
			textPut("statesheet", (takeCounter % 2) ? takeLowState : takeHighState, MAIN_PANEL, "Take");
		}

		break;	// break for case TAKE_TIMER:

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// function checkTakeEnable(void) is called to adjust the state of the TAKE
// button based on the presence of a valid source and destination selection.
//
void StC_TakeCancel::checkTakeEnable(void)
{
	// 
	if ((namesID >= MIN_DRIVER) && (namesID <= MAX_DRIVER) && (sourceID >= MATRIX_FIRST) && (destID >= MATRIX_FIRST))
	{
		// Is Take Already enabled? If not, enable it. Then start the timeout count system.
		if (!takeEnabled)
		{
			controlEnable(MAIN_PANEL, "Take");
			takeEnabled = true;
		}

		takeCounter = TAKE_COUNT;		// Set to initial time out tick count.
		textPut("statesheet", takeHighState, MAIN_PANEL, "Take");	// Set state.
		timerStart(TAKE_TIMER, TAKE_INTERVAL);
	}
	else
	{
		// No valid matrix + source + destination selection
		if (takeEnabled)
		{
			textPut("statesheet", takeHighState, MAIN_PANEL, "Take");
			controlDisable(MAIN_PANEL, "Take");
			takeEnabled = false;
			timerStop(TAKE_TIMER);
		}
	}
}


////////////////////////////////////////////////////////////////////////////////
// function checkMatrixSet(void) is called to test if the set of names provided
// in the design time form are possible BNCS values.
//
void StC_TakeCancel::checkMatrixSet(void)
{
 	if (((namesID >= MIN_DRIVER) && (namesID <= MAX_DRIVER))
		&& ((sdiID >= MIN_DRIVER) && (sdiID <= MAX_DRIVER))
		&& ((anlID >= MIN_DRIVER) && (anlID <= MAX_DRIVER))
		&& ((aesID >= MIN_DRIVER) && (aesID <= MAX_DRIVER))
		&& ((xptID >= MIN_DRIVER) && (xptID <= MAX_DRIVER))
	   )
		m_goodMtxSet = true;
	else
		m_goodMtxSet = false;

}

/////////////////////////////////////////////////////////////////////////////////////////////////////
// function getXpointData(const int dataBaseID, const int xptid, int *sdixpt, int anlxpt, int aesxpt)
// is called to extract the individual level crosspoint ids for the designated source data:
//   dataBaseID	identofies the source or destination crosspoint data required
//   xptid		is the package ID crosspoint data required
//   sdixpt		is the video level source/destination crosspoint index
//   anlxpt		is the audio level source/destination crosspoint index
//   aesxpt		is the AES digital audio level source/destination crosspoint index
//
// Returned index value of -1 means the level is not in use (a '-' in the level string).
// Uses the crosspoint data device number stored in global variable xptID
//
void StC_TakeCancel::getXpointData(const int dataBaseID, const int xpt_id, int *sdixpt, int *anlxpt, int *aesxpt)
{
	bncs_string xpt_list, partA, residueA, partB, partC;
	int maxXpt;

	// Check for a valid xpt list driver ID
	if ((xptID < MIN_DRIVER) || (xptID > MAX_DRIVER))
	{
		*sdixpt = -1;
		*anlxpt = -1;
		*aesxpt = -1;
		return;
	}

	// Check that the index is in range
	switch (dataBaseID)
	{
		case SOURCENAMES_DB:		// Request is for source string.
			maxXpt = numSources;
			break;

		case DESTNAMES_DB:			// Request is for destination string.
			maxXpt = numDestinations;
			break;

		default:
			maxXpt = 0;
			break;
	}

	if ((xpt_id < MATRIX_FIRST) || (xpt_id > maxXpt))
	{
		*sdixpt = -1;
		*anlxpt = -1;
		*aesxpt = -1;
		return;
	}

	// Ask for string that has the comma delimited list
	routerName(xptID, dataBaseID, xpt_id, xpt_list);
	
	// Split the string into the three levels
	xpt_list.split(',', partA, residueA);
	residueA.split(',', partB, partC);
	
	// If the level string is a "-" return -1, else convert the string to a number.
	*sdixpt = (partA == "-") ? -1 : partA.firstInt();
	*anlxpt = (partB == "-") ? -1 : partB.firstInt();
	*aesxpt = (partC == "-") ? -1 : partC.firstInt();
}