/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		MtxDestMimic.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			26th February 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a UMD style mimic that is intended for use with single-level matrix
destinations.

The selected matrix ID and destination number may be set in either of 2 mechanisms
	a) Use an entry from insances.xml
	b) Directly enter the matix and destination id in the visual editor component form

Method (a) overrides any attempts to set the matrix and destination fields in the form.
The matrix and destination extracted from the instance are shown in the component form entries.

A third entry field exists in the component entry form - the database ID used for the text labels.

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

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

#define MAIN_PNL	1
#define	PNL_NAME	"umdMimic.bncs_ui"
#define MIN_DEVICE 1
#define MAX_DEVICE 999
#define MIN_DEST 1
#define DB_SOURCE 0
#define DB_DESTINATION 1

#define POLL_TIMER	1
#define POLL_DURATION	10000

// Macro to make our class visible to the outside world
EXPORT_BNCS_SCRIPT(MtxDestMimic)


// ============================================ //
// Constructor - equivalent to ApplCore STARTUP //
// ============================================ //
MtxDestMimic::MtxDestMimic( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Set some default values into the variables
	m_instance = "";		// The instance string for data from instances.xml
	m_myMtxID = "";			// The user provided matrix ID used when no instance exists.
	m_myDestID = "";		// The user selected destination id to monitor.

	m_myMatrixID = 0;		// Holds the numeric version of the matrix to monitor.
	m_myDestination = 0;	// Holds the numeric version of the matrix output to monitor.
	m_mySourceDB = 0;		// Holds the database number to be used for source names.
	m_mySource = 0;			// Holds the current source number routed to m_myDestination

	m_RevertRxd = false;	// Set the revertive received flag to off.

	// Show a panel MAIN_PNL
	panelShow(MAIN_PNL, PNL_NAME);
}


// ============================================= //
// Destructor - equivalent to ApplCore CLOSEDOWN //
// ============================================= //
MtxDestMimic::~MtxDestMimic()
{
	if ((m_myMatrixID >= MIN_DEVICE) && (m_myDestination >= MIN_DEST))
	{
		// We have an active destination - delete the registration
		routerUnregister(m_myMatrixID);
		m_myMatrixID = 0;
		m_myDestination = 0;
	}

	// Stop the background poll if it is active
	if (!m_RevertRxd) timerStop(POLL_TIMER);
}


// ============================================= //
// All button pushes and notifications come here //
// ============================================= //
void MtxDestMimic::buttonCallback(buttonNotify *b)
{
	// No buttons, only labels.
}


// ======================== //
// All revertives come here //
// ======================== //
int MtxDestMimic::revertiveCallback(revertiveNotify *r)
{
	if ((r->device() == m_myMatrixID) && (r->index() == m_myDestination))
	{
		if (!m_RevertRxd)	// Have we seen a revertive?
		{
			// No - so this is the first revertive
			m_RevertRxd = true;
			timerStop(POLL_TIMER);
		}
		// Revertive is for our control - Only update if the source does not match the recorded source ID
		if (r->info() != m_mySource)
		{
			bncs_string sourceName = "";

			m_mySource = r->info();											// Note the new index
			routerName(m_myMatrixID, m_mySourceDB, m_mySource, sourceName);	// Get the name for selected database
			textPut("text", sourceName, MAIN_PNL, "myMimic");				// Show name on the control
			hostNotify(bncs_string("sourceid=%1").arg(m_mySource));			// Just in case someone wants to use this in connections
		}
	}

	return 0;
}


// ======================================== //
// All database name changes come back here //
// ======================================== //
void MtxDestMimic::databaseCallback(revertiveNotify *r)
{
	// See if the change matches our registered destination 
	if ((r->device() == m_myMatrixID) && (r->index() == m_myDestination) && (r->database() == m_mySourceDB))
	{
		bncs_string sourceName = "";

		routerName(m_myMatrixID, m_mySourceDB, m_mySource, sourceName);	// Get the source name for updated database
		textPut("text", sourceName, MAIN_PNL, "myMimic");				// Show name on the control
	}
}


