/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MODULE NAME		PkgDest_1L.DLL

USED BY			BBC Academy Wood Norton BNCS installation.

VERSION			1.0
DATE			22nd 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 single layer of a package (eg sdi only, or audio only).

The panel designer enters a instance name, a database id for name lookup, which package level 
to control, a statesheet for when the button is active, and the statesheet when the button 
is not active into the script host.

The component uses the designer selected instance to find the matrix and destination it represents.
The matrix id is used to extract the 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, MLDestMimic, that shows the current routed source name.

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 as well as 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 value is -1, the highlight is removed.

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

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

#define MY_PANEL	1
#define PANEL_NAME	"pkgDest1L.bncs_ui"
#define BTTNNAME	"myDest"
#define TALLY1NAME	"tally_L1"

#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_1L)

////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP
PkgDest_1L::PkgDest_1L(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

	myMatrixID = 0;				// Set invalid BNCS driver ID for matrix number
	myDestID = -1;				// Set invalid BNCS destination ID for our destination
	myLevel = 0;				// User selected matrix level
	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 
	L1_driverID = 0;			// Level 1 router driver ID (Video)

	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_1L::~PkgDest_1L()
{
	// 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_1L::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_1L::revertiveCallback(revertiveNotify *r)
{
	// No revertives for this element.

	return 0;	// Default safe return.
}


////////////////////////////////////////////
// All database name changes come back here
void PkgDest_1L::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_1L::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().toInt() == myDestID)
		{
			textPut("statesheet", myBtnSelectState, MY_PANEL, BTTNNAME);
			iAmSelected = true;
		}
		else
		{
			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("namesDatabase=%1").arg(myNamesDatabase);
			sl << bncs_string("uselevel=%1").arg(myLevelTxt);
			sl << bncs_string("buttonlow=%1").arg(myBtnDeselectState);
			sl << bncs_string("buttonhigh=%1").arg(myBtnSelectState);
			sl << bncs_string("tallystate=%1").arg(mytallystate_1);

			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() == "uselevel")
	{
		// We are being told which level of the package we should use.
		myLevelTxt = p->value();
		myLevel = myLevelTxt.toInt();

		// Call the data extract 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") && (mytallystate_1 != p->value()))
	{
		// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
		// State sheet name for 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 "";
	}


	// ***** 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_1L::timerCallback(int id)
{
		timerStop(id);
}


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

///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// function extractElements(void) is called to split a package into the individual elements per layer, combine
// the selection with the chosen layer (parmeter uselevel from visual eitor design) and send the selection data 
// those to the tally mimic button so it can register its interest in revertives from the router.
// The function uses the data stored in our private variables 'myMatrixID', 'myDestID' and 'myLevel' 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
//
// If parameter myLevel=1 this will select the SDI layer, myLevel=2 will select the analogue audio etc.
void PkgDest_1L::extractElements(void)
{
	bncs_string pkg_lvl_count;
	bncs_string cdlist = "";		// Will hold comma delimited list of drivers
	
	// Fetch the data from "our" object in object_settings.xml
	if (mymatrixobject == "")
	{
		// Cannot get object settings as we do not have the object name.
		return;
	}

	// Check that we have a user selected layer (> 0)
	if (myLevel == 0)
	{
		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

	// Check that we have sufficient levels to meet the requested layer. Note that the first matrix layer 
	// is the third parameter in the comma delimited list.
	if (mydrivers.count() < (myLevel + 2))
	{
		return;
	}

	pkg_names = mydrivers[0].toInt();			// Recover driver IDs
	pkg_content = mydrivers[1].toInt();			// Driver ID with comma delimited lists of crosspoints

	L1_driverID = mydrivers[myLevel+1].toInt();	// Remember indexing into list starts at 0, so [2] is first layer

	// Now extract the destinations for selected level - provided we already know our own destination ID
	if (myDestID >= BNCS_DEST_MIN)
	{
		bncs_string mydestlist = "";

		routerName(pkg_content, DEST_DBASE, myDestID, mydestlist);

		// Divide the list into elements in local variable dl. dl[0] is level 1 destination etc.
		bncs_stringlist dl(mydestlist, ',');
		L1_destination = (dl[myLevel-1] == "-") ? -1 : dl[myLevel-1].toInt();	// Extract the destination id, or -1 to replace "-"

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

}
