/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		PkgDest_2L.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			23rd April 2017
PROGRAMMER		Andy Woodhouse

Overview
========
This module implements a combination of destination selection button and source mimic for use in
building panels that control a two lvels of matrix (eg sdi only + analogue audio or, or analogue 
audio + AES audio).

The panel designer enters an instance name, a database id for source name lookup, statesheet names
for selected and non-selected button, statesheet names for the mimic displays, and the levels to
be used into the script host user tables.

The component uses the designer selected instance to find the matrix and destination it represents.
The matrix id is used to extract the destination name from the dev_xxx.ini detination database, 
using the database number entered in the script host parameter field. The panel designer can 
enter a database number to use, or leave the selection at the default value of 1. The component
uses the services of another component, MtxDestMimic, that shows the current routed source name. 
When SingleMtxDest receives an instance name, is sends that name to MtxDestMimic so that it can
register for revertives and poll for the current setting.

When the button is pressed, the component tells the host the destination value it extracted from
the instance name. This value should be sent to the other selectors in the panel including itself.
When the component receives a destination value it compares the destination id with its own 
destination. If these match, the component sets the highlight state provided by the design-time
date. If the button destination does not match, or if the destination value is -1, the highlight
is removed.

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

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

#define MY_PANEL	1
#define PANEL_NAME	"pkgDest2L.bncs_ui"
#define BTTNNAME	"myDest"
#define TALLY1NAME	"tally_L1"
#define TALLY2NAME	"tally_L2"

#define BNCS_DRV_MIN 1
#define BNCS_DRV_MAX 999
#define BNCS_DEST_MIN 1
#define DEST_DBASE 1

#define UNUSED_LEVEL_TEXT	"Unused Level"

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

////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP
PkgDest_2L::PkgDest_2L(bncs_client_callback * parent, const char * path) : bncs_script_helper(parent, path)
{
	// Initialise our working storage.
	m_instance = "";			// Set instance name to null string
	mymatrixobject = "";		// Provides access to matrix elements of the package
	myBtnDeselectState = "";	// Statesheet name for selected state
	myBtnSelectState = "";		// Statesheet name for deselected state
	mytallystate_1 = "";		// Statesheet name for level 1 router source display
	mytallystate_2 = "";		// Statesheet name for level 2 router source display
//	mytallystate_3 = "";		// Statesheet name for level 3 router source display
	myunusedleveltext = UNUSED_LEVEL_TEXT;		// Text that appears on a mimic when the level is not used by the package

	myMatrixID = 0;				// Set invalid BNCS driver ID for matrix number
	myDestID = -1;				// Set invalid BNCS destination ID for our destination
	myNamesDatabase = 1;		// Database id for destination name fetch
	pkg_names = 0;				// Package names driver ID extracted from object settings
	pkg_content = 0;			// Package contents driver ID extracted from 
	mylevel1 = 0;
	mylevel2 = 0;
	L1_driverID = 0;			// Level 1 router driver ID (Video)
	L2_driverID = 0;			// Level 2 router driver ID (Audio)
//	L3_driverID = 0;			// Level 3 router driver ID (AES)

	iAmSelected = false;		// We are not currently selected

	// Show our intelligent button and mimic panel
	panelShow(MY_PANEL, PANEL_NAME);
}


/////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN
PkgDest_2L::~PkgDest_2L()
{
	// If we have registered for revertives to get name changes, remove the registration.
	if (myMatrixID >= BNCS_DRV_MIN)
	{
		routerUnregister(myMatrixID);
	}
}


/////////////////////////////////////////////////
// All button pushes and notifications come here
void PkgDest_2L::buttonCallback(buttonNotify *b)
{
	// Be defensive - check panel_id and button_id of sender
	if ((b->panel() == MY_PANEL) && (b->id() == BTTNNAME))
	{
		if (myDestID > BNCS_DEST_MIN)	// Only send reports for valid destination IDs
		{
			hostNotify(bncs_string("selected=%1").arg(myDestID));
		}
	}
}


////////////////////////////
// All revertives come here.
int PkgDest_2L::revertiveCallback(revertiveNotify *r)
{
	// No revertives for this element.

	return 0;	// Default safe return.
}


