/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		TakeUndoCancel.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			28th February 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a component for use on single layer router panels using the wn_matrix
source grid and the BNCS destination grid. The component offers a TAKE button, UNDO button,
and 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 matrix to be controlled.

   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
   undoDouble	Conrols if the UNDO button needs a double press.
				Set to True to double press, False for single press. Default False

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 "TakeUndoCancel.h"

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

#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(TakeUndoCancel)

// ============================================ //
// Constructor - equivalent to ApplCore STARTUP //
// ============================================ //
TakeUndoCancel::TakeUndoCancel(bncs_client_callback *parent, const char *path) : bncs_script_helper(parent, path)
{
	// Initialise the instance global data
	m_instance = "";
	bgndState = "";
	takeHighState = "";
	takeLowState = "";

	matrixID = 0;			// BNCS Driver ID
	sourceID = 0;			// Source ID sent by connections engine
	destID = 0;				// Destination ID sent by connections ID
	undoSource = 0;			// Clear UNDO source memory
	undoDest = 0;			// Clear UNDO destination memory
	takeCounter = 0;		// Initial value for Take counter;
	numDestinations = 0;	// Number of destinations on the matrix 

	takeEnabled = false;	// Take and Undo buttons
	undoEnabled = false;	// are not enabled.
	revertRegistered = false;
	m_takeDouble = true;	// Set TAKE default behaviour to double push.
	m_undoDouble = false;	// Set UNDO default behaviour to single push

	for (int i = 0; i <= MAX_MTX_DEST; i++) revertState[i] = 0;	// Clear revertive store

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

	// Initialise state of some of the controls
	controlDisable(MAIN_PANEL, "Take");
	controlDisable(MAIN_PANEL, "Undo");
	textPut("text", NONAME, MAIN_PANEL, "Source");
	textPut("text", NONAME, MAIN_PANEL, "Dest");
	textPut("text", NONAME, MAIN_PANEL, "UndoSource");
	textPut("text", NONAME, MAIN_PANEL, "UndoDest");
	textPut("doublepush", (m_takeDouble ? "true" : "false"), MAIN_PANEL, "Take");
	textPut("doublepush", (m_undoDouble ? "true" : "false"), MAIN_PANEL, "Undo");
}


// ============================================= //
// Destructor - equivalent to ApplCore CLOSEDOWN //
// ============================================= //
TakeUndoCancel::~TakeUndoCancel()
{
	// If we have a matrix ID unregister for revertives.
	if ((matrixID >= MIN_DRIVER) && (matrixID <= MAX_DRIVER))
		routerUnregister(matrixID);
}


// ============================================= //
// All button pushes and notifications come here //
// ============================================= //
void TakeUndoCancel::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 valid source and destination have been selected.
			bncs_string mtxname = "";

			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");

			// Set up the data for potential UNDO
			undoDest = destID;
			undoSource = revertState[destID];
			routerName(matrixID, SOURCENAMES_DB, undoSource, mtxname);	// Get undo source name
			mtxname.replace('|', ' ');									// Remove any line breaks
			textPut("text", mtxname, MAIN_PANEL, "UndoSource");

			routerName(matrixID, DESTNAMES_DB, undoDest, mtxname);		// Get undo destination name
			mtxname.replace('|', ' ');									// Remove any line breaks
			textPut("text", mtxname, MAIN_PANEL, "UndoDest");

			controlEnable(MAIN_PANEL, "Undo");
			undoEnabled = true;

			routerCrosspoint(matrixID, sourceID, destID);		// Issue the crosspoint take command
			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() == "Undo")
		{
			// Only get get here if the UNDO button has been enabled by a TAKE
			routerCrosspoint(matrixID, undoSource, undoDest);	// Send crosspoint command

			disableUndo();		// Disable UNDO button, clear selection data
			
			return;
		}


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

			// 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;
			}

			if (undoEnabled) disableUndo();		// Disable the Undo control.

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

			return;
		}	// end of if (b->id() == "Deselect")
	}	// end of if( b->panel() == MAIN_PANEL )
}	// end of void TakeUndoCancel::buttonCallback(buttonNotify *b)


// ======================== //
// All revertives come here //
// ======================== //
int TakeUndoCancel::revertiveCallback(revertiveNotify *r)
{
	
	if (r->device() == matrixID)
	{
		// Note the revertive setting in the local matrix status memory
		revertState[r->index()] = r->info();
	}

	return 0;
}

// All database name changes come back here
void TakeUndoCancel::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 TakeUndoCancel::parentCallback( parentNotify *p )
{
	if (p->command() == "return")
	{
		if( p->value() == "all" )
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			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");
			sl << bncs_string("undoDouble=%1").arg(m_undoDouble ? "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))
		{
			// Are we registered for revertives from a previous instance? If so remove registration.
			if ((matrixID >= MIN_DRIVER) && (matrixID <= MAX_DRIVER))	routerUnregister(matrixID);

			// Valid instance - so save values
			m_instance = p->value();	// Save the new instance string
			matrixID = driverID;		// Save BNCS driver ID.

			// Get the matrix size.
			numDestinations = getRouterSize(matrixID, DESTNAMES_DB);
			
			// Register for revertives from new instance.
			routerRegister(matrixID, MATRIX_FIRST, numDestinations, false);
			revertRegistered = true;

			// Clear the revertives memory.
			for (int i = 0; i <= MAX_MTX_DEST; i++) revertState[i] = 0;	// Clear revertive store

			// Poll for current state
			routerPoll(matrixID, MATRIX_FIRST, numDestinations);

		}	// End of actions for vaid BNCS driver ID
	}	//	End of else if (p->command() == "instance" && p->value() != m_instance)


	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 (matrixID != 0)
			{
				routerName(matrixID, SOURCENAMES_DB, sourceID, sourceName);	// Get the source name from INI file	
				sourceName.replace("|", " ");		// Force the text into single line mode
				if (undoEnabled) disableUndo();		// Disable UNDO if it is enabled
			}
		}

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

		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 (matrixID != 0)
			{
				routerName(matrixID, DESTNAMES_DB, destID, destName);	// Get the destination name from INI file	
				destName.replace("|", " ");			// Force the text into single line mode
				if (undoEnabled) disableUndo();		// Disable UNDO if it is enabled
			}
			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 


	else if (p->command() == "undoDouble")
	{
		// User wants to set/clear double push action for UNDO 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_undoDouble = true;
			}
			else
			{
				m_undoDouble = false;
			}
			// Send updated state to the button
			textPut("doublepush", (m_undoDouble ? "true" : "false"), MAIN_PANEL, "Undo");
		}
	}	// End of else if (p->command() == "undoDouble")


	// ***** 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 TakeUndoCancel::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
			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 TakeUndoCancel::checkTakeEnable(void)
{
	// 
	if ((matrixID >= MIN_DRIVER) && (matrixID <= 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);
		}
	}
}



void TakeUndoCancel::disableUndo()
{
	// User has pressed the undo button. Only come here whilst the UNDO is enabled
	controlDisable(MAIN_PANEL, "Undo");					// Disable UNDO button
	undoEnabled = false;
	textPut("text", NONAME, MAIN_PANEL, "UndoSource");	// Clear names display
	textPut("text", NONAME, MAIN_PANEL, "UndoDest");
	undoSource = 0;										// Clear crosspoint records
	undoDest = 0;
}