/*
PROGRAM NAME	TallyAssoc.dll

USED BY			BBC Academy Studio C installation

Version	Date		Programmer				Comments
======= ==========  ==========              ========
1.0		12-08-2017	Andy Woodhouse			Initial Release

It is operationally desirable that devices which output video and key should show a monitor
tally light when either the fill or the key are active on mixer output. The Stdui C cue director
contains code to use a Key Association table which holds the list of which devices are linked
for tally output operations. The table is held in the tally infodriver, slots 901 to 1128 and
loaded into teh cue director when it starts. Any changes to the table are processed from the
revertives issued by the tally infodriver.

This module provides a visual editor for creating and removing the associations. The user selectes
the primary source which shows the source and associated key details in boxes on the dialog box.
If the IDs match the source has no association. When the primry source is selected, a second
source selector block becomes visible, and the user can select the associated tally using the keys
on the the second selector. When the association dta is selected, it is updated to the network store.

*/

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

#define MAIN_PNL	1
#define MAIN_PNL_NAME	"Associate.bncs_ui"

#define SRCDB 0			// Sources names database ID
#define SDIRTRINST	"StC SDI Router"
#define TALLYIDINST	"CTA Tally Infodriver"

#define TALLYASSOC001 901
#define TALLYASSOC128 TALLYASSOC001+SDI_MTX_SIZE -1

#define TIMER_SETUP	1
#define TIMER__SETUP_PERIOD	500


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


//////////////////////////////////////////////////
// Constructor - equivalent to ApplCore STARTUP //
//////////////////////////////////////////////////
TallyAssoc::TallyAssoc( bncs_client_callback * parent, const char * path ) : bncs_script_helper( parent, path )
{
	// Initialise module wide variables
	sdi_dev = 0;
	tally_dev = 0;
	now_primary = 0;			// No selections yet made
	now_assoc = 0;
	new_assoc = 0;
	pollDone = false;			// Will be set true once all polls have been received from tally infodriver.
	inEdit = false;				// No edit displays yet on show.

	for (int i = 0; i <= SDI_MTX_SIZE; i++) keyAssoc[i] = 0;
	for (int i = 0; i <= SDI_MTX_SIZE; i++) keyTallied[i] = false;

	// Get the device ids for teh sdi router and the tally infodriver
	getDev(SDIRTRINST, &sdi_dev);		// Extract the sdi router driver id
	getDev(TALLYIDINST, &tally_dev);	// Extract tally infodriver id

	panelInit(MAIN_PNL, MAIN_PNL_NAME);		// Make panel available for control cnfiguration
	hideAssocSelect();
	controlDisable(MAIN_PNL, "Associate");
	controlDisable(MAIN_PNL, "ClearAssoc");
	
	panelShow(MAIN_PNL, MAIN_PNL_NAME);		// Show user interface panel

	// Register for revertives from the tally infodriver that stores the key association table
	infoRegister(tally_dev, TALLYASSOC001, TALLYASSOC128);
	infoPoll(tally_dev, TALLYASSOC001, TALLYASSOC128);
	timerStart(TIMER_SETUP, TIMER__SETUP_PERIOD);	// Allow 1 second for all poll values to be returned
}


///////////////////////////////////////////////////
// Destructor - equivalent to ApplCore CLOSEDOWN //
///////////////////////////////////////////////////
TallyAssoc::~TallyAssoc()
{
	if (tally_dev) infoUnregister(tally_dev);
}