// ====================================================================== //
// 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 MtxDestMimic::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if( p->value() == "all" )
		{	
			// Return the persisting values for bncs_vis_ed
			bncs_stringlist sl;
			
			sl << bncs_string("myMatrix=%1").arg(m_myMatrixID);
			sl << bncs_string("myDestination=%1").arg(m_myDestination);
			sl << bncs_string("mySourceDB=%1").arg(m_mySourceDB);
			sl << bncs_string("myStatesheet=%1").arg(m_myStateSheet);
			
			return sl.toString( '\n' );
		}

		else if (p->value() == "myMatrix")
		{	
			// Specific value being asked for by a textGet
			return (bncs_string("%1=%2").arg(p->value()).arg(m_myMatrixID));
		}

		else if (p->value() == "myDestination")
		{
			// Specific value being asked for by a textGet
			return (bncs_string("%1=%2").arg(p->value()).arg(m_myDestination));
		}
	}	// END of if( p->command() == "return" )

	
	else if ((p->command() == "instance") && (p->value() != m_instance))
	{	
		// ====================== //
		// INSTANCE being changed //
		// ====================== //
		int dev = 0;
		int dest = 0;

		m_instance = p->value();			// Copy the instance string to our record
		getDev(m_instance, &dev, &dest);	// Ask for driver and contact info from Instances.xml

		// Check if the instance exists, if so process the data
		if ((dev >= MIN_DEVICE) && (dev <= MAX_DEVICE) && (dest >= MIN_DEST))
		{
			// Valid device number present, destination > 0, but still need to check for valid destination
			if (dest <= getRouterSize(dev, DB_DESTINATION))
			{
				checkAndActivate(dev, dest);	// Check the numbers, register for revertives, start poll timer
			}
		}

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


	else if (p->command() == "myMatrix")
	{	
		int newValue = 0;

		// Matrix ID is being set manually. Only accept this data if the instance string is blank.
		if (m_instance == "")
		{
			newValue = p->value().firstInt();

			// Do we now have what we need for a registration? If so activate and poll
			checkAndActivate(newValue, m_myDestination);
		}

		return "";
	}	// END of else if (p->command() == "myMatrix")


	else if (p->command() == "myDestination")
	{
		// Destination ID is being set manually. Only accept this data if the instance string is blank.
		int newValue = 0;

		if (m_instance == "")
		{
			newValue = p->value().firstInt();

			// Do we now have what we need for a registration? If so activate and poll
			checkAndActivate(m_myMatrixID, newValue);
		}
		 
		return "";
	}	// END of else if (p->command() == "myDestination")


	else if (p->command() == "mySourceDB")
	{	
		// Source database to be used for name lookup is being set.
		int newDB = 0;

		newDB = p->value().firstInt();	// Extract the number. Assume the user knows what they are doing, tolerate almost all values.
		if (newDB >= 0)
		{
			if (m_mySourceDB != newDB)
			{
				// Source database has been changed. If there is a name on display, refresh using new database
				bncs_string newName;

				m_mySourceDB = newDB;										// Record updated database number
				routerName(m_myMatrixID, newDB, m_myDestination, newName);	// Get name
				textPut("text", newName, MAIN_PNL, "myMimic");				// Send to mimic display on our panel
			}
		}

		return "";
	}	// END of else if (p->command() == "mySourceDB")
			

	else if (p->command() == "myStatesheet")
	{
		// Statesheet used to set the look of the mimic display is being set.
		if (m_myStateSheet != p->value())	// Apply processing if name has changed.
		{
			m_myStateSheet = p->value();	// Save the value to local memory
			textPut("statesheet", m_myStateSheet, MAIN_PNL, "myMimic");
		}

		return "";
	}	// END of else if (p->command() == "myStatesheet")


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

		sl << "sourceid=*";		
		
		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 << "myMatrix=[value]";
		sl << "myDestination=[value]";
		sl << "mySourceDB=[value]";
		sl << "myStatesheet=[value]";

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

	return "";
}


// ====================== //
// Timer events come here //
// ====================== //
void MtxDestMimic::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 ///////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

// =================================================================================================
// function int checkAndActivate(int dev_id, int dest_id) processes the provided device ID and 
// destination ID, checking that these are both valid BNCS values. If the numbers are valid, a
// revertive registration occurs for the device/destination pair.
//

int MtxDestMimic::checkAndActivate(int dev_id, int dest_id)
{
	// dev_id is valid if it is >= MIN_DEVICE and <= MAX_DEVICE
	// dest_id is valid if it is >= MIN_DEST and <= number of destinations for valid dev_id
	// 
	// If a updated or registration is made, the function returns true, if the values are invalid
	// (eg dev or dest = 0) the function returns false.

	bool validDevice = false;
	bool validDest = false;
	bool updatedSelection = false;
	int oldDevice = 0;

	if ((m_myMatrixID >= MIN_DEVICE) && (m_myMatrixID <= MAX_DEVICE)) validDevice = true;	// Set validDevice flag according to current stored global data.
	oldDevice = m_myMatrixID;																// Keep record of old device for unregister process

	// Check dev_id for valid, only if this value differs from stored m_myMatrixID
	if (dev_id != m_myMatrixID)
	{
		if ((dev_id >= MIN_DEVICE) && (dev_id <= MAX_DEVICE))
		{
			m_myMatrixID = dev_id;			// Store the changed value
			validDevice = true;
			updatedSelection = true;	// Flag that registration change needed
		}
		// If the validity test fails we keep the old value
	}

	// Now validate the destination ID parameter
	if (dest_id != m_myDestination)
	{
		if ((dest_id >= MIN_DEST) && validDevice)
		{
			if (dest_id <= getRouterSize(m_myMatrixID, DB_DESTINATION))	// Check destination index against recorded database size
			{
				m_myDestination = dest_id;		// 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
		if (dest_id >= MIN_DEST)
		{
			validDest = true;
		}
	}	// end of if (dest_id != m_myDestination)

	// If selections have been updated, update the revertive registration as appropriate
	if (updatedSelection)
	{
		if ((oldDevice >= MIN_DEVICE) && (oldDevice <= MAX_DEVICE))
		{
			routerUnregister(oldDevice);	// Clear old registration
		}
		
		if (validDevice && validDest)
		{
			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;
		}
	}	// end if (validDevice)
	
	return false;
}
