/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		MLDestMimic.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			22nd April 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a UMD style mimic that is intended for use inside scripted components
that manage destination selection in multi-level matrix (package) desinations.

The parent sends data to this component using 4 text identifiers:
	1)	driverid	The BNCS driver ID for this tally/mimic to monitor.
	2)	destid		The matrix destination ID for this tally/mimic to monitor.
	3)	statesheet	The statesheet name that controls the look of the mimic display.
	4)	unusedtext	Text to display when the matrix level is not in use for a package destination.

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */

#include <windows.h>
#include <stdio.h>
#include <bncs_string.h>
#include <bncs_config.h>
#include "MLDestMimic.h"

#define MY_PANEL	1
#define PANEL_NAME	"mimic.bncs_ui"

#define POLL_TIMER	1
#define POLL_DURATION	10000

#define BNCS_DRV_MIN	1
#define	BNCS_DRV_MAX	999
#define BNCS_DEST_MIN	1
#define DB_DESTINATION	1

///////////////////////////////////////////////////////
// Macro to make our class visible to the outside world
EXPORT_BNCS_SCRIPT(MLDestMimic)


///////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP
MLDestMimic::MLDestMimic(bncs_client_callback * parent, const char * path) : bncs_script_helper(parent, path)
{
	m_myMtxID = "";				// Holds the user provided matrix ID used when no instance exists.
	m_myDestID = "";			// Holds the user selected destination id to monitor.
	m_myStateSheet = "";		// Holds the name of any statesheet used to control the text appearance.
	m_myUnusedMsg = "unknown";	// Set the default string displayed when matrix id or destination are unused.

	m_myMatrixID = -1;			// Holds the numeric version of the matrix to monitor.
	m_myDestination = -1;		// Holds the numeric version of the matrix output to monitor.
	m_mySource = -1;			// Holds the current source number routed to m_myDestination

	m_RevertRxd = false;			// Set true once a revertive has been received from the router.

	// Show our label display on our assigned panel
	panelShow(MY_PANEL, PANEL_NAME);
}


/////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN.
MLDestMimic::~MLDestMimic()
{
	if ((m_myMatrixID >= BNCS_DRV_MIN) && (m_myDestination >= BNCS_DEST_MIN))
	{
		// We have an active destination - delete the registration
		routerUnregister(m_myMatrixID);
		m_myMatrixID = 0;
		m_myDestination = 0;
	}
}


/////////////////////////////////////////////////
// All button pushes and notifications come here.
// There are no active buttons in this component.
void MLDestMimic::buttonCallback(buttonNotify *b)
{
}


////////////////////////////
// All revertives come here.
int MLDestMimic::revertiveCallback(revertiveNotify *r)
{
	// Check for our matrix and destination - if matched process revertive.
	if ((r->device() == m_myMatrixID) && (r->index() == m_myDestination))
	{
		// We have received a revertive for our destination - send to the text field of  
		// the mimic if it differs from data we previously received.
		if (m_mySource != r->info())
		{
			m_mySource = r->info();
			textPut("text", r->sInfo(), MY_PANEL, "LevelMimic");
			m_RevertRxd = true;
			timerStop(POLL_TIMER);			// Revertive found, so stop the background poll
		}
	}

	return 0;
}


////////////////////////////////////////////
// All database name changes come back here.
// Driver will tell us if any source name 
// changes by issuing a revertive.
void MLDestMimic::databaseCallback(revertiveNotify *r)
{
}