///////////////////////////////////////////////////
// All button pushes and notifications come here //
///////////////////////////////////////////////////
void TallyAssoc::buttonCallback(buttonNotify *b)
{
	if (b->panel() == MAIN_PNL)
	{
		if (b->id() == "Source")
		{
			bncs_string primName, assocName;

			// Have a new source selection. A value of -1 is a new group selection made, source deselected
			if (b->value() == -1)
			{
				// Disable the selection buttons
				hideAssocSelect();
				controlDisable(MAIN_PNL, "ClearAssoc");
			}
			else
			{
				// Show the selection buttons
				showAssocSelect();
				controlEnable(MAIN_PNL, "ClearAssoc");
				inEdit = true;
				now_primary = b->value().toInt();		// Keep a copy of current selection
				now_assoc = keyAssoc[now_primary];		// Note the current association

				routerName(sdi_dev, SRCDB, now_primary, primName);		// Get primary source name
				textPut("text", b->value(), MAIN_PNL, "PrimaryNo");		// Show ID and name
				textPut("text", primName, MAIN_PNL, "PrimaryName");

				// Show association link
				routerName(sdi_dev, SRCDB, now_assoc, assocName);		// Get association source name
				textPut("text", bncs_string("%1").arg(now_assoc), MAIN_PNL, "AssocNo");
				textPut("text", assocName, MAIN_PNL, "AssocName");
			}
		
			return;
		}	// End of if (b->id() == "Source")


		// Check for new association selection
		if (b->id() == "AssocSource")
		{
			bncs_string newAssocName;

			if (b->value() == "-1")
			{
				// A group change style change has occured. Blank the displays
				new_assoc = 0;
				textPut("text=", MAIN_PNL, "NewAssocNo");		// Clear number
				textPut("text=---", MAIN_PNL, "NewAssocName");	// Clear name
				controlDisable(MAIN_PNL, "Associate");			// Disable ability to set association
			}
			else
			{
				// Note the selected source ID, then show the properties on User Interface
				new_assoc = b->value().toInt();		// Get the new source ID
				routerName(sdi_dev, SRCDB, new_assoc, newAssocName);
				textPut("text", bncs_string("%1").arg(new_assoc), MAIN_PNL, "NewAssocNo");
				textPut("text", newAssocName, MAIN_PNL, "NewAssocName");
				controlEnable(MAIN_PNL, "Associate");
			}

			return;
		}


		// Check for a "Set Association" button press
		if (b->id() == "Associate")
		{
			// Making the association means settin the primary source association to the new selection, and
			// making the new selection have an association with the primary source.
			infoWrite(tally_dev, bncs_string(new_assoc), TALLYASSOC001 + now_primary - 1);			// Update the primary association
			infoWrite(tally_dev, bncs_string(now_primary), TALLYASSOC001 + new_assoc - 1, true);	// Update the association to link to primary
			
			// Send a deselect command to the two source selectors
			textPut("index=-1", MAIN_PNL, "Source");
			textPut("index=-1", MAIN_PNL, "AssocSource");

			hideAssocSelect();							// Tidy and hide
			clearDataMimics();
			inEdit = false;								// Edit displays not enabled.
			controlDisable(MAIN_PNL, "Associate");
			controlDisable(MAIN_PNL, "ClearAssoc");

			return;
		}


		// Check for Clear Association button press
		if (b->id() == "ClearAssoc")
		{
			// Sources that have no associtation have their association ID's equal to their own ID.
			infoWrite(tally_dev, bncs_string(now_primary), TALLYASSOC001 + now_primary - 1);	// Remove association from primary
			infoWrite(tally_dev, bncs_string(now_assoc), TALLYASSOC001 + now_assoc - 1, true);	// Remove association from secondary
			
			// Send a deselect command to the two source selectors
			textPut("index=-1", MAIN_PNL, "Source");
			textPut("index=-1", MAIN_PNL, "AssocSource");

			hideAssocSelect();
			clearDataMimics();
			inEdit = false;
			controlDisable(MAIN_PNL, "Associate");
			controlDisable(MAIN_PNL, "ClearAssoc");

			return;
		}

	}	// End of if (b->panel() == MAIN_PNL)
}