////////////////////////////////////////////
// All database name changes come back here
void PkgDest_2L::databaseCallback(revertiveNotify *r)
{
	// May have a destination name change to manage.
	if ((r->device() == myMatrixID) && (r->database() == myNamesDatabase) && (r->index() == myDestID))
	{
		// Database name change is for our destination button, so update the text name shown.
		textPut("text", r->sInfo(), MY_PANEL, BTTNNAME);
	}
}


//////////////////////////////////////////////////////////////////////////
// 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 PkgDest_2L::parentCallback(parentNotify *p)
{
	if (p->command() == "selected")
	{
		// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// Parent is telling us a selection destination value. If this ID is ours,
		// we must show the selected statesheet, if it is another destination or
		// -1 we must set the deselected state.

		if ((p->value() == "-1") && (iAmSelected))
		{
			// I was selected, but am being deselected by a "deselect all"
			textPut("statesheet", myBtnDeselectState, MY_PANEL, BTTNNAME);
			iAmSelected = false;
			return "";
		}

		if ((p->value().toInt() == myDestID) && (!iAmSelected))
		{
			// I have been selected, and I was not already selected
			textPut("statesheet", myBtnSelectState, MY_PANEL, BTTNNAME);
			iAmSelected = true;
			return "";
		}
		
		if ((p->value().toInt() != myDestID) && (iAmSelected))
		{
			// I was selected, but another instance has been set active
			textPut("statesheet", myBtnDeselectState, MY_PANEL, BTTNNAME);
			iAmSelected = false;
			return "";
		}

	}


	else if (p->command() == "return")
	{
		if (p->value() == "all")
		{
			// +++++++++++++++++++++++++++++++++
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;

			sl << bncs_string("matrixobject=%1").arg(mymatrixobject);
			sl << bncs_string("uselevels=%1").arg(myuselevels);
			sl << bncs_string("namesDatabase=%1").arg(myNamesDatabase);
			sl << bncs_string("buttonlow=%1").arg(myBtnDeselectState);
			sl << bncs_string("buttonhigh=%1").arg(myBtnSelectState);
			sl << bncs_string("tallystate_L1=%1").arg(mytallystate_1);
			sl << bncs_string("tallystate_L2=%1").arg(mytallystate_2);
			sl << bncs_string("unusedleveltext=%1").arg(myunusedleveltext);

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


	else if ((p->command() == "instance") && (p->value() != m_instance))
	{
		// +++++++++++++++++++++++++++++++++++++
		// Our instance is being set or changed.
		int d_id = 0;
		int offset = 0;

		if ((myMatrixID >= BNCS_DRV_MIN) && (myMatrixID <= BNCS_DRV_MAX))
		{
			// Delete registration that we hold for database name change reports
			routerUnregister(myMatrixID);
		}

		m_instance = p->value();	// Save the new instance value
		myMatrixID = 0;				// Set safe values for destination ID 
		myDestID = -1;				// and matrix ID

		getDev(m_instance, &d_id, &offset);	// Get the new package name device and offset/index for our destination

		// Validate values and save to working variables
		if ((d_id >= BNCS_DRV_MIN) && (d_id <= BNCS_DRV_MAX) && (offset >= BNCS_DEST_MIN))
		{
			bncs_string destName = "";

			myMatrixID = d_id;	// Save the extracted router ID and destination ID
			myDestID = offset;

			// Get the destination name and place it on the button
			routerName(myMatrixID, myNamesDatabase, myDestID, destName);
			textPut("text", destName, MY_PANEL, BTTNNAME);

			// Register a destination so we get database name changes reported
			routerRegister(myMatrixID, 1, 1);

			// Extract the individual elements of the package and send them to the tally mimic displays
			extractElements();
		}
		else	// Not a valid instance - so play safe
		{
			// Remove name from button, set deselected state
			textPut("text=Unknown|Destination", MY_PANEL, BTTNNAME);
			textPut("statesheet", myBtnDeselectState, MY_PANEL, BTTNNAME);
			hostNotify("selection=-1");
		}

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


	else if (p->command() == "matrixobject")
	{
		// We are being told object name that holds the level settings data
		mymatrixobject = p->value();

		// Call the data extract routine
		extractElements();
	}


	else if (p->command() == "uselevels")
	{
		// We are being told object name that holds the level settings data
		myuselevels = p->value();

		// Call the data extract and process routine
		extractElements();
	}


	else if (p->command() == "namesDatabase")
	{
		// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// Name database to use for destination is being changed.
		if (p->value().toInt() > 0)
		{
			myNamesDatabase = p->value().toInt();
			// Validate values and save to working variables
			if ((myMatrixID >= BNCS_DRV_MIN) && (myMatrixID <= BNCS_DRV_MAX) && (myDestID >= BNCS_DEST_MIN))
			{
				bncs_string destName = "";

				// Get the destination name and place it on the button
				routerName(myMatrixID, myNamesDatabase, myDestID, destName);
				textPut("text", destName, MY_PANEL, BTTNNAME);
			}
		}

		return "";
	}


	else if( p->command() == "buttonlow" )
	{	
		// ++++++++++++++++++++++++++++++++++++++
		// Deselected state sheet name being set.
		myBtnDeselectState = p->value();

		if (!iAmSelected)
		{
			// Update state sheet for button
			textPut("statesheet", myBtnDeselectState, MY_PANEL, BTTNNAME);
		}

		return "";
	}


	else if (p->command() == "buttonhigh")
	{	
		// ++++++++++++++++++++++++++++++++++++
		// Selected state sheet name being set.
		myBtnSelectState = p->value();
		
		if (iAmSelected)
		{
			// Update state sheet for button
			textPut("statesheet", myBtnSelectState, MY_PANEL, BTTNNAME);
		}

		return "";
	}

	else if ((p->command() == "tallystate_L1") && (mytallystate_1 != p->value()))
	{
		// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// State sheet name for layer 1 tally mimic is being set.
		mytallystate_1 = p->value();									// Keep local copy
		textPut("statesheet", mytallystate_1, MY_PANEL, TALLY1NAME);	// Update state sheet for Layer 1 mimic

		return "";
	}


	else if ((p->command() == "tallystate_L2") && (mytallystate_2 != p->value()))
	{
		// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// State sheet name for layer 2 tally mimic is being set.
		mytallystate_2 = p->value();									// Keep local copy
		textPut("statesheet", mytallystate_2, MY_PANEL, TALLY2NAME);	// Update state sheet for Layer 2 mimic

		return "";
	}


	else if (p->command() == "unusedleveltext")
	{
		// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// Text display for unused level is being set
		myunusedleveltext = p->value();									// Keep local copy
		textPut("unusedtext", myunusedleveltext, MY_PANEL, TALLY1NAME);	// Send to all mimic layers
		textPut("unusedtext", myunusedleveltext, MY_PANEL, TALLY2NAME);
		return "";
	}


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

		sl << "selected=*";		
		
		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 << "selected=[value]";
		
		return sl.toString( '\n' );
	}

	return "";
}

/////////////////////////////////////////
// Timer events come here
// No timers used - minimse catchall code
void PkgDest_2L::timerCallback(int id)
{
		timerStop(id);
}


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

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// function extractElements(void) is called to split a package into the individual elements per layer, and send 
// those to the tally mimic buttons so they can register their interest. The function uses the data stored in
// our private variables myMatrixID and myDestID to find the package number to process. The function also
// requires information from object_settings.xml held in named object "mymatrixobject" passed to us by the
// application panel designer. In that object is a comma delimited string with an id of "driver_ids". The string
// lists the following device id's in left to right order:
// (1) Package Names driver ID - which we also received via the instance (or else there is a config error)
// (2) Package crosspoints driver ID - has comma delimited lists of source and destination crosspoints
// (3) SDI Video driver ID
// (4) Analogue/Default Audio driver ID
// (5) AES Audio driver ID
//
// This function may be called from the code that processes:
// a)	A change of 'instance' value
// b)	A change of 'matrixobject' name
// c)	A change of 'uselevels' value
//
void PkgDest_2L::extractElements(void)
{
	bncs_string pkg_lvl_count;
	bncs_string cdlist = "";		// Will hold comma delimited list of drivers
	
	// Do not do any processing unless we have strings for 'instance', 'matrixobject', and 'uselevels'
	if ((m_instance == "") || (mymatrixobject == "") || (myuselevels == ""))
	{
		return;
	}

	// We have non-blank text strings, but have not yet proved the inner data.
	// Check we have at least two levels available in this package system description
	pkg_lvl_count = getObjectSetting(mymatrixobject, "package_levels");
	if (pkg_lvl_count.toInt() < 2)
	{
		debug("Insufficient levels defined in this package object. Unable to extract levels\n");
		return;
	}

	// Get the comma delimited list of driver id's to variable cdlist.
	cdlist = getObjectSetting(mymatrixobject, "driver_ids");

	// Extract the list into elements. Use bncs_stringlist facilities as a programming aid.
	bncs_stringlist mydrivers(cdlist, ',');		// Extract list of strings using a comma as delimiter
	pkg_names = mydrivers[0].toInt();			// Recover name driver ID
	pkg_content = mydrivers[1].toInt();			// Recover crosspoint list driver ID

	// Now we need to mask out wanted levels using data in myuselevels
	bncs_stringlist myLevelData(myuselevels, ',');

	// How many levels were we asked to process. Support 1 or 2 only
	if (myLevelData.count() > 2)
	{
		debug(bncs_string("Too many levels in the uselevels parameter. Expected maximum of two, received %1\n").arg(myLevelData.count()));
		return;
	}

	// Number of levels after above test is a maximum of two.
	if (myLevelData.count() == 2)
	{
		// User has provided two levels to process
		mylevel1 = myLevelData[0];
		mylevel2 = myLevelData[1];
	}
	else
	{
		// User has only provided one level to monitor. Set a default for level 2
		mylevel1 = myLevelData[0];
		mylevel2 = 0;
	}

	// Process each level in turn, checking for a 0 on the level selection
	// Start with level 1
	if (mylevel1 == 0)
	{
		// Invalid selection, so make the level unused
		L1_driverID = 0;		// Invalid driver number
		L1_destination = -1;	// This sets a not used mode in the mimic
	}
	else
	{
		// Valid selection, if it is less than or equal to the number of levels reported by pkg_lvl_count
		if (mylevel1 <= pkg_lvl_count.toInt())
		{
			L1_driverID = mydrivers[mylevel1 + 1];	// Minimum mylevel value is 1, so mylevel+1 is first entry in driver list

			// Now extract the destination crosspoint
			bncs_string mydestlist = "";
			routerName(pkg_content, DEST_DBASE, myDestID, mydestlist);	// Get the list of crosspoints

			bncs_stringlist dl(mydestlist, ',');		// Get the list of crosspoints
			L1_destination = (dl[mylevel1 - 1] == "-") ? -1 : dl[mylevel1 - 1].toInt();	// Convert our level to an integer
		}
	}
	// Repeat for level 2
	if (mylevel2 == 0)
	{
		// Invalid selection, so make the level unused
		L2_driverID = 0;		// Invalid driver number
		L2_destination = -1;	// This sets a not used mode in the mimic
	}
	else
	{
		// Valid selection, if it is less than or equal to the number of levels reported by pkg_lvl_count
		if (mylevel1 <= pkg_lvl_count.toInt())
		{
			L2_driverID = mydrivers[mylevel2 + 1];	// Minimum mylevel value is 1, so mylevel+1 is first entry in driver list

			// Now extract the destination crosspoint
			bncs_string mydestlist = "";
			routerName(pkg_content, DEST_DBASE, myDestID, mydestlist);	// Get the list of crosspoints

			bncs_stringlist dl(mydestlist, ',');		// Get the list of crosspoints
			L2_destination = (dl[mylevel2 - 1] == "-") ? -1 : dl[mylevel2 - 1].toInt();	// Convert our level to an integer
		}
	}

	// Send the driver and crosspoint data to the tally mimic displays
	textPut("driverid", L1_driverID, MY_PANEL, TALLY1NAME);
	textPut("destid", L1_destination, MY_PANEL, TALLY1NAME);
	textPut("driverid", L2_driverID, MY_PANEL, TALLY2NAME);
	textPut("destid", L2_destination, MY_PANEL, TALLY2NAME);

}