//////////////////////////////////////////////////////////////////////////
// All parent notifications come here. When this script is just one 
// component of another dialog then our host might (will even!) want to 
// tell us settings and other things.
bncs_string MLDestMimic::parentCallback( parentNotify *p )
{
	if (p->command() == "return")
	{
		if (p->value() == "all")
		{	
			// Persisting values for bncs_vis_ed. Not required in this 
			// component, so just return a null string.
			bncs_stringlist sl;
			
			sl << bncs_string( "" );
			return sl.toString( '\n' );
		}
	}


	else if (p->command() == "instance")
	{	
		// Our instance is being set/changed. Unsupported by this component.
		// So we provide a dummy action that ignores the message
		return "";
	}


	else if (p->command() == "driverid")
	{	
		// Our parent is sending us the BNCS driver ID to monitor.
		int new_value = 0;

		m_myMtxID = p->value();				// Save the string version of teh data
		new_value = m_myMtxID.toInt();		// Make an integer version
		checkAndActivate(new_value, m_myDestination);	// Start monitoring if we have the needed data
	}


	else if (p->command() == "destid")
	{
		// Our parent is sending us the matrix destination ID to monitor.
		int new_value;

		m_myDestID = p->value();					// Save the string
		new_value = m_myDestID.toInt();				// Make an integer version
		checkAndActivate(m_myMatrixID, new_value);	// Start monitoring if we have needed data
	}


	else if (p->command() == "statesheet")
	{
		// Our parent is sending us the statesheet to use.
		m_myStateSheet = p->value();		// Save the string
		textPut("statesheet", m_myStateSheet, MY_PANEL, "LevelMimic");	// Forward to the mimic
	}


	else if (p->command() == "unusedtext")
	{
		// Our parent is sending us the text to show if the destination or matrix level is unused
		m_myUnusedMsg = p->value();		// Save the string

		// Do we have a valid destination/level - if not show the text
		if ((m_myMatrixID < BNCS_DRV_MIN) || (m_myDestination < BNCS_DEST_MIN))
		{
			textPut("text", m_myUnusedMsg, MY_PANEL, "LevelMimic");
		}
	}


	// ***** 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 << "";
		return sl.toString( '\n' );
	}

	return "";
}


//////////////////////////
// Timer events come here.
void MLDestMimic::timerCallback( int id )
{
	switch( id )
	{
		case POLL_TIMER:
			if ((m_myMatrixID > 0) && (m_myDestination > 0))
				routerPoll(m_myMatrixID, m_myDestination, m_myDestination);
			else
				timerStop(POLL_TIMER);
			break;

		default:	// Unhandled timer event
			timerStop(id);
			break;
	}
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////// Callbacks above - Methods below ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////


bool MLDestMimic::checkAndActivate(int new_devID, int new_destID)
{
	bool validDevice = false;
	bool validDest = false;
	bool updatedSelection = false;
	int oldDevice = 0;

	// Set validDevice flag according to current stored global data.
	if ((new_devID >= BNCS_DRV_MIN) && (new_devID <= BNCS_DRV_MAX)) validDevice = true;

	oldDevice = m_myMatrixID;		// Keep record of old device for possible unregister process
	m_myMatrixID = new_devID;		// Update the matrix driver ID

	// Now validate the destination ID parameter
	if (new_destID != m_myDestination)
	{
		if ((new_destID >= BNCS_DEST_MIN) && validDevice)
		{
			if (new_destID <= getRouterSize(m_myMatrixID, DB_DESTINATION))	// Check destination index against recorded database size
			{
				m_myDestination = new_destID;	// Record new destination
				validDest = true;				// Flag we have valid destination
				updatedSelection = true;		// Note we have a change - so need to update registration
			}
		}
	}
	else
	{
		// Destination is unchanged, see if it is valid (>0)
		if (new_destID >= BNCS_DEST_MIN)
		{
			validDest = true;
		}
	}	// end of if (dest_id != m_myDestination)

	// If selections have been updated, update the revertive registration as appropriate
	if (updatedSelection)
	{
		if ((oldDevice >= BNCS_DRV_MIN) && (oldDevice <= BNCS_DRV_MAX))
		{
			routerUnregister(oldDevice);	// Clear old registration
		}

		if (validDevice && validDest)
		{
			m_myDestination = new_destID;	// Save new destination

			routerRegister(m_myMatrixID, m_myDestination, m_myDestination, false);	// Register for revertives
			routerPoll(m_myMatrixID, m_myDestination, m_myDestination);				// Get current setting
			m_RevertRxd = false;													// Waiting for a revertive
			timerStart(POLL_TIMER, POLL_DURATION);									// Start background poll timer
			return true;
		}
		else
		{
			textPut("text", m_myUnusedMsg, MY_PANEL, "LevelMimic");
		}
	}	// end if (validDevice)

	return false;
}