//////////////////////////////
// All revertives come here //
//////////////////////////////
int TallyAssoc::revertiveCallback(revertiveNotify *r)
{
	if (r->device() == tally_dev)
	{
		int idx = r->index() - TALLYASSOC001 + 1;	// Create index in range 1 to 128
		keyAssoc[idx] = r->sInfo().toInt();			// Save value into our array
		keyTallied[idx] = true;						// Used to check for all tallies received.
		
		if (pollDone)
		{
			// All tallies have been received so any revertives may now be considered "Operational"
			if (inEdit)
			{
				if (now_primary != 0)
				{
					bncs_string assocName;

					// We have a current primary source selected. So need to update the key association displays
					routerName(sdi_dev, SRCDB, keyAssoc[idx], assocName);		// Get association source name
					textPut("text", bncs_string("%1").arg(keyAssoc[idx]), MAIN_PNL, "AssocNo");
					textPut("text", assocName, MAIN_PNL, "AssocName");
				}
			}

			displayAssociations();		// Now update the Active associations list
		}	// End of if (pollDone) ...
	}
	return 0;
}


//////////////////////////////////////////////
// All database name changes come back here //
//////////////////////////////////////////////
void TallyAssoc::databaseCallback(revertiveNotify *r)
{
}


////////////////////////////////////////////////////////////////////////////
// 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 TallyAssoc::parentCallback(parentNotify *p)
{
	if (p->command() == "return")
	{
		if( p->value() == "all" )
		{	
			// Persisting values for bncs_vis_ed
			bncs_stringlist sl;
			sl << bncs_string("");
			return sl.toString('\n');
		}
	}

	// ***** CONNECTIONS EVENTS HELPER LIST *****
	else if (p->command() == "_events")
	{	
		// Helper-list of everything in this component generated by hostNotify's
		bncs_stringlist sl;
		sl << "";		
		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 TallyAssoc::timerCallback(int id)
{
	switch( id )
	{
	case TIMER_SETUP:
		// Check if all tally revertives have been received.
		if (checkAllTallyRx())
		{
			// All tallies received, internal store up to date.
			timerStop(id);
			pollDone = true;
			displayAssociations();
		}
		else
		{
			// Repoll for all tallies
			infoPoll(tally_dev, TALLYASSOC001, TALLYASSOC128);
		}
		
		break;

	default:	// Unhandled timer event
		timerStop(id);
		break;
	}
}


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


void TallyAssoc::hideAssocSelect(void)
{
	// Hide the various panel elements that make the Key association selection group
	controlHide(MAIN_PNL, "AssocSrcGroup");		// Group Selector
	controlHide(MAIN_PNL, "AssocSource");		// Source selector
	controlHide(MAIN_PNL, "BG02");				// Background boz
	controlHide(MAIN_PNL, "ID_Assoc");			// Text Ident
}


void TallyAssoc::showAssocSelect(void)
{
	// Hide the various panel elements that make the Key association selection group
	controlShow(MAIN_PNL, "AssocSrcGroup");		// Group Selector
	controlShow(MAIN_PNL, "AssocSource");		// Source selector
	controlShow(MAIN_PNL, "BG02");				// Background boz
	controlShow(MAIN_PNL, "ID_Assoc");			// Text Ident
}


bool TallyAssoc::checkAllTallyRx(void)
{
	// Count the number of entries in keyTall
	int sum = 0;

	for (int i = 0; i <= SDI_MTX_SIZE; i++) if (keyTallied[i]) sum++;

	return (sum == SDI_MTX_SIZE) ? true : false;
}


void TallyAssoc::clearDataMimics(void)
{
	textPut("text=", MAIN_PNL, "PrimaryNo");
	textPut("text=", MAIN_PNL, "PrimaryName");
	textPut("text=", MAIN_PNL, "AssocNo");
	textPut("text=", MAIN_PNL, "AssocName");
	textPut("text=", MAIN_PNL, "NewAssocNo");
	textPut("text=", MAIN_PNL, "NewAssocName");
}


void TallyAssoc::displayAssociations(void)
{
	textPut("clear", MAIN_PNL, "Active");				// Clear the current list
	for (int idx = 1; idx <= SDI_MTX_SIZE; idx++)
	{
		if (idx != keyAssoc[idx])
		{
			// Have an active association
			bncs_string assoc1, assoc2;
			routerName(sdi_dev, SRCDB, idx, assoc1);	// Get primary name
			routerName(sdi_dev, SRCDB, keyAssoc[idx], assoc2);
			textPut("add", assoc1 + " <--> " + assoc2, MAIN_PNL, "Active");
		}
	}
}
